建立方向盘项目初始版本
This commit is contained in:
111
scripts/build_kfl001_plate_v2.py
Normal file
111
scripts/build_kfl001_plate_v2.py
Normal file
@@ -0,0 +1,111 @@
|
||||
"""Editable KFL001 bearing mounting plate for prototype.
|
||||
|
||||
Run with rebuild_kfl001_plate_v1.FCMacro in a hidden FreeCAD instance.
|
||||
Both assembly plates have the same geometry; flip the second when fitting.
|
||||
The centre cut provides 0.5 mm radial clearance for a 38 mm boss.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import os, time, traceback, json
|
||||
import FreeCAD as A, FreeCADGui as G, Part, Import
|
||||
|
||||
R=Path(r'C:\projects\wheel')
|
||||
O=R/'models'/'kfl001_plate_v2'
|
||||
O.mkdir(parents=True, exist_ok=True)
|
||||
try:
|
||||
d=A.newDocument('KFL001_Plate_V2')
|
||||
d.Label='KFL001 8mm bearing mounting plate V2'
|
||||
s=d.addObject('Spreadsheet::Sheet','Parameters')
|
||||
s.Label='Parameters - dimensions in mm'
|
||||
rows=[('PlateWidth','76 mm','Overall width'),('PlateHeight','75 mm','Overall height'),
|
||||
('Thickness','8 mm','Plate thickness'),('AxisFromBottom','51 mm','Axle height above lower edge'),
|
||||
('CenterClearance','39 mm','39 mm clearance for user confirmed 38 mm boss'),
|
||||
('BearingPitch','48 mm','Bolt centre spacing for KFL001'),
|
||||
('BearingHole','7 mm','Bearing mount clearance diameter'),
|
||||
('SlotPitch','47 mm','Centre spacing of lower mounting slots'),
|
||||
('SlotBottom','9 mm','Mounting slot centre above plate lower edge'),
|
||||
('SlotLength','9.5 mm','Horizontal slot overall length'),
|
||||
('SlotWidth','5.5 mm','Horizontal slot width'),
|
||||
('EdgeChamfer','1.5 mm','Both outer perimeter edges only'),
|
||||
('ArchRadius','60 mm','Top arc radius')]
|
||||
for row,(name,val,note) in enumerate(rows,2):
|
||||
s.set('A%d'%row,name);s.set('B%d'%row,val);s.set('C%d'%row,note);s.setAlias('B%d'%row,name)
|
||||
def expr(o,p,v):o.setExpression(p,v)
|
||||
plate=d.addObject('Part::Box','PlateBlank');plate.Label='76 x 75 x 8 plate blank'
|
||||
expr(plate,'Length','Parameters.PlateWidth');expr(plate,'Width','Parameters.PlateHeight');expr(plate,'Height','Parameters.Thickness')
|
||||
arc=d.addObject('Part::Cylinder','ArchLimit');arc.Label='Upper R60 arc'
|
||||
expr(arc,'Radius','Parameters.ArchRadius');expr(arc,'Height','Parameters.Thickness')
|
||||
expr(arc,'Placement.Base.x','Parameters.PlateWidth / 2');expr(arc,'Placement.Base.y','Parameters.PlateHeight - Parameters.ArchRadius')
|
||||
d.recompute()
|
||||
shape=d.addObject('Part::Common','ArchedOutline');shape.Base=plate;shape.Tool=arc;d.recompute()
|
||||
assert shape.Shape.isValid() and len(shape.Shape.Solids)==1
|
||||
body=d.addObject('PartDesign::Body','ChamferBody');body.Label='Perimeter chamfer'
|
||||
body.BaseFeature=shape;d.recompute()
|
||||
outer=[]
|
||||
for i,e in enumerate(shape.Shape.Edges,1):
|
||||
b=e.BoundBox
|
||||
if abs(b.ZMax-b.ZMin)<1e-7 and (abs(b.ZMin)<1e-7 or abs(b.ZMin-8)<1e-7):outer.append('Edge%d'%i)
|
||||
chamfer=body.newObject('PartDesign::Chamfer','PerimeterChamfer')
|
||||
chamfer.Base=(chamfer.BaseFeature,outer);expr(chamfer,'Size','Parameters.EdgeChamfer')
|
||||
d.recompute()
|
||||
assert not chamfer.Shape.isNull() and chamfer.Shape.isValid(),chamfer.State
|
||||
shifted=d.addObject('App::Link','ShiftedChamferBlank');shifted.setLink(chamfer)
|
||||
shifted.LinkTransform=True
|
||||
expr(shifted,'Placement.Base.x','-Parameters.PlateWidth / 2')
|
||||
d.recompute()
|
||||
def cyl(name,x,y,r):
|
||||
o=d.addObject('Part::Cylinder',name);expr(o,'Radius',r);expr(o,'Height','Parameters.Thickness + 0.2 mm');expr(o,'Placement.Base.x',x);expr(o,'Placement.Base.y',y);o.Placement.Base.z=-.1;return o
|
||||
tools=[]
|
||||
tools.append(cyl('CentreHole','0 mm','Parameters.AxisFromBottom','Parameters.CenterClearance / 2'))
|
||||
for side in (-1,1):
|
||||
sign='-' if side<0 else ''
|
||||
tools.append(cyl('BearingHole_'+('L' if side<0 else 'R'),sign+'Parameters.BearingPitch / 2','Parameters.AxisFromBottom','Parameters.BearingHole / 2'))
|
||||
cx=sign+'Parameters.SlotPitch / 2';tag='L' if side<0 else 'R'
|
||||
tools.append(cyl('Slot'+tag+'End1','(%s) - (Parameters.SlotLength - Parameters.SlotWidth) / 2'%cx,'Parameters.SlotBottom','Parameters.SlotWidth / 2'))
|
||||
tools.append(cyl('Slot'+tag+'End2','(%s) + (Parameters.SlotLength - Parameters.SlotWidth) / 2'%cx,'Parameters.SlotBottom','Parameters.SlotWidth / 2'))
|
||||
b=d.addObject('Part::Box','Slot'+tag+'Middle')
|
||||
expr(b,'Length','Parameters.SlotLength - Parameters.SlotWidth');expr(b,'Width','Parameters.SlotWidth');expr(b,'Height','Parameters.Thickness + 0.2 mm')
|
||||
expr(b,'Placement.Base.x','(%s) - (Parameters.SlotLength - Parameters.SlotWidth) / 2'%cx)
|
||||
expr(b,'Placement.Base.y','Parameters.SlotBottom - Parameters.SlotWidth / 2');b.Placement.Base.z=-.1
|
||||
tools.append(b)
|
||||
d.recompute()
|
||||
cutters=d.addObject('Part::MultiFuse','Cutters');cutters.Shapes=tools;d.recompute()
|
||||
final=d.addObject('Part::Cut','Plate_V2');final.Label='KFL001 bearing mounting plate V2';final.Base=shifted;final.Tool=cutters
|
||||
d.recompute()
|
||||
assert not final.Shape.isNull() and final.Shape.isValid() and len(final.Shape.Solids)==1
|
||||
for o in d.Objects:
|
||||
if o.ViewObject:o.ViewObject.Visibility=False
|
||||
final.ViewObject.Visibility=True;final.ViewObject.ShapeColor=(.71,.74,.77);final.ViewObject.LineColor=(.12,.12,.12)
|
||||
final.ViewObject.Deviation=0.05
|
||||
final.ViewObject.AngularDeflection=10
|
||||
G.activateWorkbench('PartWorkbench');view=G.activeDocument().activeView();view.viewAxonometric();G.updateGui();view.fitAll();time.sleep(.3);G.updateGui();view.saveImage(str(O/'plate_isometric.png'),1400,1100,'White')
|
||||
view.viewTop();G.updateGui();view.fitAll();time.sleep(.3);G.updateGui();view.saveImage(str(O/'plate_top.png'),1400,1100,'White')
|
||||
d.saveAs(str(O/'KFL001_plate_V2.FCStd'));Import.export([final],str(O/'KFL001_plate_V2.step'))
|
||||
baseline=final.Shape.Volume
|
||||
s.set('B6','38 mm');d.recompute()
|
||||
assert final.Shape.isValid() and abs(final.Shape.Volume-baseline)>0.1
|
||||
changed=final.Shape.Volume
|
||||
s.set('B6','39 mm');d.recompute()
|
||||
assert final.Shape.isValid() and abs(final.Shape.Volume-baseline)<1e-5
|
||||
A.closeDocument(d.Name)
|
||||
dd=A.openDocument(str(O/'KFL001_plate_V2.FCStd'));dd.recompute();ff=dd.getObject('Plate_V2')
|
||||
assert ff.Shape.isValid() and len(ff.Shape.Solids)==1 and abs(ff.Shape.Volume-baseline)<1e-5
|
||||
imported=Part.read(str(O/'KFL001_plate_V2.step'))
|
||||
assert imported.isValid() and len(imported.Solids)==1
|
||||
# At the middle Z plane compare the STEP vs native shape by mutual differences.
|
||||
stepdiff1=ff.Shape.cut(imported).Volume;stepdiff2=imported.cut(ff.Shape).Volume
|
||||
assert stepdiff1<1e-4 and stepdiff2<1e-4
|
||||
angles=[(3.5,24,51),(3.5,-24,51),(19.5,0,51)]
|
||||
cylinders=[]
|
||||
for f in imported.Faces:
|
||||
surf=f.Surface
|
||||
if hasattr(surf,'Radius') and hasattr(surf,'Center'):
|
||||
cylinders.append({'radius':round(float(surf.Radius),5),'x':round(float(surf.Center.x),5),'y':round(float(surf.Center.y),5),'z_min':round(f.BoundBox.ZMin,5),'z_max':round(f.BoundBox.ZMax,5)})
|
||||
for r,x,y in angles:
|
||||
assert any(abs(c['radius']-r)<1e-4 and abs(c['x']-x)<1e-4 and abs(c['y']-y)<1e-4 and c['z_min']<=.001 and c['z_max']>=7.999 for c in cylinders), (r,x,y,cylinders)
|
||||
info={'fcstd_valid':True,'step_valid':True,'solid_count':len(imported.Solids),'volume':baseline,'diameter_parameter_test_38':changed,'diameter_test_restored':True,'step_old_minus_new':stepdiff1,'step_new_minus_old':stepdiff2,'dimensions_mm':{'width':76,'height':75,'thickness':8,'axis_from_bottom':51,'center_diameter':39,'bearing_pitch':48,'bearing_hole':7,'slot_pitch':47,'slot_bottom':9,'slot_length':9.5,'slot_width':5.5,'perimeter_chamfer':1.5,'upper_arc_radius':60,'upper_arc_centre_from_bottom':15},'cylinder_faces':cylinders,'nominal_m5_slot_range_mm':4.5,'quantity_identical_plates':2,'boss_diameter_user_confirmed_mm':38}
|
||||
(O/'validation.json').write_text(json.dumps(info,ensure_ascii=False,indent=2),encoding='utf-8')
|
||||
A.closeDocument(dd.Name)
|
||||
(O/'DONE.txt').write_text('ok',encoding='utf-8')
|
||||
os._exit(0)
|
||||
except Exception:
|
||||
(O/'ERROR.txt').write_text(traceback.format_exc(),encoding='utf-8');os._exit(1)
|
||||
Reference in New Issue
Block a user