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'/'switch_2020_raise10_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 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,label): o=d.addObject('PartDesign::Feature',n);o.Shape=s.removeSplitter();o.Label=label;o.ViewObject.ShapeColor=c;o.ViewObject.LineColor=(.15,.15,.15);return o ok=False try: d=A.openDocument(str(R/'models'/'kfl_3030_motor_corner_concept'/'Assembly.FCStd')) d.Label='Switch crossbeam 2020 - common shaft raised by 10 mm' # Every visible component moves together except the fixed 3030 frame, its joints and the long bearing-plate brackets. fixed_prefix=('NewRail','Crossbeam','PurchasedAngle','BaseJoint','FootHead') moved=[] for o in d.Objects: if hasattr(o,'Shape') and not o.Shape.isNull() and o.ViewObject.Visibility and not o.Name.startswith(fixed_prefix): p=o.Placement;p.Base.y+=10.0;o.Placement=p;moved.append(o.Name) d.recompute() (O/'moved_names.txt').write_text('\n'.join(moved)+'\n',encoding='utf8') sw=d.getObject('SwitchEnvelope');sw.ViewObject.Transparency=78 beam=add(d,'SwitchCross2020',box(-80,-55,-142,160,20,20),(.32,.36,.39),'Added 2020 switch crossbeam; solid envelope') new=[beam];ang=[];con=[];ears=[];heads=[] for i,(x,y,tag) in enumerate(((-42.,-25.,'A'),(42.,-12.,'B'))): ax=x+(-.75 if x<0 else .75) # angle offset; M4 ear bolt clearance in 6.5mm slot # 50 mm upright attaches switch; 31 mm foot sits on the top of 2020. upright=box(ax-9.5,-32,-145,19,47,3).cut(sy(ax,-25.75,.75,-145.02,3.25,3.04)) foot=box(ax-9.5,-35,-142,19,3,31).cut(sz(ax,-35.02,-131.25,-121.75,3.25,3.04)) a=add(d,'SwitchAngle'+tag,upright.fuse(foot),(.98,.52,.05),'Long 50x31 angle '+tag+'; top mounted');ang.append(a);new.append(a) # Separate assumed schematic bolt head and ear on the switch side of the angle. ear=cy(7,3,x,y,-148).cut(cy(2.2,3.04,x,y,-148.02)) e=add(d,'AssumedEar'+tag,ear,(.21,.67,.24),'ASSUMED ear '+tag+'; illustrative, no actual shell measured');ears.append(e) h=cy(3.5,2.2,x,y,-150.2) hd=add(d,'SwitchHead'+tag,h,(.12,.12,.12),'ASSUMED head '+tag+' Ø7');heads.append(hd);new.append(hd) # Fastener head at the side groove of the 2020 (no shank entering solid extrusion). railhead=add(d,'SwitchRailHead'+tag,cy(4.25,3,ax,-32,-132,V(0,1,0)),(.12,.12,.12),'Top groove head '+tag+'; bolt and groove not modeled');new.append(railhead) for i,x in enumerate((-50.6200577,50.6200577)): # 2020 to 3030 separate L angles on the opposite, plate-facing side. upright=box(x-9,-55,-122,18,20,3).cut(cy(2.75,3.04,x,-45,-122.02)) foot=box(x-9,-55,-119,18,3,20).cut(cy(2.75,3.04,x,-55.02,-109,V(0,1,0))) q=add(d,'SwitchCrossJoint%d'%i,upright.fuse(foot),(.91,.23,.18),'2020 to 3030 corner '+str(i+1));con.append(q);new.append(q) # Shim bridges the old 2020 end and raised motor corner foot, one 5.5mm fastening hole. shim=box(x-10,-55,0,20,10,40).cut(cy(2.75,10.04,x,-55.02,30,V(0,1,0))) q=add(d,'MotorShim%d'%i,shim,(.45,.58,.75),'10mm shim supports motor 2020 and connector foot '+str(i+1));new.append(q) d.recompute() contextual=[o for o in d.Objects if hasattr(o,'Shape') and not o.Shape.isNull() and o not in new and o not in ears 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):continue v=a.Shape.common(b.Shape).Volume if v>0.001:bad.append((a.Name,b.Name,v)) lines.append('%s / %s : %.6f mm3'%(a.Name,b.Name,v)) except Exception as ex:lines.append('ERROR %s/%s %s'%(a.Name,b.Name,ex)) (O/'interference_report.txt').write_text('\n'.join(lines)+'\n',encoding='utf8') (O/'positive_intersections.txt').write_text('\n'.join('%s / %s : %.6f mm3'%v for v in bad) or 'none',encoding='utf8') d.saveAs(str(O/'Assembly.FCStd'));Import.export([o for o in d.Objects if hasattr(o,'Shape') and not o.Shape.isNull() and o.ViewObject.Visibility],str(O/'Assembly.step')) view=G.activeDocument().activeView();G.activateWorkbench('PartWorkbench') def snap(name,mode): if mode=='front':view.viewBottom() elif mode=='side':view.viewFront() 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') snap('assembly.png','axon') # Show surrounding structure and schematic switch, focus the full assembly so the beam is visible. snap('end.png','front');snap('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 new+ears]+['NewRail0','NewRail1','Crossbeam0','Crossbeam1','CompactPlate0','CompactPlate1','KFLHousing0','KFLHousing1','CamShaft','SwitchEnvelope']) for o in d.Objects: if hasattr(o,'Shape'):o.ViewObject.Visibility=o.Name in show snap('focus.png','axon') simple=set(['SwitchCross2020','SwitchAngleA','SwitchAngleB','SwitchHeadA','SwitchHeadB','SwitchRailHeadA','SwitchRailHeadB','SwitchCrossJoint0','SwitchCrossJoint1','SwitchEnvelope','AssumedEarA','AssumedEarB']) for o in d.Objects: if hasattr(o,'Shape'):o.ViewObject.Visibility=o.Name in simple view.viewTop();G.updateGui();time.sleep(.3);view.fitAll();G.updateGui();time.sleep(.3);view.saveImage(str(O/'switch_front.png'),1800,1250,'White') snap('switch_detail.png','axon') 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)