Files
wheel/scripts/build_kfl_flange57_shift.py

129 lines
9.3 KiB
Python

from pathlib import Path
import math, time, traceback, os
import FreeCAD as A, FreeCADGui as G, Part, Import
from pivy import coin
V=A.Vector
R=Path(r'C:\projects\wheel'); variant=os.environ.get('KFL_HUB_VARIANT')=='narrow';O=R/'models'/'kfl_100_flange57_shift_concept';O.mkdir(parents=True,exist_ok=True)
def cy(r,h,x=0,y=0,z=0):return Part.makeCylinder(r,h,V(x,y,z))
def add(d,n,label,s,c,t=0):
o=d.addObject('PartDesign::Feature',n);o.Label=label;o.Shape=s;o.ViewObject.ShapeColor=c;o.ViewObject.LineColor=(.08,.08,.08);o.ViewObject.Transparency=t;return o
def booleanvol(a,b):
return a.common(b).Volume if a.BoundBox.intersect(b.BoundBox) else 0
try:
src=A.openDocument(str(R/'models'/'pcd50_open_pillar_v1'/'Assembly.FCStd'))
existing={o.Name:(o.Shape.copy(),tuple(o.ViewObject.ShapeColor),o.ViewObject.Transparency) for o in src.Objects if hasattr(o,'Shape') and not o.Shape.isNull()}
A.closeDocument(src.Name)
src=A.openDocument(str(R/'models'/'turn_signal_cam_v2'/'Assembly.FCStd'))
cam={n:src.getObject(n).Shape.copy() for n in ['Shaft','Key','Upper','Lower','Hardware0','Hardware1','Hardware2','Hardware3']}
A.closeDocument(src.Name)
for n,s in cam.items():s.translate(V(0,0,-108 if n=='Shaft' else -112))
cam['Shaft']=cy(6,100,0,0,-158).cut(Part.makeBox(4.1,3,14,V(-2.05,4,-119)))
doc=A.newDocument('KFL100_Schematic'); objs={}
for n,(s,c,t) in existing.items():
objs[n]=add(doc,n,'Prior existing '+n,s,c,55 if n=='Motor' else t)
for i,x in enumerate([-50.6200577,50.6200577]):
objs['NewRail%d'%i]=add(doc,'NewRail%d'%i,'Proposed 2020 longitudinal rail schematic',Part.makeBox(20,20,190,V(x-10,-75,-170)),(.48,.51,.55))
# Two vertical plates parallel to XY. The 30.2 mm bore is placeholder,
# with dummy bearing protrusion 29 mm OD; NOT a production bore.
plates=[]
for i,z in enumerate([-84,-150]):
s=Part.makeBox(140,90,8,V(-70,-55,z)).cut(cy(15.1,8.02,0,0,z-.01))
for x in (-24,24):s=s.cut(cy(3.5,8.02,x,0,z-.01))
o=add(doc,'Plate%d'%i,'8mm plate: hole 30.2 assumed',s,(.66,.69,.74));plates.append(o)
# Simplified illustrative corner brackets, contacting plate and rail only.
for j,x in enumerate([-50.6200577,50.6200577]):
a=Part.makeBox(16,12,3,V(x-8,-55,z-3))
b=Part.makeBox(16,3,11,V(x-8,-55,z-11))
o=add(doc,'L_%d_%d'%(i,j),'90deg bracket placeholder, fasteners omitted',a.fuse(b).removeSplitter(),(.88,.49,.16))
# Approximate KFL outside dimensions: 63 x 38 mm, pitch 48, flange
# ears 5.5, insert 14.5; protrusion 29mm OD and position are placeholders.
bearings=[]
for i,(plate_z,inner_z,sign) in enumerate([(-84,-88.5,1),(-150,-153,-1)]):
# face of plate towards outside axle area, mirrored.
if sign==1: face=plate_z+8; flangez=face; insertz=face-8
else: face=plate_z; flangez=face-5.5; insertz=face
ring=cy(19,5.5,0,0,flangez).cut(cy(9.5,5.52,0,0,flangez-.01))
for x in (-24,24):
ear=cy(7.5,5.5,x,0,flangez).fuse(Part.makeBox(12,15,5.5,V(min(x,0),-7.5,flangez)))
ring=ring.fuse(ear)
for x in (-24,24):ring=ring.cut(cy(3.5,5.52,x,0,flangez-.01))
ring=ring.cut(cy(9.5,5.52,0,0,flangez-.01))
ring=ring.fuse(cy(14.5,8,0,0,insertz).cut(cy(9.5,8.02,0,0,insertz-.01))).removeSplitter()
outer=add(doc,'KFLHousing%d'%i,'KFL001 housing: 29 protrusion ASSUMED',ring,(.45,.49,.53));bearings.append(outer)
inner=cy(9.2,14.5,0,0,inner_z).cut(cy(6.1,14.52,0,0,inner_z-.01))
bearings.append(add(doc,'KFLInner%d'%i,'KFL inner 14.5mm rotating schematic',inner,(.77,.77,.79)))
# M6 screws only illustrative straight 5.8mm shank; heads omitted.
for j,x in enumerate((-24,24)):
z0=plate_z-1 if sign==1 else flangez-1
o=add(doc,'KFLBolt%d_%d'%(i,j),'M6 shank schematic, heads omitted',cy(2.9,15.5,x,0,z0),(.18,.19,.20));objs[o.Name]=o
# Real shaft/cam from existing project.
colors={'Shaft':(.65,.66,.68),'Key':(.98,.65,.1),'Upper':(.05,.40,.80),'Lower':(.16,.62,.88)}
for n,s in cam.items():objs['Cam'+n]=add(doc,'Cam'+n,'Existing cam V2 '+n,s,colors.get(n,(.2,.2,.21)))
# Actual small conversion disk STEP: mirrored to front face at z=-38.
conv=Part.read(str(R/'models'/'转换盘1.step'));conv.rotate(V(),V(1,0,0),180);conv.translate(V(0,0,-38))
objs['Conversion']=add(doc,'Conversion','Existing conversion disk STEP; face position approx',conv,(.82,.83,.85))
# Six-hole keyed flange is a placeholder, the hub overlaps 12mm of shaft.
flange=cy(28.5,4,0,0,-50).cut(cy(6.2,4.02,0,0,-50.01))
for j in range(6):
a=2*math.pi*j/6+math.pi/6;flange=flange.cut(cy(2.75,4.02,24*math.cos(a),24*math.sin(a),-50.01))
hubradius=10
hub=cy(15,20,0,0,-70).cut(cy(6.2,20.02,0,0,-70.01));flange=flange.fuse(hub).removeSplitter()
objs['MotorFlange']=add(doc,'MotorFlange','MOTOR actual nominal OD57 flange 4mm, OD30 hub protrusion 20mm',flange,(.9,.66,.20))
# Wheel hub is deliberately modelled with only 9mm shaft engagement.
wh=cy(hubradius,9,0,0,-162).cut(cy(6.2,9.02,0,0,-162.01))
whplate=cy(27,3,0,0,-165).cut(cy(6.2,3.02,0,0,-165.01))
for j in range(6):
a=2*math.pi*j/6;whplate=whplate.cut(cy(2.2,3.02,24*math.cos(a),24*math.sin(a),-165.01))
wh=wh.fuse(whplate).removeSplitter()
objs['WheelHub']=add(doc,'WheelHub','Wheel flange schematic: only 9mm engagement unverified',wh,(.9,.66,.20))
wheel70=Part.read(str(R/'models'/'70转接板.step'));wheel70.rotate(V(),V(1,0,0),180);wheel70.translate(V(0,0,-165))
objs['Wheel70']=add(doc,'Wheel70','Existing 70 adapter, nominal assumed position, bolt access unchecked',wheel70,(.82,.83,.85))
# Switch envelope is deliberately only transparent rectangular with generic
# centre bore; 50mm depth includes off-axis connector. Actual contours unknown.
sw=Part.makeBox(110,80,50,V(-55,-40,-138.5)).cut(cy(23.5,50.02,0,0,-138.51))
objs['SwitchEnvelope']=add(doc,'SwitchEnvelope','50mm switch bounding placeholder; 47mm bore ASSUMED',sw,(.45,.73,.43),80)
doc.recompute()
# Restrict checks to shapes actually modelled. Intentional shaft-to-hub clearance.
checks=[]
pairs=[('Plate%d'%i,'KFLHousing%d'%i,plates[i].Shape,bearings[2*i].Shape) for i in range(2)]
pairs += [('Plate%d'%i,'KFLInner%d'%i,plates[i].Shape,bearings[2*i+1].Shape) for i in range(2)]
pairs += [('SwitchEnvelope','Cam'+n,sw,cam[n]) for n in ('Upper','Lower','Hardware0','Hardware1','Hardware2','Hardware3')]
pairs += [('MotorFlange','Shaft',flange,cam['Shaft']),('WheelHub','Shaft',wh,cam['Shaft']),('WheelHub','70Adapter',wh,wheel70),('Plate0','Plate1',plates[0].Shape,plates[1].Shape)]
pairs += [('MotorFlange','RearBearing',flange,bearings[0].Shape),('MotorFlange','RearInner',flange,bearings[1].Shape),('MotorFlange','RearPlate',flange,plates[0].Shape),('WheelHub','FrontBearing',wh,bearings[2].Shape)]
for a,b,sa,sb in pairs:
checks.append('%s / %s = %.6f mm3'%(a,b,booleanvol(sa,sb)))
for n,s in [('RearConflict',flange.common(bearings[0].Shape)),('FrontConflict',wh.common(bearings[2].Shape))]:
if not s.isNull() and s.Volume>.001: add(doc,n,'RED: nominal collision '+n,s,(1.0,.03,.03))
lines=['CONCEPT ONLY; NOT ASSEMBLY FIT VALIDATION.',
'Motor side flange disc OD57 thickness4, six PCD48 holes nominal5.5; hub OD30 extending20 past disc face. Wheel side hub OD20 assumed.',
'Z decreases from motor toward steering wheel; motor rear z0; wheel shaft z=-58..-158 length100.',
'KFL actual nominal 63x38 housing envelope, 48 bolt centers, 7 hole, 5.5 ears, inner 14.5; protrusion OD29 and position assumed.',
'8mm plates central hole OD30.2 assumed, not actual production spec.',
'50mm switch opaque-enclosing envelope z=-138.5..-88.5; opening OD47 ASSUMED only; real rotary features and contacts unknown.',
'Comparison: shaft z=-58..-158, motor hub z=-70..-50: nominal overlap12mm. Rear bearing and switch and wheel hub shifted toward wheel 10mm. Wheel hub z=-162..-153: actual overlap5mm with shaft, is not adequate without verified engineering data.',
'Sleeve20 sits inside switch envelope and is not additional 20mm.',
'Motor mounting boss, shaft end keys, original M3 nut, lights/white contacts, bearing set screws, pin fastening/head/tool and adjuster are not modelled.',
'No workable fit is demonstrated. Real KFL and switch shapes, motor M3 nut and both shaft end details, fastener/tool paths, front hub and contact/clearances are unmeasured.',
'Checked positive-volume intersections (other pairs, loaded operation, exact bearing and switch shape not tested):']+checks
(O/'geometry_check.txt').write_text('\n'.join(lines)+'\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(name,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/name),1800,1250,'White')
save('assembly.png','axon');save('side.png','side')
# Simplified focus hides rear encoder/rails so bearing through plate is visible.
for n,o in objs.items():
if n in existing or n.startswith('NewRail') or n=='Conversion':o.ViewObject.Visibility=False
for o in doc.Objects:
if o.Name.startswith('L_'):o.ViewObject.Visibility=False
save('focus.png','axon')
for o in doc.Objects:
if hasattr(o,'Shape'):o.ViewObject.Visibility=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