104 lines
6.0 KiB
Plaintext
104 lines
6.0 KiB
Plaintext
from pathlib import Path
|
|
import os, math, json, time, traceback
|
|
import FreeCAD as A, FreeCADGui as G, Part, Import
|
|
V = A.Vector
|
|
root = Path(r'C:\projects\wheel')
|
|
out = root / 'models' / 'fan_direct_145'
|
|
out.mkdir(parents=True, exist_ok=True)
|
|
try:
|
|
d = A.openDocument(str(root / 'models' / 'integer_profiles' / 'Assembly.FCStd'))
|
|
d.recompute()
|
|
rails = [d.getObject('Rail0'), d.getObject('Rail1')]
|
|
xs = [(o.Shape.BoundBox.XMin + o.Shape.BoundBox.XMax) / 2 for o in rails]
|
|
spacing = xs[1] - xs[0]
|
|
theta = math.degrees(math.acos(spacing / (105.0 * math.sqrt(2.0)))) - 45.0
|
|
corners_before = [d.getObject('MotorBaseCorner%d' % i).Shape.copy() for i in range(2)]
|
|
old_rails = [o.Shape.copy() for o in rails]
|
|
for i, o in enumerate(rails):
|
|
o.Placement = A.Placement()
|
|
o.Shape = Part.makeBox(20, 145, 20, V(xs[i] - 10, -55, 0))
|
|
o.Label = '2020 motor profile - 145 mm'
|
|
d.recompute()
|
|
src = Part.makeBox(120, 120, 25, V(-60, -60, 20)).cut(Part.makeCylinder(56, 25.04, V(0,0,19.98)))
|
|
for x in (-52.5, 52.5):
|
|
for y in (-52.5, 52.5):
|
|
src = src.cut(Part.makeCylinder(2.2, 25.04, V(x,y,19.98)))
|
|
fan = src.removeSplitter()
|
|
fan.rotate(V(0,0,0), V(0,0,1), theta)
|
|
fan.translate(V(0,28,0))
|
|
assert fan.isValid() and len(fan.Solids) == 1
|
|
original = [o for o in d.Objects if hasattr(o,'Shape') and not o.Shape.isNull() and o.ViewObject.Visibility]
|
|
fan_hits = []
|
|
for o in original:
|
|
if fan.BoundBox.intersect(o.Shape.BoundBox):
|
|
volume = fan.common(o.Shape).Volume
|
|
if volume > 1e-4: fan_hits.append([o.Name, volume])
|
|
assert not fan_hits, 'fan collisions: ' + str(fan_hits)
|
|
extra_hits = []
|
|
for i, rail in enumerate(rails):
|
|
extra = rail.Shape.cut(old_rails[i])
|
|
for o in original:
|
|
if o == rail or o in rails: continue
|
|
if extra.BoundBox.intersect(o.Shape.BoundBox):
|
|
volume = extra.common(o.Shape).Volume
|
|
if volume > 1e-4: extra_hits.append([rail.Name, o.Name, volume])
|
|
assert old_rails[i].cut(rail.Shape).Volume < 1e-5
|
|
assert not extra_hits, 'rail extension collisions: ' + str(extra_hits)
|
|
for i in range(2):
|
|
assert corners_before[i].cut(d.getObject('MotorBaseCorner%d' % i).Shape).Volume < 1e-7
|
|
assert d.getObject('MotorBaseCorner%d' % i).Shape.cut(corners_before[i]).Volume < 1e-7
|
|
o = d.addObject('PartDesign::Feature', 'DirectFan')
|
|
o.Label = '120 mm fan envelope - rotated, two diagonal fixings; user fan assumed'
|
|
o.Shape = fan
|
|
o.ViewObject.ShapeColor = (.22,.24,.27)
|
|
o.ViewObject.Transparency = 18
|
|
alpha = math.radians(theta)
|
|
fasteners=[]
|
|
holes=[]
|
|
for sign in (-1,1):
|
|
px = sign * 52.5 * (math.cos(alpha) - math.sin(alpha))
|
|
py = 28 + sign * 52.5 * (math.cos(alpha) + math.sin(alpha))
|
|
idx=0 if sign<0 else 1
|
|
assert abs(px-xs[idx]) < 1e-6
|
|
assert -55 < py < 90
|
|
shaft=Part.makeCylinder(2,25,V(px,py,20))
|
|
head=Part.makeCylinder(3.5,3,V(px,py,45))
|
|
screw=shaft.fuse(head).removeSplitter()
|
|
assert screw.common(fan).Volume < 1e-5
|
|
f=d.addObject('PartDesign::Feature','FanBolt'+str(idx))
|
|
f.Shape=screw; f.Label='Illustrative M4 fastener, nut not modeled'; f.ViewObject.ShapeColor=(.65,.66,.68)
|
|
fasteners.append(f)
|
|
holes.append({'x':px,'y':py,'rail_center_x':xs[idx], 'distance_to_end_mm': min(py+55,90-py)})
|
|
d.recompute()
|
|
for obj in [o]+fasteners:
|
|
assert not obj.Shape.isNull() and obj.Shape.isValid()
|
|
d.Label='120 mm fan on 145 mm profiles - 2 diagonal screws, no bracket'
|
|
d.saveAs(str(out/'Assembly.FCStd'))
|
|
visible=[a for a in d.Objects if hasattr(a,'Shape') and not a.Shape.isNull() and a.ViewObject.Visibility]
|
|
Import.export(visible, str(out/'Assembly.step'))
|
|
view=G.activeDocument().activeView(); view.viewAxonometric(); G.updateGui();view.fitAll();time.sleep(.5);G.updateGui()
|
|
view.saveImage(str(out/'assembly.png'),1800,1300,'White')
|
|
vis={a.Name:a.ViewObject.Visibility for a in d.Objects if hasattr(a,'Shape')}
|
|
names={'Rail0','Rail1','NewRail0','NewRail1','MotorBaseCorner0','MotorBaseCorner1','DirectFan','FanBolt0','FanBolt1','Motor','OpenPillarBracket','Holder','Magnet','PCB','Chip'}
|
|
for a in d.Objects:
|
|
if hasattr(a,'Shape'):a.ViewObject.Visibility=a.Name in names
|
|
view.viewTop();G.updateGui();view.fitAll();time.sleep(.5);G.updateGui();view.saveImage(str(out/'fan_top.png'),1800,1300,'White')
|
|
view.viewAxonometric();G.updateGui();view.fitAll();time.sleep(.5);G.updateGui();view.saveImage(str(out/'fan_detail.png'),1800,1300,'White')
|
|
for a in d.Objects:
|
|
if hasattr(a,'Shape'):a.ViewObject.Visibility=vis.get(a.Name,False)
|
|
d.recompute();d.saveAs(str(out/'Assembly.FCStd'))
|
|
expected=[a.Name for a in visible]
|
|
A.closeDocument(d.Name)
|
|
d2=A.openDocument(str(out/'Assembly.FCStd'));d2.recompute()
|
|
assert all(d2.getObject(x) and d2.getObject(x).Shape.isValid() for x in ['Rail0','Rail1','DirectFan','FanBolt0','FanBolt1'])
|
|
assert all(abs(d2.getObject('Rail%d'%i).Shape.BoundBox.YLength-145)<1e-7 for i in (0,1))
|
|
st=Part.read(str(out/'Assembly.step'));assert st.isValid() and len(st.Solids)>0
|
|
data={'motor_2020_length_mm':145,'2020_longitudinal_limits_mm':[-55,90], 'fan_envelope_mm':[120,120,25], 'fan_mount_pitch_mm':105,'fan_center_y_mm':28,'motor_center_y_mm':10,'fan_offset_from_motor_mm':18,'rotation_deg':theta,'fastener_centers':holes,'fan_collisions':fan_hits,'extension_collisions':extra_hits,'original_corners_unchanged':True,'fcstd_reopened':True,'step_valid':True,'assumptions':'Fan solid frame and holes schematic; no blade, hub, wires, real T-slot or nut. Shaft of screw only above extrusion surface.'}
|
|
(out/'validation.json').write_text(json.dumps(data,ensure_ascii=False,indent=2),encoding='utf8')
|
|
A.closeDocument(d2.Name)
|
|
(out/'DONE.txt').write_text('ok',encoding='utf8')
|
|
os._exit(0)
|
|
except Exception:
|
|
(out/'ERROR.txt').write_text(traceback.format_exc(),encoding='utf8')
|
|
os._exit(1)
|