156 lines
13 KiB
Python
156 lines
13 KiB
Python
from pathlib import Path
|
|
import math, time, traceback
|
|
import FreeCAD as A, FreeCADGui as G, Part, Import
|
|
from pivy import coin
|
|
|
|
R=Path(r'C:\projects\wheel'); O=R/'models'/'kfl_plate_adjust_concept'; O.mkdir(parents=True,exist_ok=True)
|
|
V=A.Vector
|
|
def cylinder(r,h,p,d=V(0,0,1)): return Part.makeCylinder(r,h,p,d)
|
|
def obj(doc,name,label,shape,color,trans=0):
|
|
o=doc.addObject('PartDesign::Feature',name);o.Label=label;o.Shape=shape;o.ViewObject.ShapeColor=color;o.ViewObject.LineColor=(.12,.12,.12);o.ViewObject.Transparency=trans;return o
|
|
def slot_z(x,y1,y2,r,z,h):
|
|
return Part.makeBox(2*r,y2-y1,h,V(x-r,y1,z)).fuse(cylinder(r,h,V(x,y1,z))).fuse(cylinder(r,h,V(x,y2,z)))
|
|
def slot_y(x1,x2,z,r,y,h):
|
|
return Part.makeBox(x2-x1,h,2*r,V(x1,y,z-r)).fuse(cylinder(r,h,V(x1,y,z),V(0,1,0))).fuse(cylinder(r,h,V(x2,y,z),V(0,1,0)))
|
|
def slot_xz(x1,x2,y,r,z,h):
|
|
return Part.makeBox(x2-x1,2*r,h,V(x1,y-r,z)).fuse(cylinder(r,h,V(x1,y,z))).fuse(cylinder(r,h,V(x2,y,z)))
|
|
def hex_prism(radius,length,p,d): return Part.makePolygon([]) if False else Part.makeCylinder(radius,length,p,d,360)
|
|
def common(a,b):
|
|
if not a.BoundBox.intersect(b.BoundBox): return 0.0
|
|
return a.common(b).Volume
|
|
try:
|
|
doc=A.openDocument(str(R/'models'/'kfl_150_switch60_concept'/'Assembly.FCStd'))
|
|
# Prior rails are solid envelopes; retain their positions but extend forward 10mm.
|
|
for name in ('Plate0','Plate1','L_0_0','L_0_1','L_1_0','L_1_1'):
|
|
if doc.getObject(name): doc.removeObject(name)
|
|
doc.recompute()
|
|
# Give foreground rails their actual representation as outline envelopes only.
|
|
for i,x in enumerate((-50.6200577,50.6200577)):
|
|
rail=doc.getObject('NewRail%d'%i);rail.Shape=Part.makeBox(20,20,235,V(x-10,-75,-215))
|
|
rail.Label='2020 rail envelope; slots not modelled';rail.ViewObject.Transparency=25
|
|
# KMT KFL001 A0=16, A=11.5, A1=5.5: relative to the mounting
|
|
# plane, inner insert protrudes 10.5mm and housing protrudes 6mm.
|
|
# Circular protrusion OD29 is still a geometric placeholder.
|
|
rear=doc.getObject('KFLHousing0');rear.Shape=rear.Shape.cut(cylinder(20,2.02,V(0,0,-84.01))).removeSplitter();rear.Label='KFL housing 6mm nominal protrusion, OD29 assumed'
|
|
front=doc.getObject('KFLHousing1');front.Shape=front.Shape.cut(cylinder(20,2.02,V(0,0,-181.01))).removeSplitter();front.Label='KFL housing 6mm nominal protrusion, OD29 assumed'
|
|
inner=doc.getObject('KFLInner0');s=inner.Shape.copy();s.translate(V(0,0,2));inner.Shape=s;inner.Label='KFL insert 10.5mm nominal protrusion behind mounting plane'
|
|
inner=doc.getObject('KFLInner1');s=inner.Shape.copy();s.translate(V(0,0,-1));inner.Shape=s;inner.Label='KFL insert 10.5mm nominal protrusion behind mounting plane'
|
|
doc.recompute()
|
|
plates=[]; corners=[]; boardfast=[]; railfast=[]; knuts=[]
|
|
for i,z in enumerate((-84.,-187.)):
|
|
shape=Part.makeBox(140,86,8,V(-70,-51,z)).cut(cylinder(15.1,8.04,V(0,0,z-.02)))
|
|
for x in (-24.,24.):shape=shape.cut(cylinder(3.5,8.04,V(x,0,z-.02)))
|
|
for x in (-50.6200577,50.6200577): shape=shape.cut(slot_xz(x-2,x+2,-42.75,2.75,z-.02,8.04))
|
|
plates.append(obj(doc,'AdjustPlate%d'%i,'8mm plate, horizontal cross-slots, mounting aperture provisional',shape.removeSplitter(),(.72,.74,.78)))
|
|
for j,x in enumerate((-50.6200577,50.6200577)):
|
|
# Brackets on outer side of each plate, legs exactly meet each other.
|
|
sign=1 if i==0 else -1
|
|
face=z+8 if sign==1 else z
|
|
zplate=face if sign==1 else face-3
|
|
zfoot=face if sign==1 else face-20
|
|
leg=Part.makeBox(18,20,3,V(x-9,-55,zplate))
|
|
leg=leg.cut(slot_z(x,-45.75,-41.75,2.75,zplate-.02,3.04))
|
|
foot=Part.makeBox(18,3,20,V(x-9,-55,zfoot))
|
|
zb=face+10 if sign==1 else face-10
|
|
foot=foot.cut(cylinder(2.75,3.04,V(x,-55.02,zb),V(0,1,0)))
|
|
bracket=leg.fuse(foot).removeSplitter()
|
|
corners.append(obj(doc,'RealCorner%d%d'%(i,j),'2020 L angle 20x20x18: assumed 9.5x5.5 long slot and 5.5 round',bracket,(.88,.48,.14)))
|
|
# Plate M5 screw shank, head and nut: 5 mm diameter nominal.
|
|
# 1 mm thick washers and 19 mm shank envelope (not a selected screw length).
|
|
stem=cylinder(2.45,19,V(x,-42.75,z-4));
|
|
if sign==1:
|
|
head=cylinder(4.2,3,V(x,-42.75,face+4));nut=cylinder(4.3,3,V(x,-42.75,z-4));wash1=cylinder(5,1,V(x,-42.75,face+3));wash2=cylinder(5,1,V(x,-42.75,z-1))
|
|
else:
|
|
head=cylinder(4.2,3,V(x,-42.75,z-7));nut=cylinder(4.3,3,V(x,-42.75,z+9));wash1=cylinder(5,1,V(x,-42.75,z-4));wash2=cylinder(5,1,V(x,-42.75,z+8))
|
|
boardfast.append(obj(doc,'BoardFast%d%d'%(i,j),'M5 plate fastener and 10mm washer envelopes',stem.fuse(head).fuse(nut).fuse(wash1).fuse(wash2),(.13,.14,.16)))
|
|
# Foot screw goes into the rail; T-slot geometry omitted from the rail envelope.
|
|
# Ø5.5 round hole at profile, low-profile Ø9.5 x 2.75 head, no washer.
|
|
shank=cylinder(2.45,8,V(x,-60,zb),V(0,1,0));head=cylinder(4.75,2.75,V(x,-52,zb),V(0,1,0))
|
|
railfast.append(obj(doc,'RailFast%d%d'%(i,j),'M5 low-profile 9.5x2.75 head at round hole; no washer',shank.fuse(head),(.13,.14,.16)))
|
|
knuts.append(obj(doc,'Tnut%d%d'%(i,j),'2020 T nut location schematic inside rail',Part.makeBox(8,4,8,V(x-4,-61,zb-4)),(.7,.7,.72)))
|
|
# Accurate KFL heads and nuts are not dimensioned, add nominal cylindrical envelopes.
|
|
for i,z in enumerate((-84.,-187.)):
|
|
for j,x in enumerate((-24.,24.)):
|
|
if i==0: head=cylinder(5,3,V(x,0,-70.5));nut=cylinder(5,3,V(x,0,-87))
|
|
else: head=cylinder(5,3,V(x,0,-195.5));nut=cylinder(5,3,V(x,0,-179))
|
|
obj(doc,'KFLFastEnd%d%d'%(i,j),'M6 head / nut envelopes assumed',head.fuse(nut),(.15,.16,.18))
|
|
doc.recompute()
|
|
tests=[]
|
|
def check(label,a,b):
|
|
v=common(a.Shape,b.Shape);tests.append((label,v))
|
|
for i in range(2):
|
|
for n in ('KFLHousing%d'%i,'KFLInner%d'%i):check('Plate%d/%s'%(i,n),plates[i],doc.getObject(n))
|
|
for j in range(2):
|
|
k=2*i+j; check('Plate%d/corner%d%d'%(i,i,j),plates[i],corners[k]);check('boardfast%d%d/corner'%(i,j),boardfast[k],corners[k]);check('boardfast%d%d/plate'%(i,j),boardfast[k],plates[i]);check('railfast%d%d/corner'%(i,j),railfast[k],corners[k])
|
|
check('boardfast%d%d/railfast'%(i,j),boardfast[k],railfast[k]);check('boardfast%d%d/rail'%(i,j),boardfast[k],doc.getObject('NewRail%d'%j));check('railfast%d%d/plate'%(i,j),railfast[k],plates[i])
|
|
for n in ('Motor','SwitchEnvelope','MotorFlange','WheelHub','Wheel70'):
|
|
if doc.getObject(n):
|
|
check('corner%d%d/%s'%(i,j,n),corners[k],doc.getObject(n));check('boardfast%d%d/%s'%(i,j,n),boardfast[k],doc.getObject(n));check('railfast%d%d/%s'%(i,j,n),railfast[k],doc.getObject(n))
|
|
# Whole solid rail envelopes include a drawn solid area where a T-slot exists.
|
|
check('corner%d%d/ownRail'%(i,j),corners[k],doc.getObject('NewRail%d'%j))
|
|
for i in range(2):
|
|
for n in ('KFLHousing%d'%i,'KFLInner%d'%i):
|
|
for other in (('MotorFlange' if i==0 else 'WheelHub'),'CamShaft','SwitchEnvelope'):
|
|
if doc.getObject(n) and doc.getObject(other):check(n+'/'+other,doc.getObject(n),doc.getObject(other))
|
|
for x in (-24,24):
|
|
nut=doc.getObject('KFLFastEnd%d%d'%(i,0 if x<0 else 1))
|
|
for n in ('MotorFlange','WheelHub','SwitchEnvelope'):
|
|
if doc.getObject(n):check(nut.Name+'/'+n,nut,doc.getObject(n))
|
|
traveltests=[]
|
|
for i in range(4):
|
|
for dy in (-2.,0.,1.):
|
|
moved=boardfast[i].Shape.copy();moved.translate(V(0,dy,0))
|
|
vol=common(moved,railfast[i].Shape)
|
|
distance=moved.distToShape(railfast[i].Shape)[0]
|
|
traveltests.append((boardfast[i].Name,dy,vol,distance))
|
|
combined=[]
|
|
for i in range(4):
|
|
for dx in (-2.,0.,2.):
|
|
for dy in (-2.,0.,1.):
|
|
movedfast=boardfast[i].Shape.copy();movedfast.translate(V(0,dy,0))
|
|
movedplate=plates[i//2].Shape.copy();movedplate.translate(V(dx,dy,0))
|
|
overlaps=(common(movedfast,railfast[i].Shape),common(movedfast,corners[i].Shape),common(movedfast,movedplate),common(movedplate,corners[i].Shape),common(movedplate,doc.getObject('NewRail%d'%(i%2)).Shape))
|
|
combined.append((i,dx,dy,max(overlaps)))
|
|
doc.recompute()
|
|
lines=['CONCEPT ONLY: NOT FOR CNC OR PRINT.',
|
|
'Prior shaft 12x150, 60mm switch envelope, mirrored KFL and known motor assembly retained.',
|
|
'8 mm plates: 140x86 envelope; central hole OD30.2 assumed, KFL holes OD7 at +/-24 (48mm pitch).',
|
|
'KMT catalogue A0=16, A1=5.5, A=11.5: inner extends 10.5mm and housing 6mm from flange mounting plane. Housing projection OD29 assumed; not catalog-confirmed.',
|
|
'Minimum nominal clearance between centre and KFL bolt holes: 24-15.1-3.5=5.4 mm; not a strength calculation.',
|
|
'Whole plate height adjusts via 9.5x5.5 long slot in angle leg; initial bolt is 1mm off slot centre, nominal offsets -2 to +1mm. Plate to rail has 4mm nominal gap.',
|
|
'Whole plate left/right adjusts via 9.5x5.5 slots on plates, crossing each slotted angle leg, about +/-2mm subject to fit.',
|
|
'Axial positioning via sliding T-nuts in the 2020 profile. Brackets 20x20x18x3, 9.5x5.5 slot / 5.5 round as assumed stock.',
|
|
'M5 head/nut envelopes include 10mm board washers; round-hole rail fasteners use low-profile 9.5mm head, no washer; T nuts, shape and actual extrusion grooves need review.',
|
|
'Centre hole and bearing projection are assumptions: do NOT assume bearing moves in plate opening.',
|
|
'Slots geometry offers horizontal +/-2mm, vertical -2..+1mm, but assembled shaft, flange and small gaps constrain safe motion. Loosen and check continuously; do not force alignment.',
|
|
'KFL shape, fastener head/nut access, actual motor, electrical wiring, switch shape, stator etc not validated.',
|
|
'Bearing locating/floating and structural strength remain unresolved.',
|
|
'Positive-volume intersection checks in cubic mm:']+[f'{n}: {v:.6f}' for n,v in tests]
|
|
lines+=['M5 hardware clearance over candidate vertical travel (cubic mm intersection and mm minimum separation):']+[f'{n} dy={dy:+.1f} intersection={v:.6f} distance={d:.6f}' for n,dy,v,d in traveltests]
|
|
lines+=['Combined adjustment corners (plate dx/dy and M5s relative bracket; no mounted shaft or surrounding structure included):']+[f'corner={i} dx={dx:+.1f} dy={dy:+.1f} max_intersection={v:.6f}' for i,dx,dy,v in combined]
|
|
lines+=['Nominal simplified axial clearances (mm): rear housing/flange %.6f; rear inner/flange %.6f; front housing/flange %.6f; front inner/flange %.6f.'%(doc.getObject('KFLHousing0').Shape.distToShape(doc.getObject('MotorFlange').Shape)[0],doc.getObject('KFLInner0').Shape.distToShape(doc.getObject('MotorFlange').Shape)[0],doc.getObject('KFLHousing1').Shape.distToShape(doc.getObject('WheelHub').Shape)[0],doc.getObject('KFLInner1').Shape.distToShape(doc.getObject('WheelHub').Shape)[0])]
|
|
lines+=['KFL inner axial centre distance 104.5mm, switch envelope to inner end gaps rear15.5mm front14.5mm; computed from nominal adjusted model.']
|
|
(O/'geometry_check.txt').write_text('\n'.join(lines)+'\n',encoding='utf8')
|
|
# Red solids explicitly indicate any detected nonzero intersection.
|
|
bad=[(n,v) for n,v in tests if v>0.001]
|
|
(O/'checks_status.txt').write_text('INTERSECTIONS FOUND: '+str(bad)+'\n',encoding='utf8')
|
|
doc.saveAs(str(O/'Assembly.FCStd'));Import.export([o for o in doc.Objects if hasattr(o,'Shape')],str(O/'Assembly.step'))
|
|
view=G.activeDocument().activeView();G.activateWorkbench('PartWorkbench')
|
|
def save(path,mode):
|
|
if mode=='side': view.viewFront()
|
|
else: view.viewAxonometric();G.updateGui();view.getCameraNode().orientation.setValue(coin.SbRotation(.880476,.115917,-.279848,-.364705))
|
|
G.updateGui();time.sleep(.7);view.fitAll();G.updateGui();time.sleep(.7);view.saveImage(str(O/path),1800,1250,'White')
|
|
save('assembly.png','axon');save('side.png','side')
|
|
# Bearing/plates context only; hides shaft/end assemblies and green envelope, so holes visible.
|
|
visible={o.Name:o.ViewObject.Visibility for o in doc.Objects if hasattr(o,'Shape')}
|
|
show=set([o.Name for o in plates+corners+boardfast+railfast]+['KFLHousing0','KFLHousing1','KFLInner0','KFLInner1','KFLBolt0_0','KFLBolt0_1','KFLBolt1_0','KFLBolt1_1','KFLFastEnd00','KFLFastEnd01','KFLFastEnd10','KFLFastEnd11','NewRail0','NewRail1','CamShaft'])
|
|
for o in doc.Objects:
|
|
if hasattr(o,'Shape'):o.ViewObject.Visibility=o.Name in show
|
|
save('adjustment.png','axon')
|
|
for o in doc.Objects:
|
|
if hasattr(o,'Shape'):o.ViewObject.Visibility=visible.get(o.Name,True)
|
|
doc.recompute();doc.saveAs(str(O/'Assembly.FCStd'))
|
|
(O/'DONE.txt').write_text('ok',encoding='utf8')
|
|
except Exception:
|
|
(O/'ERROR.txt').write_text(traceback.format_exc(),encoding='utf8');raise
|