Files
wheel/scripts/build_mad_m9c12.py

135 lines
6.2 KiB
Python

"""Build the approximate, editable MAD M9C12 model in FreeCAD.
Run via rebuild_mad_m9c12.FCMacro in the FreeCAD GUI. The model and STEP
file are overwritten. See models/MAD_M9C12_说明.md for measurement limits.
"""
import math
from pathlib import Path
import FreeCAD as App
import Part
import Import
ROOT = Path(__file__).resolve().parent.parent
MODEL = ROOT / 'models' / 'MAD_M9C12.FCStd'
STEP = ROOT / 'models' / 'MAD_M9C12.step'
if not App.GuiUp:
raise RuntimeError('Run through the GUI macro to save visibility and camera state')
import FreeCADGui as Gui
doc = App.newDocument('MAD_M9C12')
sheet = doc.addObject('Spreadsheet::Sheet', 'Parameters')
sheet.Label = 'Parameters - mm and degrees (verify assumptions)'
sheet.set('A1', 'Parameter'); sheet.set('B1', 'Value'); sheet.set('C1', 'Source / status')
rows = [
('BodyDiameter', '120 mm', 'User measurement: body outside diameter'),
('BodyThickness', '30 mm', 'User measurement: body thickness; excludes boss and shaft'),
('FrontPCD', '20 mm', 'User confirmed adapter inner alternating holes; verify with measurements'),
('FrontHoleDiameter', '3 mm', 'Assumed M3 nominal diameter; verify'),
('FrontHoleDepth', '5 mm', 'Placeholder blind depth; verify'),
('FrontAngle', '0 deg', 'Assumed first hole phase; verify'),
('RearInnerPCD', '50 mm', 'User ruler measurement; verify with calipers'),
('RearOuterPCD', '76 mm', 'User ruler measurement; verify with calipers'),
('RearHoleDiameter', '4 mm', 'M4 nominal diameter, no modeled thread'),
('RearHoleDepth', '8 mm', 'Placeholder; M4x8 does not establish thread depth'),
('RearInnerAngle', '45 deg', 'Assumed phase; verify'),
('RearOuterAngle', '45 deg', 'Assumed relative phase; verify'),
('BossDiameter', '26 mm', 'Visual approximation; front mounting holes enter its top face'),
('BossHeight', '8 mm', 'Visual approximation, verify adapter clearance'),
('ShaftDiameter', '8 mm', 'Visual approximation'),
('ShaftHeight', '6 mm', 'Visual approximation, measured above boss'),
('CenterHoleDiameter', '3 mm', 'Axial through hole; smooth nominal M3 diameter'),
]
for i, (name, value, meaning) in enumerate(rows, 2):
sheet.set('A%d' % i, name); sheet.set('B%d' % i, value)
sheet.set('C%d' % i, meaning); sheet.setAlias('B%d' % i, name)
doc.recompute()
def cylinder(name, label, radius, height, z, x=None, y=None):
obj = doc.addObject('Part::Cylinder', name)
obj.Label = label
obj.setExpression('Radius', radius)
obj.setExpression('Height', height)
obj.setExpression('Placement.Base.z', z)
if x is not None:
obj.setExpression('Placement.Base.x', x)
obj.setExpression('Placement.Base.y', y)
return obj
def pattern(name, pcd, angle, diameter, depth, z):
holes = []
for i in range(4):
phase = 'Parameters.%s + %d deg' % (angle, i * 90)
x = 'Parameters.%s / 2 * cos(%s)' % (pcd, phase)
y = 'Parameters.%s / 2 * sin(%s)' % (pcd, phase)
holes.append(cylinder(name + str(i + 1), name + ' hole ' + str(i + 1),
'Parameters.%s / 2' % diameter,
'Parameters.%s' % depth, z, x, y))
group = doc.addObject('Part::Compound', name + 'Tools')
group.Label = name + ' (four hole tools)'
group.Links = holes
return group
def checked(obj):
doc.recompute()
if obj.Shape.isNull() or not obj.Shape.isValid() or len(obj.Shape.Solids) != 1:
raise RuntimeError('Invalid geometry: ' + obj.Name)
body = cylinder('MotorBody', 'Motor housing - approximate',
'Parameters.BodyDiameter / 2', 'Parameters.BodyThickness', '0 mm')
boss = cylinder('Boss', 'Front boss - approximate',
'Parameters.BossDiameter / 2', 'Parameters.BossHeight', 'Parameters.BodyThickness')
shaft = cylinder('Shaft', 'Shaft - approximate',
'Parameters.ShaftDiameter / 2', 'Parameters.ShaftHeight',
'Parameters.BodyThickness + Parameters.BossHeight')
housing = doc.addObject('Part::MultiFuse', 'Housing')
housing.Label = 'Housing, boss and shaft'
housing.Shapes = [body, boss, shaft]
checked(housing)
front = pattern('Front', 'FrontPCD', 'FrontAngle', 'FrontHoleDiameter', 'FrontHoleDepth',
'Parameters.BodyThickness + Parameters.BossHeight - Parameters.FrontHoleDepth')
cut_front = doc.addObject('Part::Cut', 'FrontHoles')
cut_front.Label = 'Front 4 holes - phase and depth to verify'
cut_front.Base = housing; cut_front.Tool = front
checked(cut_front)
rear_inner = pattern('RearInner', 'RearInnerPCD', 'RearInnerAngle', 'RearHoleDiameter', 'RearHoleDepth', '0 mm')
cut_inner = doc.addObject('Part::Cut', 'RearInnerHoles')
cut_inner.Label = 'Rear inner 4 holes - measured approximately'
cut_inner.Base = cut_front; cut_inner.Tool = rear_inner
checked(cut_inner)
rear_outer = pattern('RearOuter', 'RearOuterPCD', 'RearOuterAngle', 'RearHoleDiameter', 'RearHoleDepth', '0 mm')
final = doc.addObject('Part::Cut', 'Motor')
final.Label = 'MAD M9C12 - approximate motor'
final.Base = cut_inner; final.Tool = rear_outer
checked(final)
center_bore = cylinder('CenterBore', 'Center bore tool',
'Parameters.CenterHoleDiameter / 2',
'Parameters.BodyThickness + Parameters.BossHeight + Parameters.ShaftHeight',
'0 mm')
center_cut = doc.addObject('Part::Cut', 'CenterThrough')
center_cut.Label = 'MAD M9C12 - center through hole'
center_cut.Base, center_cut.Tool = final, center_bore
checked(center_cut)
final = center_cut
for obj in doc.Objects:
if obj.ViewObject:
obj.ViewObject.Visibility = False
final.ViewObject.Visibility = True
final.ViewObject.ShapeColor = (0.47, 0.50, 0.53)
final.ViewObject.Deviation = 0.05
final.ViewObject.AngularDeflection = 10
from pivy import coin
view = Gui.activeDocument().activeView()
# Set the camera directly so a hidden GUI also saves the intended view.
view.getCameraNode().orientation.setValue(coin.SbRotation(0.364705, 0.279848, 0.115917, 0.880476))
view.fitAll()
doc.recompute()
Gui.updateGui()
view.saveImage(str(ROOT / 'models' / 'MAD_M9C12_预览.png'), 1400, 1000, 'White')
doc.saveAs(str(MODEL))
Import.export([final], str(STEP))
print('MODEL_OK', MODEL, 'STEP', STEP, 'volume', final.Shape.Volume,
'bounds', final.Shape.BoundBox)