160 lines
8.5 KiB
Python
160 lines
8.5 KiB
Python
"""Closed-bore pillar concept for M9C12 / MT6826S; NOT a print-ready part.
|
|
|
|
Run with FreeCAD GUI via rebuild_pcd50_pillar_concept.FCMacro. The existing
|
|
models are input only. All nominal dimensions and limitations are in the report.
|
|
"""
|
|
from pathlib import Path
|
|
import math, time, traceback
|
|
import FreeCAD as A, FreeCADGui as G, Part, Import
|
|
from pivy import coin
|
|
|
|
R=Path(__file__).resolve().parent.parent
|
|
O=R/'models'/'pcd50_pillar_concept'; O.mkdir(parents=True,exist_ok=True)
|
|
V=A.Vector
|
|
def cy(r,h,x,y,z): return Part.makeCylinder(r,h,V(x,y,z))
|
|
def add(d,n,label,s,c,t=0):
|
|
o=d.addObject('PartDesign::Feature',n);o.Label=label;o.Shape=s
|
|
o.ViewObject.ShapeColor=c;o.ViewObject.LineColor=(.1,.1,.1);o.ViewObject.Transparency=t
|
|
return o
|
|
|
|
try:
|
|
src=A.openDocument(str(R/'models'/'pcd50_fit_concept'/'PCD50_Assembly.FCStd'))
|
|
names=['Motor','Holder','Magnet','PCB','Chip']+['PCBHead%d'%i for i in range(1,4)]+['Corner%d'%i for i in range(4)]+['Rail%d'%i for i in range(2)]
|
|
sh={n:src.getObject(n).Shape.copy() for n in names}
|
|
old=src.getObject('OpenBracket').Shape.copy()
|
|
A.closeDocument(src.Name)
|
|
# PCD50 source uses corner offset +2.0mm; move 0.5mm farther out.
|
|
for n in ['Corner%d'%i for i in range(4)]+['Rail%d'%i for i in range(2)]:
|
|
c=(sh[n].BoundBox.XMin+sh[n].BoundBox.XMax)/2
|
|
sh[n].translate(V(.5 if c>0 else -.5,0,0))
|
|
pts=[(25*math.cos(math.radians(45+90*i)),25*math.sin(math.radians(45+90*i))) for i in range(4)]
|
|
# Rebuild a complete panel rather than inherit the prior U-slot tool gaps.
|
|
# Preserve the original three PCB pads, not its open 50mm fastener ears.
|
|
pcbpads=old.common(Part.makeBox(120,120,3.2,V(-60,-60,10.9)))
|
|
panel=Part.makeBox(49,49,3.2,V(-24.5,-24.5,10.9)).cut(cy(16.8,3.22,0,0,10.89)).fuse(pcbpads)
|
|
p76=38/math.sqrt(2)
|
|
for sx in (-1,1):
|
|
for sy in (-1,1):panel=panel.cut(cy(5.2,3.24,sx*p76,sy*p76,10.88))
|
|
columns=[]; cutters=[]
|
|
for i in range(4):
|
|
bb=sh['Corner%d'%i].BoundBox
|
|
cutters.append(Part.makeBox(bb.XLength+.4,bb.YLength+.4,3.22,V(bb.XMin-.2,bb.YMin-.2,-.01)))
|
|
bracket=panel
|
|
for x,y in pts:
|
|
# Carry each column through the panel thickness so the screw head bears on
|
|
# a complete closed ring rather than hanging over the old open-panel slot.
|
|
col=cy(4.5,13.6,x,y,.5)
|
|
for cut in cutters:col=col.cut(cut)
|
|
bracket=bracket.fuse(col)
|
|
for x,y in pts:bracket=bracket.cut(cy(2.2,13.8,x,y,.4))
|
|
bracket=bracket.removeSplitter()
|
|
if bracket.isNull() or not bracket.isValid() or len(bracket.Solids)!=1:raise RuntimeError('Invalid / disconnected closed-hole bracket')
|
|
# Four identical nominal 0.5mm support shims, not under 76mm angle brackets.
|
|
shims=[]
|
|
for x,y in pts:
|
|
s=cy(4.5,.5,x,y,0).cut(cy(2.2,.52,x,y,-.01))
|
|
for cut in cutters:s=s.cut(cut)
|
|
s=s.removeSplitter()
|
|
if s.isNull() or not s.isValid() or len(s.Solids)!=1:raise RuntimeError('invalid shim')
|
|
shims.append(s)
|
|
doc=A.newDocument('PCD50_PillarConcept');objs={}
|
|
colors={'Motor':(.32,.34,.37),'Holder':(.98,.55,.1),'Magnet':(.8,.8,.8),'PCB':(.04,.65,.19),'Chip':(.1,.1,.1)}
|
|
for n in names:
|
|
c=colors.get(n,(.84,.47,.13) if n.startswith('Corner') else (.55,.56,.60) if n.startswith('Rail') else (.8,.8,.8))
|
|
objs[n]=add(doc,n,n,sh[n],c,70 if n=='Motor' else 20 if n.startswith('Corner') else 0)
|
|
b=add(doc,'PillarBracket','PCD50 closed-bore long pillar - conceptual',bracket,(.12,.43,.88))
|
|
shimobjs=[add(doc,'Shim%d'%i,'PCD50 0.5mm shim - illustrative',s,(.70,.70,.75)) for i,s in enumerate(shims)]
|
|
heads=[];shafts=[]
|
|
for i,(x,y) in enumerate(pts):
|
|
heads.append(add(doc,'Head50_%d'%i,'M4 head Ø7x3 assumed',cy(3.5,3,x,y,14.1),(.8,.8,.8)))
|
|
shafts.append(add(doc,'Shaft50_%d'%i,'M4x20 illustrative - nominal 5.9mm engagement',cy(2,20,x,y,-5.9),(.8,.8,.8)))
|
|
p=38/math.sqrt(2);mount76=[];washers76=[]
|
|
for sx in (-1,1):
|
|
for sy in (-1,1):
|
|
x,y=sx*p,sy*p
|
|
washer=cy(5,.8,x,y,3).cut(cy(2.15,.82,x,y,2.99)).removeSplitter()
|
|
washers76.append(add(doc,'Washer76_%d'%len(washers76),'M4 washer Ø10x0.8 assumed',washer,(.75,.75,.77)))
|
|
screw=cy(2,8,x,y,-4.2).fuse(cy(3.5,2.5,x,y,3.8)).removeSplitter()
|
|
mount76.append(add(doc,'Mount76_%d'%len(mount76),'M4x8 angle screw conceptual',screw,(.8,.8,.8)))
|
|
doc.recompute()
|
|
corners=[objs['Corner%d'%i] for i in range(4)];rails=[objs['Rail%d'%i] for i in range(2)]
|
|
pairs=[]
|
|
for a in [b]+shimobjs+heads+shafts:
|
|
for c in corners+rails:pairs.append((a,c))
|
|
for a in shafts:
|
|
for c in [b]+shimobjs:pairs.append((a,c))
|
|
for a in mount76+washers76:
|
|
for c in corners+rails+[b]+shimobjs+heads+shafts:pairs.append((a,c))
|
|
for a in [b]+shimobjs+heads+shafts:
|
|
for n in ['Motor','Holder','Magnet','PCB','Chip']+['PCBHead%d'%i for i in range(1,4)]:pairs.append((a,objs[n]))
|
|
hits=[]
|
|
for a,c in pairs:
|
|
if a.Shape.BoundBox.intersect(c.Shape.BoundBox):
|
|
vol=a.Shape.common(c.Shape).Volume
|
|
if vol>.001:hits.append('%s / %s %.6fmm3'%(a.Name,c.Name,vol))
|
|
# The motor-facing 50mm bores must remain complete; no side exit. Sample
|
|
# immediately outside the bore in 72 directions at a low and high Z.
|
|
bore=[]
|
|
for i,(x,y) in enumerate(pts):
|
|
for z in (1.0,13.0):
|
|
missing=sum(not bracket.isInside(V(x+2.22*math.cos(a*math.pi/36),y+2.22*math.sin(a*math.pi/36),z),1e-7,True) for a in range(72))
|
|
bore.append('Pillar%d z=%.1f closed-bore surrounding shell missing samples: %d/72'%(i,z,missing))
|
|
access=[]
|
|
for i,(x,y) in enumerate(pts):
|
|
tool=cy(3.75,20,x,y,17.11);found=[]
|
|
for o in [b]+corners+rails:
|
|
if tool.BoundBox.intersect(o.Shape.BoundBox):
|
|
v=tool.common(o.Shape).Volume
|
|
if v>.001:found.append('%s %.4f'%(o.Name,v))
|
|
access.append('M4 tool %d: %s'%(i,', '.join(found) if found else 'no checked obstruction'))
|
|
for sx in (-1,1):
|
|
for sy in (-1,1):
|
|
x,y=sx*p,sy*p;tool=cy(3.75,20,x,y,6.31);found=[]
|
|
for o in [b]+corners+rails:
|
|
if tool.BoundBox.intersect(o.Shape.BoundBox):
|
|
v=tool.common(o.Shape).Volume
|
|
if v>.001:found.append('%s %.4f'%(o.Name,v))
|
|
access.append('76 M4 tool %+.0f,%+.0f: %s'%(sx,sy,', '.join(found) if found else 'no checked obstruction'))
|
|
headareas=[]
|
|
for x,y in pts:
|
|
count=0;step=.2
|
|
for ix in range(-18,19):
|
|
for iy in range(-18,19):
|
|
dx,dy=ix*step,iy*step
|
|
if dx*dx+dy*dy<=3.5**2 and bracket.isInside(V(x+dx,y+dy,14.09),1e-7,True):count+=1
|
|
headareas.append(count*step*step)
|
|
report=['CONCEPT ONLY: PCD50/PCD76 phase45 and slot9.5x5.5 assumed.',
|
|
'Corners flush to motor, moved 2.5mm outward from slot midpoint; 4mm screw nominal slot-end clearance 0.25mm.',
|
|
'Pillars 0.5..10.9mm and panel 10.9..14.1mm; closed Ø4.4mm bore.',
|
|
'M4x20 nominal screw engagement in motor 20 - 14.1 = 5.9mm, true tapped depth unknown.',
|
|
'Pillar cylinders Ø9 extend from z0.5 to 14.1 and merge through panel; clipped at z0.5..3.21 against expanded corner projection 0.2mm.',
|
|
'Minimum low-side wall near angle ~0.54mm theoretical, no print/strength guarantee.',
|
|
'Estimated head bearing material by sampling (mm2): '+', '.join('%.2f'%x for x in headareas),
|
|
'Selected positive-volume intersections >0.001mm3:']+(hits or ['none'])+['Tool envelope Ø7.5:']+access+['Low-bore shell check (positive shell is expected):']+bore
|
|
(O/'geometry_check.txt').write_text('\n'.join(report)+'\n',encoding='utf8')
|
|
G.activateWorkbench('PartWorkbench');view=G.activeDocument().activeView()
|
|
def render(top,path):
|
|
if top:view.viewTop()
|
|
else:
|
|
view.viewAxonometric();G.updateGui();time.sleep(.5)
|
|
view.getCameraNode().orientation.setValue(coin.SbRotation(.880476,.115917,-.279848,-.364705))
|
|
G.updateGui();time.sleep(.8);view.fitAll();G.updateGui();time.sleep(.8);G.updateGui();view.saveImage(str(O/path),1600,1200,'White')
|
|
render(True,'rear.png');render(False,'assembly.png')
|
|
states={o.Name:o.ViewObject.Visibility for o in doc.Objects if hasattr(o,'Shape')}
|
|
for o in doc.Objects:
|
|
if hasattr(o,'Shape'):o.ViewObject.Visibility=False
|
|
b.ViewObject.Visibility=True;render(False,'bracket.png')
|
|
# Remove the outer half of the two upper pillars to show the bore cross
|
|
# section, then discard this object before saving the actual assembly.
|
|
b.ViewObject.Visibility=False
|
|
section=bracket.cut(Part.makeBox(120,60,25,V(-60,-60-25/math.sqrt(2),0))).removeSplitter()
|
|
sectionobj=add(doc,'SectionPreview','Half section for illustration only',section,(.12,.43,.88))
|
|
doc.recompute();render(False,'section.png');doc.removeObject(sectionobj.Name)
|
|
for o in doc.Objects:
|
|
if hasattr(o,'Shape'):o.ViewObject.Visibility=states[o.Name]
|
|
render(False,'assembly.png');doc.recompute();doc.saveAs(str(O/'PCD50_Pillar_Assembly.FCStd'))
|
|
Import.export([o for o in doc.Objects if hasattr(o,'Shape')],str(O/'PCD50_Pillar_Assembly.step'))
|
|
(O/'DONE.txt').write_text('ok\n',encoding='utf8')
|
|
except Exception:
|
|
(O/'ERROR.txt').write_text(traceback.format_exc(),encoding='utf8');raise
|