107 lines
7.1 KiB
Python
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)
|