"""Build a separate Ø3.3 drill-hole quoting test copy, not a threaded model.""" from pathlib import Path import FreeCAD as App, FreeCADGui as Gui, Part, Import import math, traceback root=Path(r'C:\projects\wheel') out=root/'models'/'conversion_disk_m4_drill' out.mkdir(parents=True,exist_ok=True) logs=[] 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_drill' / '转换盘_M4底孔.FCStd'") src=src.replace("('OuterHole','4.234 mm','Outer 6-hole diameter'),", "('OuterHole','3.3 mm','Outer 6-hole M4 tap-drill, no modeled thread'),") assert "conversion_disk_m4_drill' / '转换盘_M4底孔.FCStd'" in src and "('OuterHole','3.3 mm'" in src ctx={'__name__':'__main__','__file__':str(source)} exec(compile(src,str(source),'exec'),ctx) d=ctx['doc'];body=d.getObject('Body');f=d.getObject('InnerHoleThrough');d.recompute() assert f.Shape.isValid() and len(f.Shape.Solids)==1 def inspect(shape): found=[] for face in shape.Faces: surf=face.Surface if isinstance(surf,Part.Cylinder) and abs(surf.Radius-1.65)<1e-5: c=surf.Center if abs(math.hypot(c.x,c.y)-24)<1e-4 and face.BoundBox.ZLength>7.99: found.append((c.x,c.y)) assert len(found)==6, 'found %d intended Ø3.3 through bores' % len(found) for i in range(6): a=math.radians(30+i*60);x=24*math.cos(a);y=24*math.sin(a) assert any(math.hypot(x-u,y-v)<1e-4 for u,v in found), 'Missing bore %d' % i return found pos=inspect(f.Shape);logs.append('FCStd: six Ø3.3 full-depth PCD48 holes, phase 30°, coordinates='+str(pos)) original=App.openDocument(str(root/'models'/'conversion_disk_parametric.FCStd')) orig=original.getObject('InnerHoleThrough').Shape masks=[Part.makeCylinder(3,8.2,App.Vector(24*math.cos(math.radians(30+i*60)),24*math.sin(math.radians(30+i*60)),-.1)) for i in range(6)] mask=masks[0].multiFuse(masks[1:]) def outside(a,b): t=a.cut(b) if t.isNull():return 0.0 try:r=t.cut(mask) except ValueError as e: if str(e)=='Null shape':return 0.0 raise return 0.0 if r.isNull() else r.Volume old_new=outside(orig,f.Shape);new_old=outside(f.Shape,orig) assert old_new<1e-5 and new_old<1e-5 logs.append('Outside hole masks differences: old-new=%.9f new-old=%.9f mm³' % (old_new,new_old)) App.closeDocument(original.Name);App.setActiveDocument(d.Name) for o in d.Objects: if o.ViewObject:o.ViewObject.Visibility=False body.ViewObject.Visibility=True;f.ViewObject.Visibility=True Gui.activeDocument().activeView().viewAxonometric();Gui.activeDocument().activeView().fitAll();Gui.updateGui() Gui.activeDocument().activeView().saveImage(str(out/'转换盘_M4底孔预览.png'),1500,1100,'White') d.saveAs(str(out/'转换盘_M4底孔.FCStd'));volume=f.Shape.Volume Import.export([f],str(out/'转换盘_M4底孔.step')) App.closeDocument(d.Name) d=App.openDocument(str(out/'转换盘_M4底孔.FCStd'));d.recompute();f=d.getObject('InnerHoleThrough') assert f.Shape.isValid();inspect(f.Shape);logs.append('FCStd reopened and recomputed: valid') App.closeDocument(d.Name) d=App.newDocument('STEPCheck');Import.insert(str(out/'转换盘_M4底孔.step'),d.Name);d.recompute() shapes=[o.Shape for o in d.Objects if hasattr(o,'Shape') and not o.Shape.isNull()] assert len(shapes)==1 and shapes[0].isValid(); inspect(shapes[0]);assert abs(shapes[0].Volume-volume)<0.001 logs.append('STEP reopened: valid, six Ø3.3 through bores, volume=%.8f, error=%.9f' % (shapes[0].Volume,abs(shapes[0].Volume-volume))) App.closeDocument(d.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()