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