from pathlib import Path import os, time, traceback import FreeCAD as A, FreeCADGui as G, Part, Import from pivy import coin V=A.Vector R=Path(r'C:\projects\wheel'); O=R/'models'/'kfl_3030_concept'; O.mkdir(parents=True,exist_ok=True) def box(x,y,z,dx,dy,dz): return Part.makeBox(dx,dy,dz,V(x,y,z)) def cy(r,h,x,y,z,d=V(0,0,1)): return Part.makeCylinder(r,h,V(x,y,z),d) def sx(x,y,z,r,h): return box(x-2,y-r,z,4,2*r,h).fuse(cy(r,h,x-2,y,z)).fuse(cy(r,h,x+2,y,z)) def sy(x,lo,hi,z,r,h): return box(x-r,lo,z,2*r,hi-lo,h).fuse(cy(r,h,x,lo,z)).fuse(cy(r,h,x,hi,z)) def sz(x,y,lo,hi,r,h): return box(x-r,y,lo,2*r,h,hi-lo).fuse(cy(r,h,x,y,lo,V(0,1,0))).fuse(cy(r,h,x,y,hi,V(0,1,0))) def add(d,n,s,c): o=d.addObject('PartDesign::Feature',n);o.Shape=s.removeSplitter();o.Label=n;o.ViewObject.ShapeColor=c;o.ViewObject.LineColor=(.15,.15,.15);return o def cv(a,b): return a.common(b).Volume if a.BoundBox.intersect(b.BoundBox) else 0. ok=False try: d=A.openDocument(str(R/'models'/'kfl_double_crossbar_concept'/'Assembly.FCStd'));d.Label='3030 base and purchased long-slotted angles (concept)' for o in list(d.Objects): if o.Name.startswith(('CompactPlate','LongAngle','CrossAngle','Crossbeam','BoardM5_','FootM5_','CrossTopM5_','CrossSideM5_')):d.removeObject(o.Name) d.recompute() for i,x in enumerate((-50.6200577,50.6200577)): q=d.getObject('NewRail%d'%i);q.Shape=box(x-15,-85,-215,30,30,235);q.Label='3030 longitudinal base rail - solid envelope, groove omitted' parts=[];plates=[];cross=[];angles=[];fast=[] for i,z in enumerate((-84.,-187.)): face=z if i==0 else z+8; sign=-1 if i==0 else 1 # Crossbeams butt between longitudinal members at the same height, centreline +/-35.6200577. z0=face-30 if i==0 else face cb=add(d,'Crossbeam%d'%i,box(-35.6200577,-87,z0,71.2401154,30,30),(.46,.49,.53));cross.append(cb) p=box(-38,-51,z,76,80,8).cut(cy(19.5,8.04,0,0,z-.02)) for x in (-24.,24.):p=p.cut(cy(3.5,8.04,x,0,z-.02)) for x in (-23.5,23.5):p=p.cut(sx(x,-42,z-.02,2.75,8.04)) plates.append(add(d,'CompactPlate%d'%i,p,(.72,.74,.78))) for j,side in enumerate((-1,1)): x=side*23.5 # Overall 50 mm upright includes 3 mm foot; 33mm long slot 6 mm from inside bend. vz=face-3 if i==0 else face upright=box(x-9.5,-54,vz,19,47,3).cut(sy(x,-44.75,-18.25,vz-.02,3.25,3.04)) fz=face-31 if i==0 else face # Short leg 31, 16 mm slot position assumed centered; T slot at 15 mm. if i==0: lo,hi=face-20.25,face-10.75 else: lo,hi=face+10.75,face+20.25 foot=box(x-9.5,-57,fz,19,3,31).cut(sz(x,-57.02,lo,hi,3.25,3.04)) a=add(d,'PurchasedAngle%d%d'%(i,j),upright.fuse(foot),(.95,.5,.12));angles.append(a) if i==1: stem=cy(2.45,18,x,-42,z-4);head=cy(4.25,3,x,-42,face+4);nut=cy(4.25,3,x,-42,z-3);wz=face+3 else: stem=cy(2.45,18,x,-42,z-5);head=cy(4.25,3,x,-42,face-7);nut=cy(4.25,3,x,-42,z+8);wz=face-4 washer=cy(5.5,1,x,-42,wz).cut(cy(2.75,1.02,x,-42,wz-.01)) fast.append(add(d,'BoardM5_%d%d'%(i,j),stem.fuse(head).fuse(nut).fuse(washer),(.14,.15,.17))) cz=face+sign*15 # Shank intentionally omitted within solid extrusion; actual T slot is not modelled. fast.append(add(d,'FootHead%d%d'%(i,j),cy(5.5,1,x,-54,cz,V(0,1,0)).cut(cy(2.75,1.02,x,-54.01,cz,V(0,1,0))).fuse(cy(4.25,3,x,-53,cz,V(0,1,0))),(.14,.15,.17))) # Side connector concept: to rail inward vertical face and crossbeam top. 20 mm angle, one per end. # Top leg occupies outside edge, held clear of bearing-plate long angles. inner=side*35.6200577 if side<0: xa=inner; vx=inner; xb=inner+10 else: xa=inner-20; vx=inner-3; xb=inner-10 sidez=z0+30 if i==0 else z0-3 vz0=z0+30 if i==0 else z0-20 cz2=(vz0+10) top=box(xa,-80,sidez,20,18,3).cut(cy(2.75,3.04,xb,-72,sidez-.02)) vert=box(vx,-80,vz0,3,18,20).cut(cy(2.75,3.04,vx-.02,-70,cz2,V(1,0,0))) angles.append(add(d,'BaseJoint%d%d'%(i,j),top.fuse(vert),(.78,.38,.08))) d.recompute() # Full independent shape-pair screening for relevant structural parts. Suppressed solid T-slot # screw shanks; inherited switch shape represents only an assumed block around its central opening. new=[o for o in d.Objects if o.Name.startswith(('CompactPlate','PurchasedAngle','Crossbeam','BaseJoint','NewRail','BoardM5_','FootHead'))] contextual=[o for o in d.Objects if hasattr(o,'Shape') and not o.Shape.isNull() and o not in new and o.ViewObject.Visibility] lines=[];bad=[] for i,a in enumerate(new): for b in new[i+1:]+contextual: try: if not a.Shape.BoundBox.intersect(b.Shape.BoundBox): lines.append('%s / %s = 0.0000 mm^3 (bounding boxes disjoint)'%(a.Name,b.Name));continue v=cv(a.Shape,b.Shape) if v>0.001:bad.append((a.Name,b.Name,v)) lines.append('%s / %s = %.4f mm^3'%(a.Name,b.Name,v)) except Exception as e: lines.append('ERROR '+a.Name+'/'+b.Name+': '+str(e)) (O/'all_checks.txt').write_text('\n'.join(lines)+'\n',encoding='utf8') (O/'positive_intersections.txt').write_text('\n'.join('%s / %s = %.4f mm^3'%x for x in bad) or 'none',encoding='utf8') (O/'visible_parts.txt').write_text('\n'.join('%s %s %s'%(o.Name,o.Label,str(o.Shape.BoundBox)) for o in contextual),encoding='utf8') d.saveAs(str(O/'Assembly.FCStd'));Import.export([o for o in d.Objects if hasattr(o,'Shape')],str(O/'Assembly.step')) view=G.activeDocument().activeView();G.activateWorkbench('PartWorkbench') def save(name,mode): if mode=='side':view.viewFront() elif mode=='plate':view.viewBottom() else:view.viewAxonometric();G.updateGui();view.getCameraNode().orientation.setValue(coin.SbRotation(.880476,.115917,-.279848,-.364705)) G.updateGui();time.sleep(.3);view.fitAll();G.updateGui();time.sleep(.4);view.saveImage(str(O/name),1800,1250,'White') save('assembly.png','axon');save('side.png','side') vis={o.Name:o.ViewObject.Visibility for o in d.Objects if hasattr(o,'Shape')} show=set([o.Name for o in plates+angles+cross+fast]+['NewRail0','NewRail1','KFLHousing0','KFLHousing1','KFLInner0','KFLInner1','CamShaft']) for o in d.Objects: if hasattr(o,'Shape'):o.ViewObject.Visibility=o.Name in show save('frame.png','axon');save('plate.png','plate') for o in d.Objects: if hasattr(o,'Shape'):o.ViewObject.Visibility=vis.get(o.Name,True) d.recompute();d.saveAs(str(O/'Assembly.FCStd'));A.closeDocument(d.Name) (O/'DONE.txt').write_text('ok',encoding='utf8');ok=True except Exception: (O/'ERROR.txt').write_text(traceback.format_exc(),encoding='utf8') finally:os._exit(0 if ok else 1)