107 lines
5.9 KiB
Python
107 lines
5.9 KiB
Python
"""Illustrative 76 PCD dual-stage standoff mount; all fasteners are assumed."""
|
|
import math,time,traceback
|
|
from pathlib import Path
|
|
import FreeCAD as A, FreeCADGui as G, Part, Import
|
|
from pivy import coin
|
|
R=Path(__file__).resolve().parent.parent
|
|
O=R/'models'/'shared76_concept'; O.mkdir(parents=True,exist_ok=True)
|
|
V=A.Vector
|
|
def cy(r,h,x=0,y=0,z=0): return Part.makeCylinder(r,h,V(x,y,z))
|
|
def item(doc,name,label,s,c,trans=0):
|
|
o=doc.addObject('PartDesign::Feature',name);o.Label=label;o.Shape=s
|
|
o.ViewObject.ShapeColor=c;o.ViewObject.LineColor=(.1,.1,.1);o.ViewObject.Transparency=trans;return o
|
|
def corner(x,y,side):
|
|
start=x-8.75
|
|
flat=Part.makeBox(20,18,3,V(start,y-9,0))
|
|
slot=Part.makeBox(4,5.5,3.04,V(x-2,y-2.75,-.02)).fuse(cy(2.75,3.04,x-2,y,-.02)).fuse(cy(2.75,3.04,x+2,y,-.02))
|
|
flat=flat.cut(slot)
|
|
wall=Part.makeBox(3,18,20,V(start+17,y-9,0))
|
|
wall=wall.cut(Part.makeCylinder(2.75,3.04,V(start+16.98,y,10),V(1,0,0)))
|
|
sh=flat.fuse(wall).removeSplitter()
|
|
return sh.mirror(V(),V(1,0,0)) if side<0 else sh
|
|
def hexprism(x,y,z,height):
|
|
pts=[V(x+4.05*math.cos(math.radians(30+i*60)),y+4.05*math.sin(math.radians(30+i*60)),z) for i in range(6)]
|
|
pts.append(pts[0]);return Part.Face(Part.Wire(Part.makePolygon(pts).Edges)).extrude(V(0,0,height))
|
|
try:
|
|
src=A.openDocument(str(R/'models'/'encoder_print_v1'/'EncoderAssembly_Fit.FCStd'))
|
|
names=['Motor','Holder','Magnet','PCB','Chip','PCBHead1','PCBHead2','PCBHead3']
|
|
base={k:src.getObject(k).Shape.copy() for k in names}
|
|
oldring=src.getObject('Bracket').Shape.copy().common(Part.makeBox(100,100,3.2,V(-50,-50,10.9)))
|
|
A.closeDocument(src.Name)
|
|
doc=A.newDocument('Shared76Concept'); objs={}
|
|
colors={'Motor':(.32,.34,.37),'Holder':(.98,.55,.1),'Magnet':(.8,.8,.8),'PCB':(.04,.65,.19),'Chip':(.1,.1,.1)}
|
|
for k,s in base.items():objs[k]=item(doc,k,k,s,colors.get(k,(.8,.8,.8)),70 if k=='Motor' else 0)
|
|
p=38/math.sqrt(2); rails=[]; corners=[]; washers=[]; columns=[]; bolts=[]
|
|
for side in (-1,1):
|
|
for y in (-p,p):
|
|
x=side*p
|
|
c=corner(p,y,side);corners.append(item(doc,'Corner%d'%len(corners),'2020 angle direct against motor',c,(.84,.47,.13),15))
|
|
washer=cy(5,.8,x,y,3).cut(cy(2.15,.82,x,y,2.99))
|
|
washers.append(item(doc,'Washer%d'%len(washers),'Metal M4 washer, OD10 assumed',washer,(.73,.73,.76)))
|
|
body=hexprism(x,y,3.8,7.1).cut(cy(2.0,6.62,x,y,4.29))
|
|
stud=cy(1.95,8.8,x,y,-5)
|
|
standoff=body.fuse(stud).removeSplitter()
|
|
if not standoff.isValid() or len(standoff.Solids)!=1:raise RuntimeError('invalid standoff')
|
|
columns.append(item(doc,'Standoff%d'%len(columns),'Metal male-female standoff assumed',standoff,(.85,.75,.38)))
|
|
bolt=cy(1.9,6.6,x,y,7.5).fuse(cy(3.5,2.5,x,y,14.1)).removeSplitter()
|
|
bolts.append(item(doc,'TopM4%d'%len(bolts),'Separate top M4 head and screw assumed',bolt,(.78,.8,.82)))
|
|
x=p-8.75+20
|
|
rail=Part.makeBox(20,110,20,V(x,-55,0))
|
|
if side<0:rail=rail.mirror(V(),V(1,0,0))
|
|
rails.append(item(doc,'Rail%d'%len(rails),'2020 extrusion envelope; length assumed',rail,(.55,.56,.6)))
|
|
# Square encoder frame suggested by sketch, PCB and magnet positions unchanged.
|
|
# Outer 64mm leaves material outside the 76-PCD nominal screw centres.
|
|
frame=Part.makeBox(64,64,3.2,V(-32,-32,10.9)).cut(cy(16.8,3.22,0,0,10.89)).fuse(oldring)
|
|
# 4 open slots, to outer x side, directly above the 76 PCD standoff tops.
|
|
for side in (-1,1):
|
|
for y in (-p,p):
|
|
x=side*p; start=x if side>0 else x-10
|
|
cut=Part.makeBox(10,4.4,3.24,V(start,y-2.2,10.88)).fuse(cy(2.2,3.24,x,y,10.88))
|
|
frame=frame.cut(cut)
|
|
frame=frame.removeSplitter()
|
|
if frame.isNull() or not frame.isValid() or len(frame.Solids)!=1:raise RuntimeError('Frame invalid')
|
|
fr=item(doc,'EncoderFrame','Encoder frame - open slots PCD76, concept',frame,(.13,.43,.86))
|
|
doc.recompute()
|
|
checks=[]
|
|
pairs=[]
|
|
for c in corners:
|
|
for t in [fr,objs['PCB'],objs['Holder'],objs['Magnet']]+rails:pairs.append((c,t))
|
|
for t in [objs['Motor'],objs['Holder'],objs['Magnet'],objs['PCB'],objs['Chip']]+corners+rails+washers+columns+bolts:pairs.append((fr,t))
|
|
for col in columns:
|
|
for c in corners:pairs.append((col,c))
|
|
for w in washers:pairs.append((col,w))
|
|
for b in bolts:
|
|
for col in columns:pairs.append((b,col))
|
|
for a,b in pairs:
|
|
if not a.Shape.BoundBox.intersect(b.Shape.BoundBox):continue
|
|
vol=a.Shape.common(b.Shape).Volume
|
|
if vol>.0001:checks.append('%s / %s: %.5f mm3'%(a.Name,b.Name,vol))
|
|
report=['CONCEPT; corners directly contact motor, each held by metal washer and metal male-female column.',
|
|
'PCD76 and PCD50 phase=45deg assumed; 2020=20x20x110 solid envelope; angle slot=9.5x5.5 assumed and set at middle.',
|
|
'Illustrative washer OD10x0.8; hex column across flats ~7mm height 7.1, male M4 assumed 8.8mm; 5mm enters motor.',
|
|
'PCB back z10.9; chip-to-magnet 1mm nominal. Actual fastener, pressure, tool, wiring, rotation tests absent.',
|
|
'Checked positive volume intersections >0.0001mm3:']+(checks or ['none in checked pairs'])
|
|
(O/'Shared76_check.txt').write_text('\n'.join(report)+'\n',encoding='utf8')
|
|
view=G.activeDocument().activeView()
|
|
def render(kind,name):
|
|
if kind=='rear':view.viewTop()
|
|
else:
|
|
view.viewAxonometric();G.updateGui();time.sleep(.8)
|
|
view.getCameraNode().orientation.setValue(coin.SbRotation(.880476,.115917,-.279848,-.364705))
|
|
G.updateGui();time.sleep(1);view.fitAll();G.updateGui();time.sleep(1);G.updateGui();view.saveImage(str(O/name),1600,1200,'White')
|
|
render('rear','Shared76_rear.png');render('axon','Shared76_axon.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
|
|
fr.ViewObject.Visibility=True
|
|
render('rear','Shared76_frame.png')
|
|
for o in doc.Objects:
|
|
if hasattr(o,'Shape'):o.ViewObject.Visibility=states[o.Name]
|
|
render('axon','Shared76_axon.png')
|
|
doc.recompute();doc.saveAs(str(O/'Shared76.FCStd'))
|
|
Import.export([o for o in doc.Objects if hasattr(o,'Shape')],str(O/'Shared76.step'))
|
|
(O/'DONE.txt').write_text('success\n',encoding='utf8')
|
|
except Exception:
|
|
(O/'ERROR.txt').write_text(traceback.format_exc(),encoding='utf8')
|
|
raise
|