Files
wheel/scripts/build_conversion_disk_m4.FCMacro

83 lines
5.2 KiB
Plaintext

"""Create a separate copy of the conversion disk with six true M4 threads.
Run with FreeCAD GUI (headless/hidden window is fine). Original files untouched.
"""
from pathlib import Path
import traceback, math, time
import FreeCAD as App, FreeCADGui as Gui, Part, Import
root=Path(r'C:\projects\wheel')
out=root/'models'/'conversion_disk_m4'
out.mkdir(parents=True, exist_ok=True)
logs=[]
doc=None
try:
source=root/'scripts'/'build_conversion_disk.py'
src=source.read_text(encoding='utf-8')
src=src.replace("target = ROOT / 'models' / 'conversion_disk_parametric.FCStd'", "target = ROOT / 'models' / 'conversion_disk_m4' / '转换盘_M4.FCStd'")
src=src.replace("('OuterHole','4.234 mm','Outer 6-hole diameter'),", "('OuterHole','3.3 mm','Hole sketch circle for the 6x M4 thread centers; native Hole determines final geometry'),")
old=" holes_cut=body.newObject('PartDesign::Pocket',diam+'Through');holes_cut.Profile=holes;holes_cut.Length=8;holes_cut.setExpression('Length','Parameters.Thickness');verify(holes_cut)"
new=""" if count==6:
holes_cut=body.newObject('PartDesign::Hole','OuterM4Thread');holes_cut.Label='PCD48: 6 x M4 x 0.7, 6H through (modeled)';holes_cut.Profile=holes
holes_cut.Threaded=True;holes_cut.ThreadType='ISOMetricProfile';doc.recompute()
holes_cut.ThreadSize='M4x0.7';holes_cut.ThreadClass='6H';holes_cut.DepthType='ThroughAll';holes_cut.ModelThread=True;doc.recompute();verify(holes_cut)
else:
holes_cut=body.newObject('PartDesign::Pocket',diam+'Through');holes_cut.Profile=holes;holes_cut.Length=8;holes_cut.setExpression('Length','Parameters.Thickness');verify(holes_cut)"""
assert old in src and "conversion_disk_m4' / '转换盘_M4.FCStd'" in src
src=src.replace(old,new)
context={'__name__':'__main__','__file__':str(source)}
exec(compile(src,str(source),'exec'),context)
doc=context['doc'];body=doc.getObject('Body');thread=doc.getObject('OuterM4Thread');final=doc.getObject('InnerHoleThrough')
doc.recompute()
assert thread.Threaded and thread.ModelThread and thread.ThreadSize=='M4x0.7' and thread.ThreadClass=='6H'
assert thread.Shape.isValid() and final.Shape.isValid() and len(final.Shape.Solids)==1
logs.append('Native Hole: threaded=%s modeled=%s size=%s class=%s depth_type=%s valid=%s faces=%d' % (thread.Threaded,thread.ModelThread,thread.ThreadSize,thread.ThreadClass,thread.DepthType,thread.Shape.isValid(),len(thread.Shape.Faces)))
logs.append('Final: valid=%s solids=%d volume=%.8f faces=%d' % (final.Shape.isValid(),len(final.Shape.Solids),final.Shape.Volume,len(final.Shape.Faces)))
original=App.openDocument(str(root/'models'/'conversion_disk_parametric.FCStd'))
reference=original.getObject('InnerHoleThrough').Shape
masks=[]
for i in range(6):
a=math.radians(30+60*i);masks.append(Part.makeCylinder(3.2,8.2,App.Vector(24*math.cos(a),24*math.sin(a),-.1)))
mask=masks[0].multiFuse(masks[1:]); mask=mask.removeSplitter()
def outside_delta(a,b):
delta=a.cut(b)
if delta.isNull():return 0.0
try: remaining=delta.cut(mask)
except ValueError as exc:
if str(exc)=='Null shape':return 0.0
raise
return 0.0 if remaining.isNull() else remaining.Volume
delta1=outside_delta(reference,final.Shape)
delta2=outside_delta(final.Shape,reference)
logs.append('Outside six PCD48 masks, old minus new volume=%.9f; new minus old=%.9f mm3' % (delta1,delta2))
assert delta1<1e-5 and delta2<1e-5
App.closeDocument(original.Name)
App.setActiveDocument(doc.Name)
for obj in doc.Objects:
if obj.ViewObject:obj.ViewObject.Visibility=False
body.ViewObject.Visibility=True;final.ViewObject.Visibility=True
Gui.activeDocument().activeView().viewAxonometric();Gui.activeDocument().activeView().fitAll();Gui.updateGui()
Gui.activeDocument().activeView().saveImage(str(out/'转换盘_M4预览.png'),1500,1100,'White')
doc.recompute();doc.saveAs(str(out/'转换盘_M4.FCStd'))
final_volume=final.Shape.Volume
Import.export([final],str(out/'转换盘_M4.step'))
name=doc.Name;App.closeDocument(name)
reopened=App.openDocument(str(out/'转换盘_M4.FCStd'));reopened.recompute()
rt=reopened.getObject('OuterM4Thread');rf=reopened.getObject('InnerHoleThrough')
assert rt.ModelThread and rt.Threaded and rt.ThreadSize=='M4x0.7' and rt.ThreadClass=='6H' and rf.Shape.isValid()
logs.append('Reopened FCStd: modeled=%s size=%s class=%s final_valid=%s' % (rt.ModelThread,rt.ThreadSize,rt.ThreadClass,rf.Shape.isValid()))
App.closeDocument(reopened.Name)
stepdoc=App.newDocument('STEP_validation');Import.insert(str(out/'转换盘_M4.step'),stepdoc.Name);stepdoc.recompute()
shapes=[o.Shape for o in stepdoc.Objects if hasattr(o,'Shape') and not o.Shape.isNull()]
assert len(shapes)>0 and all(s.isValid() for s in shapes)
logs.append('STEP objects=%d total_volume=%.8f total_faces=%d valid=True' % (len(shapes),sum(s.Volume for s in shapes),sum(len(s.Faces) for s in shapes)))
assert abs(sum(s.Volume for s in shapes)-final_volume)<0.001
App.closeDocument(stepdoc.Name)
(out/'validation.txt').write_text('\n'.join(logs)+'\n',encoding='utf-8')
(out/'DONE.txt').write_text('ok',encoding='utf-8')
except Exception:
(out/'ERROR.txt').write_text(traceback.format_exc()+'\n'+'\n'.join(logs),encoding='utf-8')
finally:
for d in list(App.listDocuments().values()):
App.closeDocument(d.Name)
Gui.getMainWindow().close()