Files
wheel/scripts/build_washer_panel_4x4.py

68 lines
4.5 KiB
Python

"""Make one solid two-thickness panel containing 16 cut-out C washers.
Run using FreeCAD's bundled Python. Cutting guide is documentation only;
the resulting STL deliberately contains no artificial connecting bars.
"""
from pathlib import Path
import json, math
import FreeCAD as App
import Part, Mesh, MeshPart
ROOT = Path(r'C:\projects\wheel')
OUT = ROOT / 'models' / 'washer_panel_4x4'
OUT.mkdir(parents=True, exist_ok=True)
V = App.Vector
doc = App.newDocument('WasherPanel4x4')
sheet = doc.addObject('Spreadsheet::Sheet', 'Parameters')
sheet.set('A1', 'Parameter'); sheet.set('B1', 'Value'); sheet.set('C1', 'Notes')
for row, name, value, desc in [(2, 'Thin', '0.5 mm', 'Thickness rows 1-2'), (3, 'Thick', '1 mm', 'Thickness rows 3-4')]:
sheet.set('A%d'%row,name); sheet.set('B%d'%row,value); sheet.set('C%d'%row,desc); sheet.setAlias('B%d'%row,name)
blanks=[]
for name, y, p in [('ThinHalf',0,'Parameters.Thin'),('ThickHalf',24,'Parameters.Thick')]:
b=doc.addObject('Part::Box',name); b.Length=48; b.Width=24; b.Placement.Base=V(0,y,0); b.setExpression('Height',p); blanks.append(b)
blank=doc.addObject('Part::MultiFuse','PanelBlank'); blank.Shapes=blanks
cutters=[]
for row in range(4):
for col in range(4):
x,y=12*col+6,12*row+6
circle=doc.addObject('Part::Cylinder',f'Hole{row}{col}'); circle.Radius=2.2; circle.Height=3; circle.Placement.Base=V(x,y,-1); cutters.append(circle)
# Original 90-degree sector after rotating its 22.5-degree centre to 45 degrees.
# NE quadrant within this cell. The cutter is bounded by the cell's
# top and right edges so the cut exposes the notch after separation.
opening=doc.addObject('Part::Box',f'Opening{row}{col}')
opening.Length=6; opening.Width=6; opening.Height=3; opening.Placement.Base=V(x,y,-1); cutters.append(opening)
allcuts=doc.addObject('Part::MultiFuse','HoleAndNotchCutters'); allcuts.Shapes=cutters
panel=doc.addObject('Part::Cut','WasherPanel'); panel.Base=blank; panel.Tool=allcuts
panel.Label='One-piece 4 x 4 washer panel - cut to separate'
doc.recompute()
s=panel.Shape
assert not s.isNull() and s.isValid() and len(s.Solids)==1, (s.isValid(),len(s.Solids))
baseline=s.Volume
for row in range(4):
for col in range(4):
t=.5 if row<2 else 1
cell=s.common(Part.makeBox(12,12,2,V(12*col,12*row,0)))
assert cell.isValid() and len(cell.Solids)==1 and abs(cell.BoundBox.ZLength-t)<1e-6
# At the centre and within the quadrant, the bore and open sector are clear.
for xx,yy in [(0,0),(1,0),(.3,.3),(3,3),(5.9,5.9)]:
assert not cell.isInside(V(12*col+6+xx,12*row+6+yy,t/2),1e-7,False),(row,col,xx,yy)
# Outside the original round bore and notch, the square retained material.
assert cell.isInside(V(12*col+2,12*row+2,t/2),1e-7,False)
for o in doc.Objects:
if hasattr(o,'ViewObject') and o.ViewObject: o.ViewObject.Visibility=False
if panel.ViewObject: panel.ViewObject.Visibility=True
doc.recompute()
fc=OUT/'一体式4x4双厚垫片.FCStd'; step=OUT/'一体式4x4双厚垫片.step'; stl=OUT/'一体式4x4双厚垫片.stl'
doc.saveAs(str(fc)); Part.export([panel],str(step))
MeshPart.meshFromShape(Shape=s,LinearDeflection=.025,AngularDeflection=.15,Relative=False).write(str(stl))
sheet.set('B2','0.6 mm'); doc.recompute(); test=panel.Shape.Volume
assert panel.Shape.isValid() and test>baseline
sheet.set('B2','0.5 mm');doc.recompute();assert abs(panel.Shape.Volume-baseline)<1e-6
App.closeDocument(doc.Name)
dd=App.openDocument(str(fc));dd.recompute();f=dd.getObject('WasherPanel').Shape; ss=Part.read(str(step)); mesh=Mesh.Mesh(str(stl)); components=mesh.getSeparateComponents()
assert f.isValid() and ss.isValid() and len(f.Solids)==len(ss.Solids)==1 and len(components)==1 and components[0].isSolid()
assert f.cut(ss).Volume<1e-5 and ss.cut(f).Volume<1e-5
data={'sheet_size_mm':[48,48], 'cell_mm':[12,12], 'diameter_mm':4.4,'opening_sector_degrees':90,'counts':[8,8], 'thicknesses_mm':[.5,1], 'number_of_solids':len(ss.Solids), 'stl_components':len(components), 'stl_is_solid':components[0].isSolid(), 'part_is_valid':ss.isValid(),'fcstd_reopen':True, 'step_minus_fcstd_volume':ss.cut(f).Volume,'fcstd_minus_step_volume':f.cut(ss).Volume,'original_opening_direction':'northeast, rotate after separation to match each pillar', 'cut_boundaries_mm':[12,24,36], 'cut_line_is_physical_feature':False,'thickness_parameter_recompute_test':True,'nominal_kerf_mm':0,'nominal_volume_mm3':baseline}
(OUT/'几何验证.json').write_text(json.dumps(data,ensure_ascii=False,indent=2),encoding='utf-8')
print(json.dumps(data,ensure_ascii=False,indent=2))