Files
wheel/scripts/build_kfl_double_crossbar_concept.py

107 lines
7.1 KiB
Python

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_double_crossbar_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 cyl(r,h,x,y,z,d=V(0,0,1)): return Part.makeCylinder(r,h,V(x,y,z),d)
def slotx(x,y,z,r,h): return box(x-2,y-r,z,4,2*r,h).fuse(cyl(r,h,x-2,y,z)).fuse(cyl(r,h,x+2,y,z))
def sloty(x,y,z,r,h): return box(x-r,y-2,z,2*r,4,h).fuse(cyl(r,h,x,y-2,z)).fuse(cyl(r,h,x,y+2,z))
def add(d,n,s,col):
o=d.addObject('PartDesign::Feature',n);o.Shape=s.removeSplitter();o.Label=n;o.ViewObject.ShapeColor=col;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_plate_b38_concept'/'Assembly.FCStd'));d.Label='Two crossbeams, KFL001 reversed - concept'
for i in range(2):
for j in range(2):
for prefix in ('RealCorner','BoardFast','RailFast','Tnut'):
n='%s%d%d'%(prefix,i,j)
if d.getObject(n):d.removeObject(n)
if d.getObject('AdjustPlate%d'%i):d.removeObject('AdjustPlate%d'%i)
rail=d.getObject('NewRail%d'%i)
x=(-50.6200577,50.6200577)[i]
rail.Shape=box(x-10,-75,-215,20,20,235)
rail.Label='Longitudinal 2020, schematic solid'
d.recompute()
plates=[]; angles=[]; bolts=[]; cross=[]
for i,z in enumerate((-84.,-187.)):
p=box(-38,-51,z,76,80,8).cut(cyl(19.5,8.04,0,0,z-.02))
for x in (-24.,24.):p=p.cut(cyl(3.5,8.04,x,0,z-.02))
for x in (-27.5,27.5):p=p.cut(slotx(x,-42,z-.02,2.75,8.04))
plates.append(add(d,'CompactPlate%d'%i,p,(.72,.74,.78)))
face=z+8 if i==0 else z
z0=face if i==0 else face-20
cz=z0+10
cross.append(add(d,'Crossbeam%d'%i,box(-80,-95,z0,160,20,20),(.46,.49,.53)))
for j,side in enumerate((-1,1)):
x=side*27.5
verticalz=face if i==0 else face-3
upright=box(x-9,-75,verticalz,18,45,3).cut(sloty(x,-42,verticalz-.02,2.75,3.04))
foot=box(x-9,-75,z0,18,3,20).cut(cyl(2.75,3.04,x,-75.02,cz,V(0,1,0)))
angles.append(add(d,'LongAngle%d%d'%(i,j),upright.fuse(foot),(.95,.5,.12)))
if i==0:
stem=cyl(2.45,18,x,-42,z-4);head=cyl(4.25,3,x,-42,face+4);nut=cyl(4.25,3,x,-42,z-3);wz=face+3
else:
stem=cyl(2.45,18,x,-42,z-5);head=cyl(4.25,3,x,-42,face-7);nut=cyl(4.25,3,x,-42,z+8);wz=face-4
washer=cyl(5,1,x,-42,wz).cut(cyl(2.75,1.02,x,-42,wz-.01))
bolts.append(add(d,'BoardM5_%d%d'%(i,j),stem.fuse(head).fuse(nut).fuse(washer),(.14,.15,.17)))
stem=cyl(2.45,10,x,-82,cz,V(0,1,0));head=cyl(4.25,3,x,-72,cz,V(0,1,0))
bolts.append(add(d,'FootM5_%d%d'%(i,j),stem.fuse(head),(.14,.15,.17)))
x0=-80 if side==-1 else 60.6200577
vertx=-63.6200577 if side==-1 else 60.6200577
hx=-70 if side==-1 else 70
horiz=box(x0,-75,cz-9,19.3799423,3,18).cut(cyl(2.75,3.04,hx,-75.02,cz,V(0,1,0)))
vert=box(vertx,-75,cz-9,3,20,18).cut(cyl(2.75,3.04,vertx-.02,-65,cz,V(1,0,0)))
angles.append(add(d,'CrossAngle%d%d'%(i,j),horiz.fuse(vert),(.86,.48,.16)))
bolts.append(add(d,'CrossTopM5_%d%d'%(i,j),cyl(2.45,10,hx,-82,cz,V(0,1,0)).fuse(cyl(4.25,3,hx,-72,cz,V(0,1,0))),(.14,.15,.17)))
if side==-1: sh=cyl(2.45,10,-68,-65,cz,V(1,0,0)).fuse(cyl(4.25,3,-63.6200577,-65,cz,V(-1,0,0)))
else: sh=cyl(2.45,10,58,-65,cz,V(1,0,0)).fuse(cyl(4.25,3,63.6200577,-65,cz,V(1,0,0)))
bolts.append(add(d,'CrossSideM5_%d%d'%(i,j),sh,(.14,.15,.17)))
d.recompute()
tests=[]
def check(a,b):
ao=d.getObject(a);bo=d.getObject(b)
if ao and bo:tests.append((a,b,cv(ao.Shape,bo.Shape)))
for i in range(2):
for n in ('KFLHousing%d'%i,'KFLInner%d'%i,'Motor','MotorFlange','WheelHub','Wheel70','SwitchEnvelope','NewRail0','NewRail1','Crossbeam%d'%i):check('CompactPlate%d'%i,n)
for n in ('NewRail0','NewRail1','SwitchEnvelope','Motor','CamShaft','CamUpper','CamLower'):check('Crossbeam%d'%i,n)
for j in range(2):
long='LongAngle%d%d'%(i,j);crossa='CrossAngle%d%d'%(i,j)
for n in ('CompactPlate%d'%i,'KFLHousing%d'%i,'KFLInner%d'%i,'Motor','MotorFlange','WheelHub','Wheel70','SwitchEnvelope','Crossbeam%d'%i,'NewRail0','NewRail1','BoardM5_%d%d'%(i,j),'FootM5_%d%d'%(i,j),crossa):check(long,n)
for n in ('Crossbeam%d'%i,'NewRail%d'%j,'SwitchEnvelope','Motor','LongAngle%d%d'%(i,j),'CrossTopM5_%d%d'%(i,j),'CrossSideM5_%d%d'%(i,j)):check(crossa,n)
check('BoardM5_%d%d'%(i,j),'CompactPlate%d'%i)
check('FootM5_%d%d'%(i,j),'CrossTopM5_%d%d'%(i,j))
lines=['CONCEPT ONLY: schematic profile envelopes and simplified hardware; not CNC/manufacturing drawings.',
'Two plate sizes 76x80x8; 39 bore assumed, KFL bolt centres 48 and clearance holes 7. Shaft diameter 12 x 150; switch 60 mm envelope.',
'Crossbars x -80..80, y -95..-75, z -76..-56 / -207..-187, each beneath longitudinal profiles y -75..-55.',
'Four assumed bent metal angles: outer legs 45 x 20, width 18, thickness 3, actual bend radius unspecified; foot round hole diameter 5.5, vertical 9.5 x 5.5 slot Y; plate slot 9.5 x 5.5 X at y=-42.',
'Proposed nominal M5 adjustment about +/-2mm each axis; requires testing of all actual washer, shaft, bearing and interference conditions. Each plate washer OD10 at slot lower travel remains within plate edge by nominal 2mm.',
'T slots shown as SOLID 2020 ENVELOPES: fastener shanks intentionally enter solid beam; do not treat as interference free, actual T nut and bolt depth must be checked.',
'Four standard corner angles join crossbars to long profiles, two per crossbar. Screw access, T nuts, actual slot geometry, bending radii, switch casing and strength unverified.',
'Selected static common volume tests mm3; zero volume checks apply ONLY to modeled shapes and those specific pairs:']+[f'{a}/{b}={v:.5f}' for a,b,v in tests]
(O/'geometry_check.txt').write_text('\n'.join(lines)+'\n',encoding='utf8')
(O/'positive_intersections.txt').write_text('\n'.join(f'{a}/{b}: {v:.5f}' for a,b,v in tests if v>.001) or 'none',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=='front':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')
visible={o.Name:o.ViewObject.Visibility for o in d.Objects if hasattr(o,'Shape')}
show=set([o.Name for o in plates+angles+bolts+cross]+['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','front')
for o in d.Objects:
if hasattr(o,'Shape'):o.ViewObject.Visibility=visible.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)