建立方向盘项目初始版本
This commit is contained in:
146
scripts/build_encoder_print_parts.py
Normal file
146
scripts/build_encoder_print_parts.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""Create first-fit printing parts for M9C12 + MT6826S in FreeCAD GUI.
|
||||
|
||||
Dimension assumptions are documented in the associated user instructions.
|
||||
Run using rebuild_encoder_print_parts.FCMacro. Does not change motor source.
|
||||
"""
|
||||
import math
|
||||
from pathlib import Path
|
||||
import FreeCAD as App
|
||||
import FreeCADGui as Gui
|
||||
import Part, Mesh, Import
|
||||
from pivy import coin
|
||||
|
||||
root = Path(__file__).resolve().parent.parent
|
||||
out = root / 'models' / 'encoder_print_v1'
|
||||
out.mkdir(parents=True, exist_ok=True)
|
||||
V = App.Vector
|
||||
doc = App.newDocument('EncoderPrintV1')
|
||||
|
||||
def cyl(r, h, x=0, y=0, z=0):
|
||||
return Part.makeCylinder(r, h, V(x,y,z))
|
||||
|
||||
def obj(name, label, shape, color):
|
||||
o=doc.addObject('PartDesign::Feature', name); o.Label=label; o.Shape=shape
|
||||
o.ViewObject.ShapeColor=color; o.ViewObject.LineColor=(0.1,0.1,0.1)
|
||||
return o
|
||||
|
||||
def check(name, shape):
|
||||
if shape.isNull() or not shape.isValid() or len(shape.Solids)!=1 or shape.Volume<0.01:
|
||||
raise RuntimeError('Invalid or non-single solid '+name)
|
||||
|
||||
# rear motor face z=0; positive z is away from motor.
|
||||
# Base and tower remain clear of three PCB screw heads when rotating.
|
||||
holder = cyl(13.5,3).fuse(cyl(10,4.5,z=3)).removeSplitter()
|
||||
holder = holder.cut(cyl(1.65,7.6,z=-0.05))
|
||||
# Screw head pocket from z=1.9 to 4.5; nominal M3 head 6 mm assumed.
|
||||
holder = holder.cut(cyl(3.6,2.6,z=1.9))
|
||||
# Magnet pocket: 10.3 mm diameter nominal, 3 mm deep.
|
||||
holder = holder.cut(cyl(5.15,3.05,z=4.5))
|
||||
# An edge notch allows gentle magnet removal. Never pry on magnet face.
|
||||
notch=Part.makeBox(6,2.5,2.4,V(4.5,-1.25,5.1))
|
||||
holder=holder.cut(notch).removeSplitter()
|
||||
check('holder',holder)
|
||||
|
||||
# Gap at no shim 0.5 mm. Magnet face z=7.5, chip face z=8.0;
|
||||
# chip is assumed 0.8 mm high, so board near face z=8.8 and far face 10.4.
|
||||
# Add equal 0.5 mm under feet for initial nominal 1 mm gap.
|
||||
board_near=8.8
|
||||
board_far=10.4
|
||||
ring_z=board_far
|
||||
ring_h=3.2
|
||||
bracket=cyl(20.5,ring_h,z=ring_z).cut(cyl(16.8,ring_h+0.02,z=ring_z-0.01))
|
||||
pcb_centres=[(0,13.49),(-11.71,-6.76),(11.71,-6.76)]
|
||||
# Three local bosses/bridges; PCB rests only locally instead of an unbroken ring.
|
||||
for x,y in pcb_centres:
|
||||
boss=cyl(3.2,ring_h,x,y,ring_z)
|
||||
a=math.degrees(math.atan2(y,x))
|
||||
bridge=Part.makeBox(6.0,4.0,ring_h,V(13,-2,ring_z))
|
||||
bridge.rotate(V(0,0,0),V(0,0,1),a)
|
||||
bracket=bracket.fuse(boss).fuse(bridge)
|
||||
for i in range(4):
|
||||
angle=45+i*90
|
||||
a=math.radians(angle); ux,uy=math.cos(a),math.sin(a)
|
||||
# Post nearest face at r=16.8; PCB nominal radius 16.5.
|
||||
# M4 head has 0.7 mm radial clearance if its diameter is 7 mm.
|
||||
post=cyl(2.0,ring_z-2,18.8*ux,18.8*uy,2)
|
||||
foot=cyl(4.5,2,25*ux,25*uy,0)
|
||||
bridge=Part.makeBox(10,4,2,V(18,-2,0)); bridge.rotate(V(0,0,0),V(0,0,1),angle)
|
||||
bracket=bracket.fuse(post).fuse(foot).fuse(bridge)
|
||||
bracket=bracket.cut(cyl(2.2,2.05,25*ux,25*uy,-0.02))
|
||||
# Ø1.8 pilot bores for M2 self-tapping screws, through 3.2 mm support thickness.
|
||||
for x,y in pcb_centres: bracket=bracket.cut(cyl(0.9,ring_h+0.04,x,y,ring_z-0.02))
|
||||
bracket=bracket.removeSplitter()
|
||||
check('bracket',bracket)
|
||||
|
||||
shim05=cyl(4.5,.5).cut(cyl(2.2,.52,z=-.01))
|
||||
shim10=cyl(4.5,1).cut(cyl(2.2,1.02,z=-.01))
|
||||
check('shim05',shim05); check('shim10',shim10)
|
||||
|
||||
# Export all printable parts at z=0 in an independent print-parts document.
|
||||
parts=App.newDocument('EncoderPartsPrint')
|
||||
def pobj(name,label,shape,color):
|
||||
o=parts.addObject('PartDesign::Feature',name);o.Label=label;o.Shape=shape
|
||||
o.ViewObject.ShapeColor=color;return o
|
||||
ph=pobj('MagnetHolder','Magnet holder - print motor face down',holder,(0.96,.55,.12))
|
||||
print_bracket=bracket.copy();print_bracket.translate(V(0,0,-(ring_z+ring_h)))
|
||||
print_bracket.rotate(V(0,0,0),V(1,0,0),180)
|
||||
pb=pobj('EncoderBracket','Bracket - print PCB ring face down',print_bracket,(.2,.48,.8))
|
||||
s05=pobj('Shim05','M4 shim 0.5mm - print 4',shim05,(.7,.7,.7))
|
||||
s10=pobj('Shim10','M4 shim 1.0mm - print 4 optional',shim10,(.7,.7,.7))
|
||||
parts.recompute()
|
||||
for o in [ph,pb,s05,s10]:
|
||||
check(o.Name,o.Shape)
|
||||
Mesh.export([o], str(out/(o.Name+'.stl')))
|
||||
Import.export([o], str(out/(o.Name+'.step')))
|
||||
mesh_results=[]
|
||||
for name in ['MagnetHolder','EncoderBracket','Shim05','Shim10']:
|
||||
mesh=Mesh.Mesh(str(out/(name+'.stl')))
|
||||
solid=mesh.isSolid(); bounds=mesh.BoundBox
|
||||
mesh_results.append('%s solid=%s facets=%d volume=%.5f ZMin=%.5f ZMax=%.5f' %
|
||||
(name,solid,mesh.CountFacets,mesh.Volume,bounds.ZMin,bounds.ZMax))
|
||||
if not solid or mesh.Volume <= 0 or abs(bounds.ZMin)>0.001:
|
||||
raise RuntimeError('STL verification failed: '+mesh_results[-1])
|
||||
out.joinpath('mesh_check.txt').write_text('\n'.join(mesh_results)+'\n',encoding='utf-8')
|
||||
parts.saveAs(str(out/'EncoderParts_Print.FCStd'))
|
||||
|
||||
# Assembly with nominal 0.5 mm shim under all four feet (1 mm sensor gap).
|
||||
App.setActiveDocument(doc.Name)
|
||||
motor=Part.read(str(root/'models'/'MAD_M9C12.step'))
|
||||
motor.rotate(V(0,0,0),V(1,0,0),180)
|
||||
mo=obj('Motor','Original approximate M9C12',motor,(.34,.36,.38));mo.ViewObject.Transparency=70
|
||||
ho=obj('Holder','Print magnet holder',holder,(.96,.55,.12))
|
||||
mag=cyl(5,3,z=4.5);mg=obj('Magnet','10 x 3 mm diametric magnet',mag,(.78,.78,.8))
|
||||
br=bracket.copy();br.translate(V(0,0,.5));bo=obj('Bracket','Print bracket with 0.5mm shims',br,(.2,.48,.8))
|
||||
pcb=cyl(16.5,1.6,z=board_near+.5)
|
||||
for x,y in pcb_centres: pcb=pcb.cut(cyl(1.5,1.62,x,y,board_near+.49))
|
||||
pc=obj('PCB','PCB approximate',pcb,(.06,.6,.22))
|
||||
chip=Part.makeBox(4,5,.8,V(-2,-2.5,board_near+.5-.8))
|
||||
ch=obj('Chip','Chip approximate',chip,(.1,.1,.1))
|
||||
for i in range(4):
|
||||
a=math.radians(45+i*90); x,y=25*math.cos(a),25*math.sin(a)
|
||||
so=obj('Shim%d'%i,'0.5mm shim %d'%(i+1),cyl(4.5,.5,x,y,0).cut(cyl(2.2,.52,x,y,-.01)),(.8,.8,.8))
|
||||
|
||||
# Inspect interferences in the simplified assembly. Include nominal M2
|
||||
# screw heads 4.0 mm dia, 1.5 mm high on the motor-facing PCB side.
|
||||
m2heads=[]
|
||||
for x,y in pcb_centres:
|
||||
head=cyl(2,1.5,x,y,board_near+.5-1.5)
|
||||
m2heads.append(head)
|
||||
obj('PCBHead%d'%len(m2heads),'M2 head envelope 4x1.5 assumed',head,(.8,.8,.8))
|
||||
checks=[('magnet_holder',mag,holder),('holder_bracket',holder,br),('magnet_bracket',mag,br),('chip_magnet',chip,mag),('board_bracket',pcb,br)]
|
||||
checks += [('holder_M2head_%d'%i,holder,h) for i,h in enumerate(m2heads,1)]
|
||||
results=[]
|
||||
for name,a,b in checks:
|
||||
vol=a.common(b).Volume
|
||||
results.append('%s %.6f mm3'%(name,vol))
|
||||
if vol>0.001: raise RuntimeError('Interference '+results[-1])
|
||||
doc.recompute()
|
||||
for o in [mo,ho,mg,bo,pc,ch]:
|
||||
if o.Shape.isNull() or not o.Shape.isValid(): raise RuntimeError('Invalid assembly '+o.Name)
|
||||
doc.saveAs(str(out/'EncoderAssembly_Fit.FCStd'))
|
||||
view=Gui.activeDocument().activeView()
|
||||
view.getCameraNode().orientation.setValue(coin.SbRotation(0.880476, 0.115917, -0.279848, -0.364705))
|
||||
view.fitAll();Gui.updateGui();view.saveImage(str(out/'assembly.png'),1600,1200,'White')
|
||||
out.joinpath('geometry_check.txt').write_text('\n'.join(results)+'\nNominal chip-face-to-magnet-face gap: 1 mm\n',encoding='utf-8')
|
||||
out.joinpath('BUILD_OK.txt').write_text('success\n',encoding='utf-8')
|
||||
print('PRINT_PARTS_OK')
|
||||
Reference in New Issue
Block a user