I am a fucking noob. I don't know how to do this properly, so I created a programmatic way to do this for-realium :3 :3 :3
/FreeCAD Programmatically Bisect Two Back-to-Back Symmetrical Triangles Into Four Right Triangles.md
Last active
July 7, 2026 12:50
-
-
Save whinee/3b9ac29a1f83b7c79d4f9087db47aa00 to your computer and use it in GitHub Desktop.
FreeCAD Programmatically Bisect Two Back-to-Back Symmetrical Triangles Into Four Right Triangles
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import math, traceback, Part | |
| from collections import namedtuple | |
| Point = namedtuple("Point", "x y z") | |
| def line_intersect_2d(p1, p2, p3, p4): | |
| x1, y1, x2, y2, x3, y3, x4, y4 = p1.x, p1.y, p2.x, p2.y, p3.x, p3.y, p4.x, p4.y | |
| a, b, c, d, e, f = (x1*y2 - y1*x2), (x3 - x4), (y3 - y4), (x1 - x2), (y1 - y2), (x3*y4 - y3*x4) | |
| denom = d*c-e*b | |
| if abs(denom) < 1e-9: | |
| raise ValueError("Lines are parallel") | |
| return FreeCAD.Vector((a*b-d*f)/denom, (a*c-e*f)/denom, p1.z) | |
| def centroid(pts): | |
| n = len(pts) | |
| return FreeCAD.Vector( | |
| sum(p.x for p in pts)/n, | |
| sum(p.y for p in pts)/n, | |
| sum(p.z for p in pts)/n, | |
| ) | |
| def make_polygons(ls_ls): | |
| return [Part.Face(Part.makePolygon(ls)) for ls in ls_ls] | |
| def _main(original_shape_name, new_shape_name, tol): | |
| sel = Gui.Selection.getSelectionEx() | |
| orig_faces = FreeCAD.ActiveDocument.getObject(original_shape_name).Shape.Faces | |
| face_count = sum(1 for s in sel for name in s.SubElementNames if name.startswith("Face")) | |
| assert face_count == 2, f"Expected exactly 2 faces, got {face_count} — reselect!" | |
| pts = set() | |
| for s in sel: | |
| for name, sub in zip(s.SubElementNames, s.SubObjects, strict=True): | |
| if name.startswith("Face"): | |
| for v in sub.Vertexes: | |
| pts.add(Point(x=v.X, y=v.Y, z=v.Z)) | |
| assert len(pts) == 4, f"Expected exactly 4 unique points, got {len(pts)} — check for a shared-edge mismatch or duplicate geometry!" | |
| cx, cy = (sum(p.x for p in pts) / 4), (sum(p.y for p in pts) / 4) | |
| p0, p1, p2, p3 = sorted(pts, key=lambda p: math.atan2(p.y - cy, p.x - cx)) | |
| I = line_intersect_2d(p0, p2, p1, p3) | |
| f1, f2, f3, f4 = make_polygons(([p0, p1, I, p0], [p1, p2, I, p1], [p2, p3, I, p2], [p3, p0, I, p3])) | |
| c1, c2 = centroid([p0, p2, p1]), centroid([p1, p3, p0]) | |
| kept_faces = [] | |
| for f in orig_faces: | |
| fc = centroid([FreeCAD.Vector(v.X, v.Y, v.Z) for v in f.Vertexes]) | |
| if fc.distanceToPoint(c1) < tol or fc.distanceToPoint(c2) < tol: | |
| continue | |
| kept_faces.append(f) | |
| face_difference = (len(orig_faces) - len(kept_faces)) | |
| assert face_difference == 2, f"Expected exactly difference of unique points, got {face_difference} — Change `tol` parameter!" | |
| Part.show(Part.makeCompound([*kept_faces, f1, f2, f3, f4]), new_shape_name) | |
| def main(original_shape_name, new_shape_name, tol): | |
| try: | |
| _main(original_shape_name=original_shape_name, new_shape_name=new_shape_name, tol=tol) | |
| except BaseException: | |
| traceback.print_exc() | |
| main(original_shape_name="CKB_119t_002", new_shape_name="CKB_119t_", tol=0.7) | |
| print() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import math,traceback as B,Part as H | |
| from collections import namedtuple as C | |
| U,E,A=C('Point','x y z'),len,sum | |
| def V(g,h,i,j): | |
| B,C,D,E,F,G,H,I=g.x,g.y,h.x,h.y,i.x,i.y,j.x,j.y;J,K,L,M,N,O=B*E-C*D,F-H,G-I,B-D,C-E,F*I-G*H;A=M*L-N*K | |
| if abs(A)<1e-09:raise ValueError('Lines are parallel') | |
| return FreeCAD.Vector((J*K-M*O)/A,(J*L-N*O)/A,g.z) | |
| def J(a):B=E(a);return FreeCAD.Vector(A(A.x for A in a)/B,A(A.y for A in a)/B,A(A.z for A in a)/B) | |
| def W(a):return[H.Face(H.makePolygon(A))for A in a] | |
| def D(a,b,c): | |
| T='Face';M=Gui.Selection.getSelectionEx();N=FreeCAD.ActiveDocument.getObject(a).Shape.Faces;O=A(1 for A in M for B in A.SubElementNames if B.startswith(T));assert O==2,f"Expected exactly 2 faces, got {O} — reselect!";B=set() | |
| for P in M: | |
| for(X,Y)in zip(P.SubElementNames,P.SubObjects,strict=True): | |
| if X.startswith(T): | |
| for K in Y.Vertexes:B.add(U(x=K.X,y=K.Y,z=K.Z)) | |
| assert E(B)==4,f"Expected exactly 4 unique points, got {E(B)} — check for a shared-edge mismatch or duplicate geometry!";Z,d=A(A.x for A in B)/4,A(A.y for A in B)/4;C,D,F,G=sorted(B,key=lambda p:math.atan2(p.y-d,p.x-Z));I=V(C,F,D,G);e,f,g,h=W(([C,D,I,C],[D,F,I,D],[F,G,I,F],[G,C,I,G]));i,j=J([C,F,D]),J([D,G,C]);L=[] | |
| for Q in N: | |
| R=J([FreeCAD.Vector(A.X,A.Y,A.Z)for A in Q.Vertexes]) | |
| if R.distanceToPoint(i)<c or R.distanceToPoint(j)<c:continue | |
| L.append(Q) | |
| S=E(N)-E(L);assert S==2,f"Expected exactly difference of unique points, got {S} — Change `tol` parameter!";H.show(H.makeCompound([*L,e,f,g,h]),b) | |
| def main(original,new,tol): | |
| try:D(original,new,tol) | |
| except BaseException:B.print_exc() | |
| main(original='CKB_119t_003',new='CKB_119t_',tol=.7) | |
| print() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment