132 lines
7.0 KiB
Python
132 lines
7.0 KiB
Python
"""2020 corner bracket interference check, not a production drawing."""
|
|
import math, time
|
|
from pathlib import Path
|
|
import FreeCAD as A, FreeCADGui as G, Part, Import
|
|
from pivy import coin
|
|
|
|
ROOT=Path(__file__).resolve().parent.parent
|
|
OUT=ROOT/'models'/'profile_mount_check'
|
|
OUT.mkdir(parents=True,exist_ok=True)
|
|
V=A.Vector
|
|
src=A.openDocument(str(ROOT/'models'/'encoder_print_v1'/'EncoderAssembly_Fit.FCStd'))
|
|
base={k:src.getObject(k).Shape.copy() for k in ('Motor','Holder','Magnet','Bracket','PCB','Chip','Shim0','Shim1','Shim2','Shim3','PCBHead1','PCBHead2','PCBHead3')}
|
|
A.closeDocument(src.Name)
|
|
|
|
def cylinder(r,h,x,y,z): return Part.makeCylinder(r,h,V(x,y,z))
|
|
def corner(x,y,lift,side):
|
|
# Max outward fit with 3.9mm screw in assumed 5.5mm slot: offset 2.75mm,
|
|
# leaving only 0.05mm clearance at slot end. Extreme tolerance assumption.
|
|
start=x-6.0
|
|
flat=Part.makeBox(20,18,3,V(start,y-9,lift))
|
|
# slot 9.5 long, width assumed 5.5. Fused 4mm rectangle and 2 end arcs.
|
|
sc=x+2.75
|
|
slot=Part.makeBox(4,5.5,3.04,V(sc-2,y-2.75,lift-.02))
|
|
slot=slot.fuse(cylinder(2.75,3.04,sc-2,y,lift-.02)).fuse(cylinder(2.75,3.04,sc+2,y,lift-.02))
|
|
flat=flat.cut(slot)
|
|
wall=Part.makeBox(3,18,20,V(start+17,y-9,lift))
|
|
hole=Part.makeCylinder(2.75,3.04,V(start+16.98,y,lift+10),V(1,0,0))
|
|
wall=wall.cut(hole)
|
|
shape=flat.fuse(wall).removeSplitter()
|
|
if side<0: shape=shape.mirror(V(0,0,0),V(1,0,0))
|
|
return shape
|
|
|
|
def add(doc,name,label,shape,color,trans=0):
|
|
obj=doc.addObject('PartDesign::Feature',name); obj.Label=label;obj.Shape=shape
|
|
obj.ViewObject.ShapeColor=color;obj.ViewObject.LineColor=(.1,.1,.1);obj.ViewObject.Transparency=trans
|
|
return obj
|
|
|
|
def run(title,lift):
|
|
doc=A.newDocument(title)
|
|
colors={'Motor':(.3,.32,.35),'Holder':(.98,.55,.1),'Magnet':(.8,.8,.8),'Bracket':(.15,.43,.85),'PCB':(.04,.65,.19),'Chip':(.1,.1,.1)}
|
|
objects={}
|
|
for k,shape in base.items():
|
|
col=colors.get(k,(.75,.75,.75)); objects[k]=add(doc,k,'Encoder existing '+k,shape,col,70 if k=='Motor' else 0)
|
|
# Encoder M4 heads were absent from original assembly. Their outer diameter
|
|
# 7 mm and height 3 mm are explicit approximations, not measured hardware.
|
|
encoderheads=[]
|
|
for i in range(4):
|
|
a=math.radians(45+i*90); x,y=25*math.cos(a),25*math.sin(a)
|
|
shape=cylinder(3.5,3,x,y,2.5)
|
|
encoderheads.append(add(doc,'EncoderM4Head%d'%i,'Encoder M4 head 7x3 assumed',shape,(.8,.8,.8)))
|
|
p=38/math.sqrt(2); arms=[]; rails=[]; spacers=[]
|
|
# Each extrusion face touches the bracket wall at x=start+20. y extends 55
|
|
# each way, to span both bolts. Solid 20x20 envelope, channels omitted.
|
|
for side in [-1,1]:
|
|
for y in [-p,p]:
|
|
arm=corner(p,y,lift,side)
|
|
arms.append(add(doc,'Corner%d'%len(arms),'2020 corner L slotted to motor',arm,(.84,.47,.13)))
|
|
if lift:
|
|
sp=cylinder(4,lift,side*p,y,0).cut(cylinder(2.2,lift+.02,side*p,y,-.01))
|
|
spacers.append(add(doc,'Spacer%d'%len(spacers),'M4 spacer 7 mm assumed',sp,(.7,.7,.7)))
|
|
# M4 fastener shank extends into motor and through spacer+leg.
|
|
screw=cylinder(1.95,lift+8,side*p,y,-5)
|
|
head=cylinder(3.5,2.5,side*p,y,lift+3)
|
|
add(doc,'MountM4_%d'%len(arms),'Motor M4 screw envelope schematic',screw.fuse(head),(.8,.8,.8))
|
|
x=p-6.0+20
|
|
box=Part.makeBox(20,110,20,V(x,-55,lift))
|
|
if side<0:box=box.mirror(V(),V(1,0,0))
|
|
rails.append(add(doc,'Rail%d'%len(rails),'2020 solid envelope length 110 assumed',box,(.55,.56,.6)))
|
|
doc.recompute()
|
|
if any(o.Shape.isNull() or not o.Shape.isValid() for o in doc.Objects if hasattr(o,'Shape')):
|
|
raise RuntimeError('Invalid shape')
|
|
checks=[]; intersections=[]
|
|
targets=[objects['Bracket'],objects['PCB'],objects['Chip'],objects['Holder'],objects['Magnet']]+encoderheads+[objects['Shim%d'%i] for i in range(4)]+[objects['PCBHead%d'%i] for i in range(1,4)]+[objects['Motor']]
|
|
for i,c in enumerate(arms+rails):
|
|
for t in targets:
|
|
if not c.Shape.BoundBox.intersect(t.Shape.BoundBox): continue
|
|
intersection=c.Shape.common(t.Shape); volume=intersection.Volume
|
|
if volume>0.0001:
|
|
checks.append((c.Name,t.Name,volume)); intersections.append((c.Name,t.Name,intersection))
|
|
# also compare other corner angles and rails, excluding touching intended faces
|
|
for i,a in enumerate(arms):
|
|
for b in arms[i+1:]+rails:
|
|
if not a.Shape.BoundBox.intersect(b.Shape.BoundBox):continue
|
|
vol=a.Shape.common(b.Shape).Volume
|
|
if vol>.0001:checks.append((a.Name,b.Name,vol))
|
|
report=['Layout: 4 corner brackets and 20x20 solid profile envelopes; lift %.1fmm.'%lift,
|
|
'PCD76=45deg, oval width 5.5mm, slot offset +2.75mm outward, profile length 110mm all assumed.',
|
|
'M4 shank assumed 3.9mm: extreme position leaves 0.05mm radial clearance.',
|
|
'Encoder M4 head diameter 7mm height 3mm, 0.5mm bracket shim assumed.',
|
|
'Positive-volume collisions (>0.0001mm3):']
|
|
report += ['%s vs %s: %.5f mm3'%x for x in checks] or ['none in the checked part pairs']
|
|
(OUT/(title+'_check.txt')).write_text('\n'.join(report)+'\n',encoding='utf8')
|
|
for i,(a,b,shape) in enumerate(intersections):
|
|
add(doc,'Interference%d'%i,'INTERFERENCE %s / %s'%(a,b),shape,(1.,0.,0.))
|
|
# Semi-transparent corner brackets reveal separately selectable collision solids.
|
|
for arm in arms: arm.ViewObject.Transparency=30
|
|
doc.recompute()
|
|
view=G.activeDocument().activeView()
|
|
def render(mode,name):
|
|
if mode=='top': view.viewTop()
|
|
else: view.getCameraNode().orientation.setValue(coin.SbRotation(0.880476,0.115917,-0.279848,-0.364705))
|
|
G.updateGui();time.sleep(.5);G.updateGui();view.fitAll();G.updateGui();time.sleep(.5);G.updateGui()
|
|
view.saveImage(str(OUT/name),1600,1200,'White')
|
|
render('top',title+'_rear.png')
|
|
render('axon',title+'_axon.png')
|
|
# Focus screenshot: only one corner, original blue bracket and red
|
|
# exact intersection solids are visible. No objects are repositioned.
|
|
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
|
|
objects['Bracket'].ViewObject.Visibility=True
|
|
arms[0].ViewObject.Visibility=True
|
|
encoderheads[2].ViewObject.Visibility=True
|
|
for o in doc.Objects:
|
|
if o.Name.startswith('Interference') and 'Corner0 /' in o.Label: o.ViewObject.Visibility=True
|
|
render('top',title+'_detail.png')
|
|
for o in doc.Objects:
|
|
if hasattr(o,'Shape'): o.ViewObject.Visibility=states[o.Name]
|
|
render('top',title+'_rear.png')
|
|
doc.recompute();doc.saveAs(str(OUT/(title+'.FCStd')))
|
|
Import.export([o for o in doc.Objects if hasattr(o,'Shape')],str(OUT/(title+'.step')))
|
|
A.closeDocument(doc.Name)
|
|
return checks
|
|
|
|
try:
|
|
c1=run('Direct',0)
|
|
(OUT/'BUILD_OK.txt').write_text('success\n',encoding='utf8')
|
|
except Exception as ex:
|
|
import traceback
|
|
(OUT/'BUILD_ERROR.txt').write_text(traceback.format_exc(),encoding='utf8')
|
|
raise
|