119 lines
7.2 KiB
Python
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
|