135 lines
6.2 KiB
Python
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)
|