111 lines
5.1 KiB
Python
111 lines
5.1 KiB
Python
"""Create two genuinely connected eight-washer sprues for shop review.
|
|
|
|
Run with the FreeCAD bundled Python. The original single-part files are read
|
|
only; all lengths and placements below are in millimetres.
|
|
"""
|
|
from pathlib import Path
|
|
import hashlib
|
|
import json
|
|
import math
|
|
import shutil
|
|
import zipfile
|
|
|
|
import FreeCAD as App
|
|
import Part
|
|
import Mesh
|
|
import MeshPart
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
SOURCE = ROOT / 'models' / 'pcd50_open_pillar_v1'
|
|
OUT = ROOT / 'models' / 'print_shop_joined_eight'
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
V = App.Vector
|
|
|
|
# STEP washer geometry is centered on (10,10) in these exported files. These
|
|
# coordinates are explicit because imported STEP and STL placements differ.
|
|
def centered_shape(name):
|
|
shape = Part.read(str(SOURCE / name))
|
|
if shape.isNull() or not shape.isValid() or len(shape.Solids) != 1:
|
|
raise RuntimeError('Invalid source shape ' + name)
|
|
shape.translate(V(-10,-10,0))
|
|
return shape
|
|
|
|
def make_sprue(step_name, doc_name, stem, expected_t):
|
|
source = centered_shape(step_name)
|
|
if abs(source.BoundBox.ZLength-expected_t) > 1e-5:
|
|
raise RuntimeError('Unexpected washer thickness')
|
|
pads=[]
|
|
# Left column is rotated so both inward edges are solid. Two columns and
|
|
# four rows give eight washers with seven straight connecting bars.
|
|
for col in (0, 1):
|
|
for row in range(4):
|
|
pad=source.copy()
|
|
if col == 0:
|
|
pad.rotate(V(),V(0,0,1),180)
|
|
pad.translate(V(24*col,16*row,0))
|
|
pads.append(pad)
|
|
# Each bar is constant 1.8 x 1.8 mm. Ends overlap the outer edge by
|
|
# 0.8 mm on the centerline. The root is sacrificial and needs careful
|
|
# trimming and smoothing after printing.
|
|
rods=[]
|
|
for x in (0,24):
|
|
for row in range(3):
|
|
rods.append(Part.makeBox(1.8,5.6,1.8,V(x-.9,16*row+5.2,0)))
|
|
rods.append(Part.makeBox(13.6,1.8,1.8,V(5.2,-.9,0)))
|
|
# Quantify the overlap of each end with its two washers. The rod body is
|
|
# never necked down, but its contact height is limited by the washer.
|
|
overlaps=[]
|
|
for col in (0,1):
|
|
for row in range(3):
|
|
rod=rods[col*3+row]
|
|
overlaps.append([rod.common(pads[col*4+row]).Volume,
|
|
rod.common(pads[col*4+row+1]).Volume])
|
|
overlaps.append([rods[6].common(pads[0]).Volume,
|
|
rods[6].common(pads[4]).Volume])
|
|
if any(min(pair) < 0.5 for pair in overlaps):
|
|
raise RuntimeError('Insufficient rod/pad intersection')
|
|
combined=pads[0]
|
|
# All parts touch through each rod, but may not be sequentially connected.
|
|
# MultiFuse avoids relying on order of the graph during the operation.
|
|
combined = pads[0].multiFuse(pads[1:]+rods).removeSplitter()
|
|
if combined.isNull() or not combined.isValid() or len(combined.Solids)!=1:
|
|
raise RuntimeError('Sprue is not one valid connected solid: '+stem)
|
|
# Confirm every washer's complete original geometry is retained.
|
|
for pad in pads:
|
|
if pad.cut(combined).Volume > 1e-5:
|
|
raise RuntimeError('Source washer material was removed')
|
|
bounds=combined.BoundBox
|
|
doc=App.newDocument(doc_name)
|
|
o=doc.addObject('Part::Feature','ConnectedSprue');o.Label=stem;o.Shape=combined
|
|
doc.recompute()
|
|
doc.saveAs(str(OUT/(stem+'.FCStd')))
|
|
Part.export([o],str(OUT/(stem+'.step')))
|
|
mesh=MeshPart.meshFromShape(Shape=combined,LinearDeflection=0.03,
|
|
AngularDeflection=0.2,Relative=False)
|
|
mesh.write(str(OUT/(stem+'.stl')))
|
|
App.closeDocument(doc.Name)
|
|
check=Mesh.Mesh(str(OUT/(stem+'.stl')))
|
|
components=check.getSeparateComponents()
|
|
if len(components)!=1 or not components[0].isSolid():
|
|
raise RuntimeError('STL is not one closed connected mesh')
|
|
return {'file':stem+'.stl','washer_quantity':8,'nominal_washer_thickness_mm':expected_t,
|
|
'rod_count':len(rods),'rod_width_mm':1.8,'rod_height_mm':1.8,
|
|
'rod_centerline_radial_overlap_mm':0.8,
|
|
'rod_end_intersection_volumes_mm3': [[round(v,5) for v in pair] for pair in overlaps],
|
|
'closed_components':len(components),'closed':True,
|
|
'bounds_mm':[bounds.XMin,bounds.XMax,bounds.YMin,bounds.YMax,bounds.ZMin,bounds.ZMax],
|
|
'nominal_washer_volume_mm3':round(source.Volume,5)}
|
|
|
|
result=[]
|
|
result.append(make_sprue('Shim05.step','WasherSprue05','垫片0.5mm_8个拼件',.5))
|
|
result.append(make_sprue('Shim10.step','WasherSprue10','垫片1.0mm_8个拼件',1.0))
|
|
source=SOURCE/'EncoderBracket.stl'; target=OUT/'编码器支架_单件.stl'
|
|
shutil.copyfile(source,target)
|
|
digest=hashlib.sha256(source.read_bytes()).hexdigest()
|
|
if hashlib.sha256(target.read_bytes()).hexdigest()!=digest: raise RuntimeError('Bracket copy mismatch')
|
|
m=Mesh.Mesh(str(target))
|
|
if len(m.getSeparateComponents())!=1 or not m.isSolid(): raise RuntimeError('Invalid bracket mesh')
|
|
result.insert(0,{'file':target.name,'quantity':1,'closed_components':1,'sha256':digest})
|
|
(OUT/'几何检查.json').write_text(json.dumps(result,ensure_ascii=False,indent=2)+'\n',encoding='utf-8')
|
|
print(json.dumps(result,ensure_ascii=False,indent=2))
|