"""Build a separate, editable 70 adapter with 24 M4x0.7 modeled threads. Run via rebuild_70_m4_threaded.FCMacro; does not overwrite original files. """ from pathlib import Path import os ROOT = Path(__file__).resolve().parent.parent os.chdir(str(ROOT)) OUT = ROOT / 'models' / '70_m4_threaded' OUT.mkdir(parents=True, exist_ok=True) base = (ROOT / 'scripts' / 'build_70.py').read_text(encoding='utf-8') base = base.replace('import Part\nimport Import', 'import Part\nimport Sketcher\nimport Import') base = base.replace("target = os.path.join(root, 'models', '\\u0037\\u0030\\u8f6c\\u63a5\\u677f_\\u53c2\\u6570\\u5316.FCStd')", "target = str(OUT / '70_M4_threaded.FCStd')") base = base.replace("('HoleDiameter', '4.234 mm', 'Through hole diameter (all 24 holes)')", "('HoleDiameter', '3.322 mm', 'M4x0.7 modeled hole minor diameter for continuous 90 degree countersinks')") start = base.index("for name, pcd, angle in [('42', 'PCD42', 'Angle42')") end = base.index('\ncones = []', start) replacement = '''# One native PartDesign Hole with a 24-circle parametric profile. thread_body = doc.addObject('PartDesign::Body', 'ThreadBody') thread_body.Label = 'M4 threaded holes - editable' thread_body.BaseFeature = current doc.recompute() s = thread_body.newObject('Sketcher::SketchObject', 'ThreadProfiles') s.Label = '24 thread centers, four PCD groups' s.Placement.Base.z = 8 s.setExpression('Placement.Base.z', 'Parameters.Thickness') for name,pcd,angle,angle0 in [('42','PCD42','Angle42',45),('48','PCD48','Angle48',0),('508','PCD508','Angle508',30),('70','PCD70','Angle70',30)]: radius0={'42':21.0,'48':24.0,'508':25.4,'70':35.0}[name] for i in range(6): t=math.radians(angle0+i*60) x,y=radius0*math.cos(t),radius0*math.sin(t) if abs(x)<1e-8: x=0.0 if abs(y)<1e-8: y=0.0 k=s.addGeometry(Part.Circle(App.Vector(x,y,0),App.Vector(0,0,1),1.0),False) q=s.addConstraint(Sketcher.Constraint('DistanceX',k,3,x)) s.setExpression('Constraints[%d]' % q, 'Parameters.%s / 2 * cos(Parameters.%s + %d deg)' % (pcd,angle,i*60)) q=s.addConstraint(Sketcher.Constraint('DistanceY',k,3,y)) s.setExpression('Constraints[%d]' % q, 'Parameters.%s / 2 * sin(Parameters.%s + %d deg)' % (pcd,angle,i*60)) s.addConstraint(Sketcher.Constraint('Radius',k,1.0)) doc.recompute() h=thread_body.newObject('PartDesign::Hole','ThreadHoles') h.Label='24 modeled M4x0.7 through threads' h.Profile=s h.ThreadType='ISOMetricProfile' h.Threaded=True doc.recompute() h.ThreadSize='M4x0.7' h.DepthType='ThroughAll' h.ThreadDepthType='Hole Depth' h.ModelThread=True doc.recompute() if h.Shape.isNull() or not h.Shape.isValid() or len(h.Shape.Solids)!=1: raise RuntimeError('Invalid thread feature '+str(h.State)) current=h print('THREAD', 'valid',h.Shape.isValid(),'faces',len(h.Shape.Faces),'dia',h.Diameter,'thread',h.ThreadSize) ''' base = base[:start] + replacement + base[end:] base = base.replace("print('OUTPUT', target)", "Import.export([final], str(OUT / '70_M4_threaded.step'))\nGui.activeDocument().activeView().saveImage(str(OUT / '70_M4_preview.png'), 1600, 1200, 'White')\nprint('OUTPUT', target)") exec(compile(base, str(ROOT/'scripts'/'build_70.py'), 'exec'), {'OUT':OUT, '__name__':'__main__'})