Files
wheel/scripts/build_kfl_3030_concept.py

100 lines
6.3 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_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)