Files
wheel/scripts/build_kfl_plate_b38_concept.py

119 lines
7.2 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_b38_concept'; O.mkdir(parents=True,exist_ok=True)
V=A.Vector
def cyl(r,h,x,y,z):return Part.makeCylinder(r,h,V(x,y,z))
def add(doc,name,label,s,color,trans=0):
o=doc.addObject('PartDesign::Feature',name);o.Label=label;o.Shape=s;o.ViewObject.ShapeColor=color;o.ViewObject.LineColor=(.1,.1,.1);o.ViewObject.Transparency=trans;return o
def intersection(a,b):
return a.common(b).Volume if a.BoundBox.intersect(b.BoundBox) else 0.0
try:
doc=A.openDocument(str(R/'models'/'kfl_plate_adjust_concept'/'Assembly.FCStd'))
doc.Label='KFL b38 - 39 mm bore conceptual assembly'
for i,z in enumerate((-84.,-187.)):
p=doc.getObject('AdjustPlate%d'%i)
p.Shape=p.Shape.cut(cyl(19.5,8.04,0,0,z-.02)).removeSplitter()
p.Label='8 mm plate, central through-bore Ø39 assumption'
# Enlarge only the portion assumed to penetrate the plate; leave mounting ears intact.
# KMT catalog axial section: static housing protrudes 6 mm from ear seating plane.
# A circular Ø38 bounding section is a conservative stand-in for the unmeasured
# actual penetrating profile, not a manufacturer's individual diameter callout.
for i,z in enumerate((-82.,-187.)):
h=doc.getObject('KFLHousing%d'%i)
outer=cyl(19,6,0,0,z).cut(cyl(9.5,6.02,0,0,z-.01))
h.Shape=h.Shape.fuse(outer).removeSplitter()
h.Label='KFL seat Ø38 circular envelope assumed; 6mm inside plate'
# Replace previous simplified M6 heads/nuts with individually selectable shank,
# head, nut, and two washers. OD12 ID6.6 washers are nominal placeholders.
for i,z in enumerate((-84.,-187.)):
for j,x in enumerate((-24.,24.)):
name='KFLFastEnd%d%d'%(i,j)
if doc.getObject(name):doc.removeObject(name)
old='KFLBolt%d_%d'%(i,j)
if doc.getObject(old):doc.removeObject(old)
if i==0:
# flange ear z=-76..-70.5; plate z=-84..-76
wplatez=-85.; wearz=-70.5; nutz=-88.; headz=-69.5
else:
# ear z=-192.5..-187; plate z=-187..-179
wplatez=-179.; wearz=-193.5; nutz=-178.; headz=-196.5
# Each washer is hollow to avoid its shaft being a spurious positive overlap.
ring=lambda zz: cyl(6,1,x,0,zz).cut(cyl(3.3,1.02,x,0,zz-.01))
add(doc,'BearingWasherPlate%d%d'%(i,j),'M6 washer Ø12 on plate, schematic',ring(wplatez),(.42,.44,.47))
add(doc,'BearingWasherEar%d%d'%(i,j),'M6 washer Ø12 on bearing ear, schematic',ring(wearz),(.42,.44,.47))
add(doc,'BearingHead%d%d'%(i,j),'M6 head envelope Ø10',cyl(5,3,x,0,headz),(.13,.14,.16))
add(doc,'BearingNut%d%d'%(i,j),'M6 nut envelope Ø10',cyl(5,3,x,0,nutz),(.13,.14,.16))
add(doc,'KFLBolt%d_%d'%(i,j),'M6 bolt shank engagement schematic Ø5.8',cyl(2.9,18.5,x,0,nutz if i==0 else wearz),(.13,.14,.16))
doc.recompute()
tests=[]
def chk(label,sa,sb):tests.append((label,intersection(sa,sb)))
for i in range(2):
p=doc.getObject('AdjustPlate%d'%i).Shape
for n in ('KFLHousing%d'%i,'KFLInner%d'%i):chk('plate%d/%s'%(i,n),p,doc.getObject(n).Shape)
for j in range(2):
for k in ('BearingWasherPlate','BearingWasherEar','BearingHead','BearingNut'):
v=doc.getObject('%s%d%d'%(k,i,j)).Shape
for other in ('MotorFlange','WheelHub','SwitchEnvelope'):
o=doc.getObject(other)
if o:chk('%s%d%d/%s'%(k,i,j,other),v,o.Shape)
for n in ('KFLHousing%d'%i,'KFLInner%d'%i):
other=doc.getObject('MotorFlange' if i==0 else 'WheelHub')
chk(n+'/hub',doc.getObject(n).Shape,other.Shape)
# Contact support: intersect planar annulus with plate surface only; washer radius 6, hole radius 3.3.
# The single-bore-to-plate-hole line edges are 24 - 19.5 - 3.5 = 1 mm.
# Use a thin prism intersection for total plate contact footprint (normal separation < modelling epsilon).
contact=[]
for i,z in enumerate((-84.,-187.)):
p=doc.getObject('AdjustPlate%d'%i).Shape
platevolume=0
for j,x in enumerate((-24.,24.)):
v=(cyl(6,.1,x,0,z if i==0 else z+7.9).cut(cyl(3.3,.12,x,0,z-.01 if i==0 else z+7.89))).common(p).Volume/.1
contact.append((i,j,v))
# Two-bore nearest metal/nominal washer footprint purely geometric; not strength.
b=doc.getObject('KFLHousing0').Shape.BoundBox
lines=['CONCEPT ONLY; DO NOT USE FOR CNC OR SAFE-FIT APPROVAL.',
'Plate central bore nominal OD39, 8mm thick. 48mm KFL mounting pitch, nominal OD7 holes.',
'KFL protrusion given b=38 as conservative circular OD38 bounding assumption, not confirmed round nor actual profile; 0.5mm radial nominal bore clearance.',
'KFL ear thickness5.5, stationary central protrusion depth6, inner ring width14.5 and projects 10.5 past mating plane (KMT interpretation).',
'Centre-hole to each fastening-hole nominal edge gap = 24 - 19.5 - 3.5 = 1.0mm.',
'M6 plate-face washer assumed OD12, ID6.6, thickness1: towards centre hole it extends 1.5mm over nominal opening.',
'Plate-face washer nominal contact area mm^2 (face-intersection):']+[f'plate{i} washer{j}: {a:.6f}' for i,j,a in contact]+[
'These are static contact footprints, not load/fastener/tear-out calculations. Bearing ear footprint and flush contact require real KFL shape.',
'Axial clearance must not be inferred from standard b width. Encoder screw, wires, set-screw access and rotational clearances not fully modelled.',
'M6 heads/nuts/washer geometry is illustrative and no tool access is verified; M5 standard angles and crossing slots inherit prior notes.',
'Diameter 39 is proposed initial 1mm diametral allowance and is not a fit/tolerance guarantee. No FEA, validation or testing.',
'Checked positive-volume intersections (mm^3):']+[f'{n}: {v:.6f}' for n,v in tests]
(O/'geometry_check.txt').write_text('\n'.join(lines)+'\n',encoding='utf-8')
doc.recompute();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()
elif mode=='top':view.viewBottom()
else:view.viewAxonometric()
G.updateGui();time.sleep(.6);view.fitAll();G.updateGui();time.sleep(.6)
camera=view.getCameraNode();camera.nearDistance.setValue(.01);camera.farDistance.setValue(10000)
G.updateGui();view.saveImage(str(O/name),1800,1300,'White')
save('assembly.png','axon');save('side.png','side')
vis={o.Name:o.ViewObject.Visibility for o in doc.Objects if hasattr(o,'Shape')}
show={'AdjustPlate0','KFLHousing0','KFLInner0','KFLBolt0_0','KFLBolt0_1','BearingWasherPlate00','BearingWasherPlate01','BearingWasherEar00','BearingWasherEar01','BearingHead00','BearingHead01','BearingNut00','BearingNut01'}
for o in doc.Objects:
if hasattr(o,'Shape'):o.ViewObject.Visibility=o.Name in show
save('bearing_detail.png','axon')
# Plate + plate-face washers, as seen straight along axis; making relationship visible.
show={'AdjustPlate0','BearingWasherPlate00','BearingWasherPlate01'}
for o in doc.Objects:
if hasattr(o,'Shape'):o.ViewObject.Visibility=o.Name in show
save('plate_detail.png','top')
for o in doc.Objects:
if hasattr(o,'Shape'):o.ViewObject.Visibility=vis.get(o.Name,True)
doc.recompute();doc.saveAs(str(O/'Assembly.FCStd'))
if (O/'ERROR.txt').exists(): (O/'ERROR.txt').unlink()
(O/'DONE.txt').write_text('ok',encoding='utf8')
except Exception:
(O/'ERROR.txt').write_text(traceback.format_exc(),encoding='utf8')
raise