90 lines
4.3 KiB
Python
90 lines
4.3 KiB
Python
"""Build mixed-thickness 4x4 sprue; run with FreeCAD bundled Python."""
|
|
from pathlib import Path
|
|
import json, math, zipfile, shutil, hashlib
|
|
import FreeCAD as App
|
|
import Part, Mesh, MeshPart
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
SRC = ROOT/'models'/'pcd50_open_pillar_v1'
|
|
OUT = ROOT/'models'/'washer_grid_4x4'
|
|
OUT.mkdir(parents=True, exist_ok=True)
|
|
V=App.Vector
|
|
|
|
def load(name, thickness):
|
|
s=Part.read(str(SRC/name))
|
|
s.translate(V(-10,-10,0))
|
|
if s.isNull() or not s.isValid() or len(s.Solids)!=1 or abs(s.BoundBox.ZLength-thickness)>1e-6:
|
|
raise RuntimeError('Invalid source '+name)
|
|
# The original 90-degree opening is centered 22.5 degrees above +X.
|
|
# Rotate to the NE quadrant so bar attachments on the SW side miss it.
|
|
s.rotate(V(),V(0,0,1),22.5)
|
|
return s
|
|
|
|
source05=load('Shim05.step',.5)
|
|
source10=load('Shim10.step',1.0)
|
|
# Bars are 2x2 across their width/height. Offset attachment centerlines by
|
|
# 1.5mm away from the open quadrant. At that line, the nominal free length
|
|
# between the circular outer edges is 2.0mm; rod ends overlap by 0.8mm each.
|
|
offset=1.5
|
|
edge=math.sqrt(6.0**2-offset**2)
|
|
pitch=2*edge+2.0
|
|
rod_start=edge-.8
|
|
rod_length=pitch-2*rod_start
|
|
pads=[]
|
|
for row in range(4):
|
|
for col in range(4):
|
|
p=(source05 if row<2 else source10).copy()
|
|
p.translate(V(col*pitch,row*pitch,0))
|
|
pads.append(p)
|
|
rods=[]
|
|
endpoints=[]
|
|
for row in range(4):
|
|
for col in range(3):
|
|
rods.append(Part.makeBox(rod_length,2,2,V(col*pitch+rod_start,row*pitch-offset-1,0)))
|
|
endpoints.append((row*4+col,row*4+col+1,'H'))
|
|
for row in range(3):
|
|
for col in range(4):
|
|
rods.append(Part.makeBox(2,rod_length,2,V(col*pitch-offset-1,row*pitch+rod_start,0)))
|
|
endpoints.append((row*4+col,(row+1)*4+col,'V'))
|
|
if len(rods)!=24 or abs(rod_length-3.6)>1e-7: raise RuntimeError('Incorrect rods')
|
|
contacts=[]
|
|
for k,(a,b,d) in enumerate(endpoints):
|
|
va=rods[k].common(pads[a]).Volume; vb=rods[k].common(pads[b]).Volume
|
|
if min(va,vb)<0.2: raise RuntimeError('Weak or absent rod overlap %d: %.4f %.4f'%(k,va,vb))
|
|
contacts.append([round(va,5),round(vb,5)])
|
|
# Do not allow any bars into the 4.4mm bore. Bars are on the SW sides, so
|
|
# they also avoid the NE open quadrant; test that region on each pad.
|
|
for i,p in enumerate(pads):
|
|
x=(i%4)*pitch; y=(i//4)*pitch
|
|
bore=Part.makeCylinder(2.2,2,V(x,y,0))
|
|
opening=Part.makeBox(6.1,6.1,2,V(x,y,0))
|
|
for rod in rods:
|
|
if rod.common(bore).Volume>1e-6: raise RuntimeError('Rod blocks bore %d'%i)
|
|
if rod.common(opening).Volume>1e-6: raise RuntimeError('Rod blocks open quadrant %d'%i)
|
|
shape=pads[0].multiFuse(pads[1:]+rods).removeSplitter()
|
|
if shape.isNull() or not shape.isValid() or len(shape.Solids)!=1: raise RuntimeError('Not one valid solid')
|
|
if any(p.cut(shape).Volume>1e-5 for p in pads): raise RuntimeError('Washer removed')
|
|
doc=App.newDocument('Washers4x4')
|
|
obj=doc.addObject('Part::Feature','Grid');obj.Label='4x4 washers - 2mm links';obj.Shape=shape
|
|
doc.recompute()
|
|
doc.saveAs(str(OUT/'4x4_混合垫片.FCStd'))
|
|
Part.export([obj],str(OUT/'4x4_混合垫片.step'))
|
|
mesh=MeshPart.meshFromShape(Shape=shape,LinearDeflection=.03,AngularDeflection=.2,Relative=False)
|
|
mesh.write(str(OUT/'4x4_混合垫片.stl'))
|
|
App.closeDocument(doc.Name)
|
|
check=Mesh.Mesh(str(OUT/'4x4_混合垫片.stl'))
|
|
parts=check.getSeparateComponents()
|
|
if len(parts)!=1 or not parts[0].isSolid(): raise RuntimeError('STL is not one closed component')
|
|
shutil.copyfile(SRC/'EncoderBracket.stl',OUT/'编码器支架_单件.stl')
|
|
if hashlib.sha256((SRC/'EncoderBracket.stl').read_bytes()).digest()!=hashlib.sha256((OUT/'编码器支架_单件.stl').read_bytes()).digest():
|
|
raise RuntimeError('Bracket copy changed')
|
|
b=shape.BoundBox
|
|
data={'count':16,'washers_05':8,'washers_10':8,'rod_count':24,
|
|
'rod_width_mm':2.0,'rod_height_mm':2.0,'rod_total_length_mm':round(rod_length,6),
|
|
'rod_centerline_free_span_mm':2.0,'rod_nominal_end_overlap_mm':.8,
|
|
'rod_contact_volumes_mm3':contacts,'hole_opening_check':'passed',
|
|
'was_original_washer_geometry_retained':True,'closed_components':len(parts),
|
|
'bounds_mm':[b.XMin,b.XMax,b.YMin,b.YMax,b.ZMin,b.ZMax]}
|
|
(OUT/'几何检查.json').write_text(json.dumps(data,ensure_ascii=False,indent=2)+'\n',encoding='utf8')
|
|
print(json.dumps(data,ensure_ascii=False,indent=2))
|