建立方向盘项目初始版本
This commit is contained in:
172
scripts/build_70.py
Normal file
172
scripts/build_70.py
Normal file
@@ -0,0 +1,172 @@
|
||||
"""Build the native FreeCAD feature model for 70转接板.step.
|
||||
|
||||
Run from the FreeCAD GUI Python console, not FreeCADCmd. Set the project
|
||||
directory first, then execute this script, for example:
|
||||
|
||||
import os
|
||||
os.chdir(r'C:\projects\wheel')
|
||||
exec(open(r'scripts\build_70.py', encoding='utf-8').read())
|
||||
|
||||
GUI execution is required to persist the visible object and camera state. The
|
||||
generated document uses only built-in Part and Spreadsheet objects and
|
||||
expressions; no script is needed to open or edit the generated model.
|
||||
"""
|
||||
import math
|
||||
import os
|
||||
import FreeCAD as App
|
||||
import Part
|
||||
import Import
|
||||
|
||||
if not App.GuiUp:
|
||||
raise RuntimeError('Run this script from the FreeCAD GUI Python console; '
|
||||
'FreeCADCmd cannot persist the required visibility state.')
|
||||
import FreeCADGui as Gui
|
||||
|
||||
root = os.getcwd()
|
||||
source = os.path.join(root, 'models', '\u0037\u0030\u8f6c\u63a5\u677f.step')
|
||||
target = os.path.join(root, 'models', '\u0037\u0030\u8f6c\u63a5\u677f_\u53c2\u6570\u5316.FCStd')
|
||||
doc = App.newDocument('Adapter70')
|
||||
sheet = doc.addObject('Spreadsheet::Sheet', 'Parameters')
|
||||
sheet.Label = 'Parameters (mm, degrees)'
|
||||
rows = [
|
||||
('Thickness', '8 mm', 'Plate thickness'),
|
||||
('CenterDiameter', '32.5 mm', 'Center through hole diameter'),
|
||||
('HoleDiameter', '4.234 mm', 'Through hole diameter (all 24 holes)'),
|
||||
('PCD42', '42 mm', 'Countersunk holes pitch circle diameter'),
|
||||
('Angle42', '45 deg', 'Countersunk holes first angle'),
|
||||
('PCD48', '48 mm', 'Six holes pitch circle diameter'),
|
||||
('Angle48', '0 deg', 'PCD48 first angle'),
|
||||
('PCD508', '50.8 mm', 'Six holes pitch circle diameter'),
|
||||
('Angle508', '30 deg', 'PCD50.8 first angle'),
|
||||
('PCD70', '70 mm', 'Outer six holes pitch circle diameter'),
|
||||
('Angle70', '30 deg', 'Outer six holes first angle'),
|
||||
('OutlineScale', '1', 'Overall outline scale relative to PCD70'),
|
||||
('CountersinkDiameter', '6.8 mm', 'Countersink at bottom face'),
|
||||
]
|
||||
sheet.set('A1', 'Parameter'); sheet.set('B1', 'Value'); sheet.set('C1', 'Meaning')
|
||||
for i, (alias, val, desc) in enumerate(rows, 2):
|
||||
sheet.set('A%d' % i, alias); sheet.set('B%d' % i, val)
|
||||
sheet.set('C%d' % i, desc); sheet.setAlias('B%d' % i, alias)
|
||||
sheet.set('A16', 'CountersinkDepth'); sheet.set('B16', '= (B14 - B4) / 2')
|
||||
sheet.set('C16', '90 degree included angle, depth derived from diameters')
|
||||
sheet.setAlias('B16', 'CountersinkDepth')
|
||||
sheet.set('A17', 'OuterChamfer'); sheet.set('B17', '1 mm')
|
||||
sheet.set('C17', 'Equal-distance chamfer on both outer edges')
|
||||
sheet.setAlias('B17', 'OuterChamfer')
|
||||
doc.recompute()
|
||||
|
||||
def setexpr(obj, prop, formula):
|
||||
obj.setExpression(prop, formula)
|
||||
|
||||
def cylinder(name, label, radius, height, pcd=None, angle=None, index=0):
|
||||
obj = doc.addObject('Part::Cylinder', name)
|
||||
obj.Label = label
|
||||
setexpr(obj, 'Radius', radius)
|
||||
setexpr(obj, 'Height', height)
|
||||
if pcd:
|
||||
phase = 'Parameters.%s + %d deg' % (angle, 60 * index)
|
||||
setexpr(obj, 'Placement.Base.x', 'Parameters.%s / 2 * cos(%s)' % (pcd, phase))
|
||||
setexpr(obj, 'Placement.Base.y', 'Parameters.%s / 2 * sin(%s)' % (pcd, phase))
|
||||
return obj
|
||||
|
||||
original = Part.read(source)
|
||||
outer_wire = max(original.slice(App.Vector(0, 0, 1), 4), key=lambda wire: wire.Length)
|
||||
original_face = Part.Face(outer_wire)
|
||||
original_face.translate(App.Vector(0, 0, -4))
|
||||
profile = doc.addObject('Part::Feature', 'OriginalOutline')
|
||||
profile.Label = 'Original outline at mid-thickness (reference geometry)'
|
||||
profile.Shape = original_face
|
||||
# A built-in link uniformly scales the original plan view in XY, without
|
||||
# scaling the extrusion height; the local curve dimensions are not editable.
|
||||
scaled = doc.addObject('App::Link', 'ScaledOutline')
|
||||
scaled.Label = 'Original outline - scaled and rotated'
|
||||
scaled.setLink(profile)
|
||||
scaled.LinkTransform = True
|
||||
setexpr(scaled, 'ScaleVector.x', 'Parameters.PCD70 / 70 mm * Parameters.OutlineScale')
|
||||
setexpr(scaled, 'ScaleVector.y', 'Parameters.PCD70 / 70 mm * Parameters.OutlineScale')
|
||||
setexpr(scaled, 'Placement.Rotation.Angle', 'Parameters.Angle70 - 30 deg')
|
||||
outline = doc.addObject('Part::Extrusion', 'Outline')
|
||||
outline.Label = 'Outline - original profile extruded'
|
||||
outline.Base = scaled
|
||||
outline.DirMode = 'Custom'
|
||||
outline.Dir = App.Vector(0, 0, 1)
|
||||
outline.LengthFwd = 8
|
||||
outline.Solid = True
|
||||
setexpr(outline, 'LengthFwd', 'Parameters.Thickness')
|
||||
doc.recompute()
|
||||
assert not outline.Shape.isNull() and outline.Shape.isValid() and len(outline.Shape.Solids) == 1
|
||||
|
||||
# Import the parametric outline into a Body as a linked base feature. Its
|
||||
# PartDesign chamfer has a single native size that accepts an expression.
|
||||
edge_ids = []
|
||||
for n, edge in enumerate(outline.Shape.Edges, 1):
|
||||
z0, z1 = edge.BoundBox.ZMin, edge.BoundBox.ZMax
|
||||
if abs(z0-z1) < 1e-7 and (abs(z0) < 1e-7 or abs(z0-8) < 1e-7):
|
||||
edge_ids.append(n)
|
||||
body = doc.addObject('PartDesign::Body', 'OutlineBody')
|
||||
body.Label = 'Outline with chamfer'
|
||||
body.BaseFeature = outline
|
||||
doc.recompute()
|
||||
chamfer = body.newObject('PartDesign::Chamfer', 'OuterChamfer')
|
||||
chamfer.Label = 'Outer chamfer (1 mm)'
|
||||
chamfer.Base = (chamfer.BaseFeature, ['Edge%d' % i for i in edge_ids])
|
||||
setexpr(chamfer, 'Size', 'Parameters.OuterChamfer')
|
||||
doc.recompute()
|
||||
assert not chamfer.Shape.isNull() and chamfer.Shape.isValid(), 'outer chamfer failed'
|
||||
|
||||
center = cylinder('CenterBore', 'Center through hole', 'Parameters.CenterDiameter / 2', 'Parameters.Thickness')
|
||||
current = doc.addObject('Part::Cut', 'CenterCut')
|
||||
current.Label = 'Center hole'
|
||||
current.Base, current.Tool = body, center
|
||||
doc.recompute()
|
||||
|
||||
for name, pcd, angle in [('42', 'PCD42', 'Angle42'), ('48', 'PCD48', 'Angle48'),
|
||||
('508', 'PCD508', 'Angle508'), ('70', 'PCD70', 'Angle70')]:
|
||||
holes = [cylinder('Hole%s_%d' % (name, i), 'PCD %s hole %d' % (name, i+1),
|
||||
'Parameters.HoleDiameter / 2', 'Parameters.Thickness', pcd, angle, i)
|
||||
for i in range(6)]
|
||||
group = doc.addObject('Part::MultiFuse', 'Bores%s' % name)
|
||||
group.Label = 'PCD %s - six bores' % name
|
||||
group.Shapes = holes
|
||||
cut = doc.addObject('Part::Cut', 'Cut%s' % name)
|
||||
cut.Label = 'PCD %s holes' % name
|
||||
cut.Base, cut.Tool = current, group
|
||||
current = cut
|
||||
doc.recompute()
|
||||
|
||||
cones = []
|
||||
for i in range(6):
|
||||
cone = doc.addObject('Part::Cone', 'Countersink%d' % i)
|
||||
cone.Label = 'Bottom countersink %d' % (i+1)
|
||||
setexpr(cone, 'Radius1', 'Parameters.CountersinkDiameter / 2')
|
||||
setexpr(cone, 'Radius2', 'Parameters.HoleDiameter / 2')
|
||||
setexpr(cone, 'Height', 'Parameters.CountersinkDepth')
|
||||
phase = 'Parameters.Angle42 + %d deg' % (i*60)
|
||||
setexpr(cone, 'Placement.Base.x', 'Parameters.PCD42 / 2 * cos(%s)' % phase)
|
||||
setexpr(cone, 'Placement.Base.y', 'Parameters.PCD42 / 2 * sin(%s)' % phase)
|
||||
cones.append(cone)
|
||||
sink_group = doc.addObject('Part::MultiFuse', 'Countersinks')
|
||||
sink_group.Label = 'PCD 42 - six bottom countersinks'
|
||||
sink_group.Shapes = cones
|
||||
final = doc.addObject('Part::Cut', 'FinishedPlate')
|
||||
final.Label = '70 adapter - finished plate'
|
||||
final.Base, final.Tool = current, sink_group
|
||||
doc.recompute()
|
||||
assert not final.Shape.isNull() and final.Shape.isValid() and len(final.Shape.Solids) == 1, 'invalid result'
|
||||
|
||||
prior = set(doc.Objects)
|
||||
Import.insert(source, doc.Name)
|
||||
reference = list(set(doc.Objects) - prior)
|
||||
assert len(reference) == 1
|
||||
reference[0].Label = 'Original STEP - reference (hidden)'
|
||||
doc.recompute()
|
||||
for obj in doc.Objects:
|
||||
if obj.ViewObject:
|
||||
obj.ViewObject.Visibility = False
|
||||
final.ViewObject.Visibility = True
|
||||
doc.recompute()
|
||||
Gui.activeDocument().activeView().viewAxonometric()
|
||||
Gui.activeDocument().activeView().fitAll()
|
||||
doc.saveAs(target)
|
||||
print('OUTPUT', target)
|
||||
print('VALID', final.Shape.isValid(), 'VOLUME', final.Shape.Volume, 'FACES', len(final.Shape.Faces))
|
||||
Reference in New Issue
Block a user