建立方向盘项目初始版本
This commit is contained in:
165
scripts/build_shared76_outward_check.py
Normal file
165
scripts/build_shared76_outward_check.py
Normal file
@@ -0,0 +1,165 @@
|
||||
"""Inspect outboard 2020 brackets without changing the existing 76 mm parts.
|
||||
|
||||
Run as a GUI macro via rebuild_shared76_outward_check.FCMacro. All fastener,
|
||||
slot, phase and motor data remain the documented first-fit assumptions.
|
||||
"""
|
||||
from pathlib import Path
|
||||
import math, time, traceback
|
||||
import FreeCAD as A
|
||||
import FreeCADGui as G
|
||||
import Part, Import
|
||||
from pivy import coin
|
||||
|
||||
R = Path(__file__).resolve().parent.parent
|
||||
O = R / 'models' / 'shared76_outward_check'
|
||||
O.mkdir(parents=True, exist_ok=True)
|
||||
V = A.Vector
|
||||
|
||||
def cylinder(r, h, x, y, z):
|
||||
return Part.makeCylinder(r, h, V(x,y,z))
|
||||
|
||||
def valid(shape):
|
||||
return not shape.isNull() and shape.isValid()
|
||||
|
||||
def item(doc, name, label, shape, color, transparent=0):
|
||||
obj = doc.addObject('PartDesign::Feature', name)
|
||||
obj.Label = label
|
||||
obj.Shape = shape
|
||||
obj.ViewObject.ShapeColor = color
|
||||
obj.ViewObject.LineColor = (0.1,0.1,0.1)
|
||||
obj.ViewObject.Transparency = transparent
|
||||
return obj
|
||||
|
||||
try:
|
||||
source = A.openDocument(str(R/'models'/'encoder_shared76_print_v1'/'Shared76_Assembly.FCStd'))
|
||||
objects = [o for o in source.Objects if hasattr(o, 'Shape') and not o.Shape.isNull()]
|
||||
original = {o.Name:o.Shape.copy() for o in objects}
|
||||
colors = {o.Name:tuple(o.ViewObject.ShapeColor) for o in objects}
|
||||
transparencies = {o.Name:o.ViewObject.Transparency for o in objects}
|
||||
labels = {o.Name:o.Label for o in objects}
|
||||
A.closeDocument(source.Name)
|
||||
names = list(original)
|
||||
corners = [n for n in names if n.startswith('Corner')]
|
||||
rails = [n for n in names if n.startswith('Rail')]
|
||||
frame_name = 'EncoderBracket'
|
||||
if len(corners)!=4 or len(rails)!=2 or frame_name not in original:
|
||||
raise RuntimeError('Expected existing components were not found: ' + str(names))
|
||||
|
||||
# M4 shaft 3.9 assumed; 9.5x5.5 oval gives 4mm straight travel and
|
||||
# 0.8mm radial clearance; 2.75mm shift leaves ~0.05mm at the slot end.
|
||||
shifted = {}
|
||||
for name in corners+rails:
|
||||
shape = original[name].copy()
|
||||
x = (shape.BoundBox.XMin+shape.BoundBox.XMax)/2
|
||||
direction = -1 if x < 0 else 1
|
||||
shape.translate(V(direction*2.75,0,0))
|
||||
shifted[name] = shape
|
||||
p50 = 25/math.sqrt(2)
|
||||
heads = []
|
||||
tools = []
|
||||
centers=[]
|
||||
for side in (-1,1):
|
||||
for ysign in (-1,1):
|
||||
x, y = side*p50, ysign*p50
|
||||
centers.append((x,y))
|
||||
heads.append(cylinder(3.5,3,x,y,0))
|
||||
# Access envelope: 7.5mm diameter including screw head to lift it,
|
||||
# measured from the top of the head through the rear access side.
|
||||
tools.append(cylinder(3.75,22,x,y,3.01))
|
||||
|
||||
reports = ['ASSEMBLY STUDY, all dimensions mm.',
|
||||
'50 mm screws ARE NOT in the current product; heads are only assumed envelopes Ø7 x 3 at z=0..3.',
|
||||
'Corner/rail move ±2.75 in X, encoder, motor and 76 mm fixing studs unchanged.',
|
||||
'Oval slot 9.5x5.5, M4 male diameter 3.9 assumed; outer limit clearance 0.05.',
|
||||
'50 and 76 mm hole-circle angular phase 45 degrees is assumed.',
|
||||
'Screw head zero separation may be intended contact; positive volume indicates interference.',
|
||||
'50 mm head versus same-quadrant corner:']
|
||||
for i,(x,y) in enumerate(centers):
|
||||
matching = min(corners, key=lambda n: (((original[n].BoundBox.XMin+original[n].BoundBox.XMax)/2)-x)**2+(((original[n].BoundBox.YMin+original[n].BoundBox.YMax)/2)-y)**2)
|
||||
before=original[matching]; after=shifted[matching]; head=heads[i]
|
||||
reports.append('%s (%+.2f,%+.2f) before common=%.4f after common=%.4f mm3; after separation=%.4f mm' %
|
||||
(matching,x,y,before.common(head).Volume,after.common(head).Volume,after.distToShape(head)[0]))
|
||||
|
||||
fixed = [n for n in names if n not in corners+rails]
|
||||
collisions=[]
|
||||
for c in corners+rails:
|
||||
a=shifted[c]
|
||||
for n in fixed:
|
||||
b=original[n]
|
||||
if not a.BoundBox.intersect(b.BoundBox): continue
|
||||
volume=a.common(b).Volume
|
||||
if volume>.001:collisions.append('%s / %s: %.5f mm3'%(c,n,volume))
|
||||
for i,a_name in enumerate(corners):
|
||||
for b_name in corners[i+1:]+rails:
|
||||
a,b=shifted[a_name],shifted[b_name]
|
||||
if a.BoundBox.intersect(b.BoundBox):
|
||||
volume=a.common(b).Volume
|
||||
if volume>.001:collisions.append('%s / %s: %.5f mm3'%(a_name,b_name,volume))
|
||||
reports += ['Shifted corners and rails versus existing components (>0.001mm3):']+(collisions or ['none in checked pairs'])
|
||||
for i, tool in enumerate(tools):
|
||||
found=[]
|
||||
for name in [frame_name]+corners+rails+['PCB','Chip','Holder','Magnet']:
|
||||
sh=shifted.get(name,original[name])
|
||||
if tool.BoundBox.intersect(sh.BoundBox):
|
||||
vol=tool.common(sh).Volume
|
||||
if vol>.001:found.append('%s %.3fmm3'%(name,vol))
|
||||
reports.append('50 hole %d access envelope Ø7.5 from z=3.01..25.01: %s'%(i, ', '.join(found) if found else 'no checked obstruction'))
|
||||
|
||||
# Separate optional candidate, no mutation of existing printed STL:
|
||||
# Ø8 clearance holes allow the assumed Ø7 screw head to pass from behind.
|
||||
frame=original[frame_name].copy()
|
||||
for x,y in centers:
|
||||
frame=frame.cut(cylinder(4,3.24,x,y,11.28))
|
||||
frame=frame.removeSplitter()
|
||||
if not valid(frame) or len(frame.Solids)!=1:raise RuntimeError('access hole candidate not a valid single solid')
|
||||
candidate_hits=[]
|
||||
for i,tool in enumerate(tools):
|
||||
for name in [frame_name]+corners+rails+['PCB','Chip','Holder','Magnet']:
|
||||
sh=frame if name==frame_name else shifted.get(name,original[name])
|
||||
if tool.BoundBox.intersect(sh.BoundBox):
|
||||
vol=tool.common(sh).Volume
|
||||
if vol>.001:candidate_hits.append('hole%d/%s: %.3fmm3'%(i,name,vol))
|
||||
reports += ['OPTIONAL Ø8 access holes in encoder bracket, only a concept, NOT a released print file:',
|
||||
'Assumed 50 mm head Ø7; fastener head diameter, tool, screw, and wiring need physical verification.',
|
||||
'Ø7.5 access envelope candidate:'] + (candidate_hits or ['no checked obstruction'])
|
||||
(O/'geometry_check.txt').write_text('\n'.join(reports)+'\n',encoding='utf8')
|
||||
|
||||
def make_doc(name, use_candidate):
|
||||
doc=A.newDocument(name)
|
||||
colors2={'Motor':(.35,.35,.37),'EncoderBracket':(.16,.43,.85),'PCB':(.04,.65,.22),
|
||||
'Holder':(.98,.55,.10),'Magnet':(.8,.8,.8)}
|
||||
items=[]
|
||||
for n in names:
|
||||
shape=frame if n==frame_name and use_candidate else shifted.get(n,original[n])
|
||||
c=colors2.get(n, colors[n])
|
||||
items.append(item(doc,n,labels[n],shape,c,75 if n=='Motor' else transparencies[n]))
|
||||
# pink marks hypothetical 50-hole heads; omitted from STEP export by design.
|
||||
for i,head in enumerate(heads):
|
||||
items.append(item(doc,'Assumed50Head%d'%i,'50mm M4 head envelope ONLY - not installed',head,(.85,.15,.62)))
|
||||
doc.recompute()
|
||||
if any(not valid(o.Shape) for o in items):raise RuntimeError('Invalid object in '+name)
|
||||
G.activateWorkbench('PartWorkbench')
|
||||
view=G.activeDocument().activeView()
|
||||
def render(kind,path):
|
||||
if kind=='rear':view.viewTop()
|
||||
else:
|
||||
view.viewAxonometric();G.updateGui();time.sleep(.4)
|
||||
view.getCameraNode().orientation.setValue(coin.SbRotation(.880476,.115917,-.279848,-.364705))
|
||||
G.updateGui();time.sleep(.7);view.fitAll();G.updateGui();time.sleep(.7);G.updateGui()
|
||||
view.saveImage(str(O/path),1600,1200,'White')
|
||||
render('rear',name+'_rear.png')
|
||||
render('axon',name+'_axon.png')
|
||||
for ob in items:
|
||||
if ob.Name.startswith('Assumed50Head'):ob.ViewObject.Visibility=False
|
||||
render('axon',name+'_assembly.png')
|
||||
for ob in items:
|
||||
if ob.Name.startswith('Assumed50Head'):ob.ViewObject.Visibility=True
|
||||
doc.recompute();doc.saveAs(str(O/(name+'.FCStd')))
|
||||
Import.export([o for o in items if not o.Name.startswith('Assumed50Head')],str(O/(name+'.step')))
|
||||
A.closeDocument(doc.Name)
|
||||
make_doc('Outward',False)
|
||||
make_doc('ServiceHolesConcept',True)
|
||||
(O/'DONE.txt').write_text('success\n',encoding='utf8')
|
||||
except Exception:
|
||||
(O/'ERROR.txt').write_text(traceback.format_exc(),encoding='utf8')
|
||||
raise
|
||||
Reference in New Issue
Block a user