from pathlib import Path import math, json, os, traceback, time import FreeCAD as A, FreeCADGui as G, Part, Mesh, MeshPart, Import V=A.Vector root=Path(r'C:\projects\wheel') out=root/'models'/'encoder_standoff_plate_print' try: src=A.openDocument(str(root/'models'/'symmetric_corner_18_concept'/'Assembly_对称角码装配_概念.FCStd')) src.recompute() plate=src.getObject('EncoderPlateConcept').Shape.copy() assert plate.isValid() and len(plate.Solids)==1 and plate.Volume>1 # 10mm standoff with 1mm shim -> support face at z=11, rear motor z=0. plate.translate(V(0,0,0.1)) hit=[]; distances=[] for o in src.Objects: if not hasattr(o,'Shape') or o.Shape.isNull() or not o.ViewObject.Visibility or o.Name=='EncoderPlateConcept': continue if o.Name.startswith(('EncoderHex','EncoderSpacer')): continue v=plate.common(o.Shape).Volume if plate.BoundBox.intersect(o.Shape.BoundBox) else 0 if v>0.001: hit.append((o.Name,round(v,6))) if o.Name.startswith(('NewMotorCorner','Rail')): distances.append((o.Name,round(plate.distToShape(o.Shape)[0],5))) # Test panel has four complete M4 clear bores, as in the source design. bore=[] for k in range(4): angle=math.radians(45+90*k) x=25*math.cos(angle);y=10+25*math.sin(angle) probe=Part.makeCylinder(2.19,3.3,V(x,y,10.95)) bore.append(round(probe.common(plate).Volume,6)) assert max(bore)<1e-5 # The old PCB rests against the lower face of this 3.2 mm slab. Verify # each 1.8 mm pilot and surrounding pads match the old original panel. pcb_centres=[(0,13.49),(-11.71,-6.76),(11.71,-6.76)] pcb_probes=[]; pcb_pad=[] for x,y in pcb_centres: y+=10 probe=Part.makeCylinder(.89,3.3,V(x,y,10.95)) pcb_probes.append(round(probe.common(plate).Volume,6)) pad=Part.makeCylinder(3.1,3.2,V(x,y,11)) pcb_pad.append(round(pad.common(plate).Volume,6)) assert max(pcb_probes)<1e-5 and min(pcb_pad)>50 orig=src.getObject('OpenPillarBracket').Shape.common(Part.makeBox(120,120,3.2,V(-60,-60,10.9))) compare=plate.copy();compare.translate(V(0,0,-.1)) panel_diff=[orig.cut(compare).Volume,compare.cut(orig).Volume] assert max(panel_diff)<1e-5 # Put support face flat on build plate and centre the source motor axis. printed=plate.copy();printed.translate(V(0,-10,-11)) assert abs(printed.BoundBox.ZMin)<1e-5 doc=A.newDocument('EncoderStandoffPlate') obj=doc.addObject('PartDesign::Feature','EncoderPlate') obj.Label='PCD50 encoder panel - four metal stand-offs' obj.Shape=printed;obj.ViewObject.ShapeColor=(.15,.4,.85) doc.recompute();doc.saveAs(str(out/'EncoderPlate.FCStd')) Import.export([obj],str(out/'EncoderPlate.step')) mesh=MeshPart.meshFromShape(Shape=printed,LinearDeflection=0.05,AngularDeflection=math.radians(15),Relative=False) mesh.write(str(out/'EncoderPlate.stl')) mesh2=Mesh.Mesh(str(out/'EncoderPlate.stl')) assert mesh2.isSolid() and mesh2.Volume>0 step=Part.read(str(out/'EncoderPlate.step')) assert step.isValid() and len(step.Solids)==1 G.activateWorkbench('PartWorkbench');G.activeDocument().activeView().viewAxonometric();G.updateGui();time.sleep(.6);view=G.activeDocument().activeView();view.fitAll();G.updateGui();time.sleep(.6);view.saveImage(str(out/'EncoderPlate.png'),1400,1000,'White') report={'solid_valid':printed.isValid(),'solids':len(printed.Solids),'volume_mm3':printed.Volume,'dimensions_mm':[printed.BoundBox.XLength,printed.BoundBox.YLength,printed.BoundBox.ZLength],'mesh_closed':mesh2.isSolid(),'mesh_volume_mm3':mesh2.Volume,'bore_probe_intersections_mm3':bore,'pcb_pilot_probe_intersections_mm3':pcb_probes,'pcb_pad_material_mm3':pcb_pad,'original_panel_two_way_difference_mm3':panel_diff,'tested_position':'10 mm standoffs + 1 mm shim, panel bottom at z=11 mm','positive_intersections_mm3':hit,'angle_rail_clearances_mm':distances,'warning':'All geometry and PCB dimensions assumed; tool access and physical assembly unverified.'} (out/'验证.json').write_text(json.dumps(report,ensure_ascii=False,indent=2),encoding='utf8') A.closeDocument(doc.Name) reopened=A.openDocument(str(out/'EncoderPlate.FCStd'));reopened.recompute() assert reopened.getObject('EncoderPlate').Shape.isValid();A.closeDocument(reopened.Name) A.closeDocument(src.Name) (out/'完成.txt').write_text('ok',encoding='utf8') except Exception: (out/'错误.txt').write_text(traceback.format_exc(),encoding='utf8') os._exit(0)