Inline QC station: real 40x40 profiles, labelled investor render
User, 18 Oct 2026: use real 40x40 profiles, make a render with text pointing out the parts to show the investor; the ring lights looked like they were floating. - build_station.py: frame drawn from the real 8-slot 40 series section (profile_shape: slots, centre bore, corner cavities; 40x80 with centre cavity) and 24 cast angle brackets; top beams now butt between the uprights; label-camera posts moved beside the cameras so both ring lights clamp straight to them; top line-scan on a side post with visible camera plate and light hangers; slide strips on carrier angles, pulley axles, end-stop posts, presence-sensor brackets, backlight post, cabinet on base crosses, chute on two supports and clear of the rail, pneumatics rail, stack light on the back upright, touch screen on the front end upright. A connectivity check at the end lists any part that does not reach the floor through touching parts: none. - render_annotated.py (FreeCAD): front three-quarter view, camera backed off so nothing is clipped, no navigation cube or preselection, label positions projected from the camera and checked with marker renders. - annotate_render.py (Pillow): title, 16 labels in two columns, leaders assigned so none cross -> inline-qc-station-annotated.png. - render_station.py: same camera and preselection fixes; all six views re-rendered. - BOM: 32 m of 40x40 in 45 pieces; total still USD 16k to 33k. - inline-optical-qc.md and README updated; .gitignore for the render intermediates.
This commit is contained in:
1 parent
609424b598
commit
e8674795f5
16 files changed
+483
-46
No files matched your search
@@ -5,8 +5,10 @@
|
||||
# It creates the document "InlineQCStation", saves inline-qc-station.FCStd next
|
||||
# to this script and leaves the view open for rendering (render_station.py).
|
||||
#
|
||||
# Concept, not a fabrication drawing: profiles are drawn as plain 40 mm bars,
|
||||
# bought parts as their envelope boxes. Sizes follow ../inline-optical-qc.md.
|
||||
# Concept, not a fabrication drawing: the frame uses the real 8-slot 40 series
|
||||
# section (profile_shape), bought parts are drawn as their envelope boxes. Sizes
|
||||
# follow ../inline-optical-qc.md. At the end the script lists any part that does
|
||||
# not reach the floor through parts it touches (panels not counted).
|
||||
#
|
||||
# Axes: x = record flow (infeed at x = 0, outfeed at x = L), y = across
|
||||
# (operator front at -y), z = up from the floor. All values in mm.
|
||||
@@ -107,8 +109,82 @@ def cyl(r, h, base, d=V(0, 0, 1)):
|
||||
return Part.makeCylinder(r, h, base, d)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- real slot profiles
|
||||
# 8-slot 40 series (item / Misumi HFS8 class): 8.2 mm slot opening, 4.3 mm lip,
|
||||
# undercut chamber 16 mm wide, slot depth 12 mm, centre bore 6.8 mm (M8 tap),
|
||||
# corner cavities, 0.8 mm corner chamfer. 40 x 80 adds a centre cavity.
|
||||
ALU_BRACKET = (0.60, 0.62, 0.65)
|
||||
_FACES = {}
|
||||
|
||||
|
||||
def _slot(cx, cy, nx, ny):
|
||||
td = [(-4.1, -1), (-4.1, 4.3), (-8.0, 4.3), (-8.0, 7.5), (-4.1, 12.0),
|
||||
(4.1, 12.0), (8.0, 7.5), (8.0, 4.3), (4.1, 4.3), (4.1, -1)]
|
||||
tx, ty = -ny, nx
|
||||
pts = [V(cx + t * tx + d * nx, cy + t * ty + d * ny, 0) for t, d in td]
|
||||
return Part.Face(Part.makePolygon(pts + [pts[0]]))
|
||||
|
||||
|
||||
def profile_face(a, b):
|
||||
"""Cross-section a (local X) by b (local Y), both multiples of 40, corner at the origin."""
|
||||
if (a, b) in _FACES:
|
||||
return _FACES[(a, b)]
|
||||
c = 0.8
|
||||
o = [V(c, 0, 0), V(a - c, 0, 0), V(a, c, 0), V(a, b - c, 0), V(a - c, b, 0), V(c, b, 0), V(0, b - c, 0), V(0, c, 0)]
|
||||
f = Part.Face(Part.makePolygon(o + [o[0]]))
|
||||
cuts = []
|
||||
for i in range(int(a // 40)):
|
||||
cuts += [_slot(20 + 40 * i, 0, 0, 1), _slot(20 + 40 * i, b, 0, -1)]
|
||||
for j in range(int(b // 40)):
|
||||
cuts += [_slot(0, 20 + 40 * j, 1, 0), _slot(a, 20 + 40 * j, -1, 0)]
|
||||
for i in range(int(a // 40)):
|
||||
for j in range(int(b // 40)):
|
||||
cuts.append(Part.Face(Part.Wire(Part.makeCircle(3.4, V(20 + 40 * i, 20 + 40 * j, 0)))))
|
||||
for cx, cy in [(5.4, 5.4), (a - 5.4, 5.4), (a - 5.4, b - 5.4), (5.4, b - 5.4)]:
|
||||
cuts.append(Part.Face(Part.Wire(Part.makeCircle(2.2, V(cx, cy, 0)))))
|
||||
if a == 40 and b == 80:
|
||||
cuts.append(Part.Face(Part.makePolygon([V(14, 33, 0), V(26, 33, 0), V(26, 47, 0), V(14, 47, 0), V(14, 33, 0)])))
|
||||
if a == 80 and b == 40:
|
||||
cuts.append(Part.Face(Part.makePolygon([V(33, 14, 0), V(47, 14, 0), V(47, 26, 0), V(33, 26, 0), V(33, 14, 0)])))
|
||||
for k in cuts:
|
||||
f = f.cut(k)
|
||||
_FACES[(a, b)] = f
|
||||
return f
|
||||
|
||||
|
||||
def profile_shape(x0, y0, z0, x1, y1, z1):
|
||||
"""A real slot profile filling the axis-aligned bar x0..x1, y0..y1, z0..z1."""
|
||||
dx, dy, dz = x1 - x0, y1 - y0, z1 - z0
|
||||
long_axis = max((dx, "x"), (dy, "y"), (dz, "z"))[1]
|
||||
if long_axis == "x": # local X -> Y, local Y -> Z, extrusion -> X
|
||||
a, b, n = dy, dz, dx
|
||||
m = App.Matrix(0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1)
|
||||
elif long_axis == "y": # local X -> Z, local Y -> X, extrusion -> Y
|
||||
a, b, n = dz, dx, dy
|
||||
m = App.Matrix(0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1)
|
||||
else:
|
||||
a, b, n = dx, dy, dz
|
||||
m = App.Matrix()
|
||||
a, b = round(a), round(b)
|
||||
if a % 40 or b % 40:
|
||||
return box(x0, y0, z0, x1, y1, z1)
|
||||
sol = profile_face(a, b).extrude(V(0, 0, n))
|
||||
if long_axis != "z":
|
||||
sol.transformShape(m)
|
||||
sol.translate(V(x0, y0, z0))
|
||||
return sol
|
||||
|
||||
|
||||
def prof(group, name, x0, y0, z0, x1, y1, z1):
|
||||
return add(group, name, box(x0, y0, z0, x1, y1, z1), ALU)
|
||||
return add(group, name, profile_shape(x0, y0, z0, x1, y1, z1), ALU)
|
||||
|
||||
|
||||
def gusset(group, name, corner, d1, d2, w):
|
||||
"""Cast angle bracket in the inside corner: legs 32 mm along d1 and d2, 34 mm wide along w."""
|
||||
p0 = corner - w * 17
|
||||
pts = [p0, p0 + d1 * 32, p0 + d2 * 32, p0]
|
||||
tri = Part.Face(Part.makePolygon(pts))
|
||||
return add(group, name, tri.extrude(w * 34), ALU_BRACKET)
|
||||
|
||||
|
||||
def rot(shape, center, axis, deg):
|
||||
@@ -129,37 +205,54 @@ for i, (x, y) in enumerate([(20, -YF), (20, YF), (XT0 + 20, -YF), (XT0 + 20, YF)
|
||||
for s in (-1, 1):
|
||||
prof(G, f"BaseBeam{'F' if s < 0 else 'B'}", 0, s * YF - 20, 60, L, s * YF + 20, 140) # 40 x 80
|
||||
for x0, z1, nm in [(0, 870, "End0"), (XT0, 1500, "Tunnel0"), (XT1, 1500, "Tunnel1"),
|
||||
(L - P, 1080, "End1")]:
|
||||
(L - P, 1340 if s < 0 else 1080, "End1")]: # front end upright carries the touch screen
|
||||
prof(G, f"Upright{nm}{'F' if s < 0 else 'B'}", x0, s * YF - 20, 140, x0 + P, s * YF + 20, z1)
|
||||
prof(G, f"TopBeam{'F' if s < 0 else 'B'}", XT0, s * YF - 20, 1460, XT1 + P, s * YF + 20, 1500)
|
||||
prof(G, f"TopBeam{'F' if s < 0 else 'B'}", XT0 + P, s * YF - 20, 1460, XT1, s * YF + 20, 1500)
|
||||
prof(G, f"LowBeam{'F' if s < 0 else 'B'}", XT0 + P, s * YF - 20, 420, XT1, s * YF + 20, 460)
|
||||
prof(G, f"RejectBeam{'F' if s < 0 else 'B'}", XT1 + P, s * YF - 20, 1040, L - P, s * YF + 20, 1080)
|
||||
for x0, nm in [(0, "End0"), (XT0, "Tunnel0"), (XT1, "Tunnel1"), (L - P, "End1")]:
|
||||
prof(G, f"BaseCross{nm}", x0, -YF + 20, 100, x0 + P, YF - 20, 140)
|
||||
prof(G, f"ConvCross{nm}", x0, -YF + 20, 790, x0 + P, YF - 20, 830)
|
||||
# top bridge: label camera and top line-scan
|
||||
prof(G, "TopCrossLabel", X_LB - 20, -YF + 20, 1460, X_LB + 20, YF - 20, 1500)
|
||||
prof(G, "TopCrossLabel", X_LB + 70, -YF + 20, 1460, X_LB + 110, YF - 20, 1500)
|
||||
xt = sum(TOP_LINE) / 2
|
||||
prof(G, "TopCrossLineScan", xt - 20, -YF + 20, 1460, xt + 20, YF - 20, 1500)
|
||||
prof(G, "TopCrossLineScan", xt + 48, -YF + 20, 1460, xt + 88, YF - 20, 1500)
|
||||
prof(G, "TopCrossThermal", XT0 + P, -YF + 20, 1460, XT0 + P + 40, YF - 20, 1500)
|
||||
prof(G, "TopCrossBacklight", sum(BOT_LINE) / 2 - 38, -YF + 20, 1460, sum(BOT_LINE) / 2 + 2, YF - 20, 1500)
|
||||
# lower bridge: bottom label camera, bottom line-scan, chuck lift
|
||||
xb = sum(BOT_LINE) / 2
|
||||
prof(G, "LowCrossLabel", X_LB - 20, -YF + 20, 420, X_LB + 20, YF - 20, 460)
|
||||
prof(G, "LowCrossLabel", X_LB + 70, -YF + 20, 420, X_LB + 110, YF - 20, 460)
|
||||
prof(G, "LowCrossLineScan", xb - 20, -YF + 20, 420, xb + 20, YF - 20, 460)
|
||||
prof(G, "LowCrossChuck", X_IS - 20, -YF + 20, 420, X_IS + 20, YF - 20, 460)
|
||||
prof(G, "LowCrossBacklight", xt - 20, -YF + 20, 420, xt + 20, YF - 20, 460)
|
||||
prof(G, "BaseCrossCabinet0", 1200, -YF + 20, 100, 1240, YF - 20, 140)
|
||||
prof(G, "BaseCrossCabinet1", 1580, -YF + 20, 100, 1620, YF - 20, 140)
|
||||
prof(G, "RejectBridgeCross", X_RJ - 20, -YF + 20, 1040, X_RJ + 20, YF - 20, 1080)
|
||||
|
||||
# cast angle brackets in the visible frame corners
|
||||
EX, EZ, EY = V(1, 0, 0), V(0, 0, 1), V(0, 1, 0)
|
||||
_k = 0
|
||||
for s in (-1, 1):
|
||||
y = s * YF
|
||||
for x, dirs in [(P, (1,)), (XT0, (-1,)), (XT0 + P, (1,)), (XT1, (-1,)), (XT1 + P, (1,)), (L - P, (-1,))]:
|
||||
for d in dirs:
|
||||
gusset(G, f"Bracket{_k}", V(x, y, 140), EX * d, EZ, EY); _k += 1
|
||||
for x, d, z in [(XT0 + P, 1, 1460), (XT1, -1, 1460), (XT0 + P, 1, 420), (XT1, -1, 420),
|
||||
(XT1 + P, 1, 1040), (L - P, -1, 1040)]:
|
||||
gusset(G, f"Bracket{_k}", V(x, y, z), EX * d, EZ * -1, EY); _k += 1
|
||||
|
||||
# ---------------------------------------------------------------- conveyor
|
||||
G = "Conveyor"
|
||||
for s in (-1, 1):
|
||||
side = "F" if s < 0 else "B"
|
||||
prof(G, f"Rail{side}", 0, s * 180 - 20, 830, L, s * 180 + 20, 870)
|
||||
add(G, f"BeltSlide{side}", box(30, s * Y_BELT - 5, 880, L - 30, s * Y_BELT + 5, 894), UHMW)
|
||||
add(G, f"BeltSlideCarrier{side}", box(45, min(s * 140, s * 200), 870, L - 45, max(s * 140, s * 200), 880), DARK)
|
||||
add(G, f"BeltSlide{side}", box(45, s * Y_BELT - 5, 880, L - 45, s * Y_BELT + 5, 894), UHMW)
|
||||
add(G, f"BeltTop{side}", cyl(3, L - 40, V(20, s * Y_BELT, ZB - 3), V(1, 0, 0)), BELT)
|
||||
add(G, f"BeltReturn{side}", cyl(3, L - 40, V(20, s * Y_BELT, ZB - 49), V(1, 0, 0)), BELT)
|
||||
for x, nm in [(20, "Idler"), (L - 20, "Drive")]:
|
||||
add(G, f"Pulley{nm}{side}", cyl(23, 14, V(x, s * Y_BELT - 7, ZB - 26), V(0, 1, 0)), DARK)
|
||||
add(G, f"PulleyAxle{nm}{side}", cyl(6, 6, V(x, min(s * (Y_BELT + 6), s * 160), ZB - 26), V(0, 1, 0)), PNEU)
|
||||
# infeed side guides with a flared mouth, PU strip on the rim height
|
||||
add(G, f"SideGuide{side}", box(110, s * 157 - 3, ZB + 2, XT0 - 10, s * 157 + 3, ZB + 14), UHMW)
|
||||
add(G, f"SideGuideFlare{side}", rot(box(40, s * 157 - 3, ZB + 2, 112, s * 157 + 3, ZB + 14),
|
||||
@@ -169,12 +262,15 @@ add(G, "DriveShaft", cyl(6, 2 * Y_BELT + 60, V(L - 20, -Y_BELT - 30, ZB - 26), V
|
||||
add(G, "DriveGearmotor", cyl(35, 150, V(L - 20, 210, ZB - 26), V(0, 1, 0))
|
||||
.fuse(box(L - 60, 200, ZB - 70, L + 15, 230, ZB + 15)), SERVO)
|
||||
add(G, "EndStopBar", box(L - 30, -200, ZB + 30, L - 12, 200, ZB + 48), DARK)
|
||||
for s in (-1, 1):
|
||||
add(G, f"EndStopPost{'F' if s < 0 else 'B'}", box(L - 30, min(s * 170, s * 190), 870, L - 12, max(s * 170, s * 190), ZB + 30), DARK)
|
||||
add(G, "EndStopBumper", box(L - 32, -40, ZB + 4, L - 26, 40, ZB + 30), (0.9, 0.5, 0.1))
|
||||
|
||||
# presence sensors (diffuse, looking up through the open centre) and the stop beam
|
||||
G = "Sensors"
|
||||
for x, nm in [(X_IN, "Infeed"), (X_LB - 120, "LabelTrigger"), (L - 120, "Outfeed")]:
|
||||
add(G, f"Presence{nm}", box(x - 15, -120, 850, x + 15, -100, 885), (0.15, 0.35, 0.75))
|
||||
add(G, f"PresenceBracket{nm}", box(x - 10, -160, 840, x + 10, -100, 850), DARK)
|
||||
xs = X_IS + R_REC
|
||||
for s in (-1, 1):
|
||||
add(G, f"StopBeam{'Tx' if s < 0 else 'Rx'}", box(xs - 10, min(s * 200, s * 222), 870, xs + 10, max(s * 200, s * 222), ZB + 25),
|
||||
@@ -187,15 +283,17 @@ add(G, "LabelCamTop", box(X_LB - 15, -15, ZB + WD_LABEL + 50, X_LB + 15, 15, ZB
|
||||
add(G, "LabelLensTop", cyl(17, 50, V(X_LB, 0, ZB + WD_LABEL)), LENS)
|
||||
add(G, "LabelRingTop", ring(70, 42, 22, V(X_LB, 0, ZB + 170)), LIGHTBODY)
|
||||
add(G, "LabelRingTopFace", ring(62, 48, 1, V(X_LB, 0, ZB + 169)), LIGHT)
|
||||
add(G, "LabelCamTopPost", box(X_LB - 20, 40, ZB + WD_LABEL + 50, X_LB + 20, 80, 1460), ALU)
|
||||
add(G, "LabelCamTopPlate", box(X_LB - 20, 15, ZB + WD_LABEL + 50, X_LB + 20, 40, ZB + WD_LABEL + 92), DARK)
|
||||
prof(G, "LabelCamTopPost", X_LB + 70, -20, ZB + 160, X_LB + 110, 20, 1460)
|
||||
add(G, "LabelCamTopPlate", box(X_LB + 15, -12, ZB + WD_LABEL + 55, X_LB + 70, 12, ZB + WD_LABEL + 87), DARK)
|
||||
add(G, "LabelRingTopClamp", box(X_LB + 58, -14, ZB + 166, X_LB + 70, 14, ZB + 196), DARK)
|
||||
zl = ZB - WD_LABEL
|
||||
add(G, "LabelCamBottom", box(X_LB - 15, -15, zl - 92, X_LB + 15, 15, zl - 50), CAM)
|
||||
add(G, "LabelLensBottom", cyl(17, 50, V(X_LB, 0, zl - 50)), LENS)
|
||||
add(G, "LabelRingBottom", ring(70, 42, 22, V(X_LB, 0, ZB - 80)), LIGHTBODY)
|
||||
add(G, "LabelRingBottomFace", ring(62, 48, 1, V(X_LB, 0, ZB - 58)), LIGHT)
|
||||
add(G, "LabelCamBottomPost", box(X_LB - 20, 40, 460, X_LB + 20, 80, zl - 50), ALU)
|
||||
add(G, "LabelCamBottomPlate", box(X_LB - 20, 15, zl - 92, X_LB + 20, 40, zl - 50), DARK)
|
||||
prof(G, "LabelCamBottomPost", X_LB + 70, -20, 460, X_LB + 110, 20, ZB - 48)
|
||||
add(G, "LabelCamBottomPlate", box(X_LB + 15, -12, zl - 87, X_LB + 70, 12, zl - 55), DARK)
|
||||
add(G, "LabelRingBottomClamp", box(X_LB + 58, -14, ZB - 84, X_LB + 70, 14, ZB - 54), DARK)
|
||||
|
||||
# ---------------------------------------------------------------- inspection: lift, clamp, one turn
|
||||
G = "Chuck"
|
||||
@@ -208,16 +306,16 @@ add(G, "SpindleServo", cyl(30, 110, V(X_IS, 110, zp - 242)), SERVO)
|
||||
add(G, "SpindleBeltCover", box(X_IS - 32, -32, zp - 120, X_IS + 32, 142, zp - 100), (0.3, 0.3, 0.32))
|
||||
add(G, "LiftCylinderRods", cyl(6, 100, V(X_IS - 30, 0, zp - 232)).fuse(cyl(6, 100, V(X_IS + 30, 0, zp - 232))), PNEU)
|
||||
add(G, "LiftCylinder", box(X_IS - 50, -25, zp - 322, X_IS + 50, 25, zp - 232), PNEU)
|
||||
add(G, "LiftPost", box(X_IS - 20, -20, 460, X_IS + 20, 20, zp - 322), ALU)
|
||||
prof(G, "LiftPost", X_IS - 20, -20, 460, X_IS + 20, 20, zp - 322)
|
||||
|
||||
G = "LineScanTop"
|
||||
zr = ZL + T_REC # top face of the lifted record
|
||||
add(G, "LineCamTop", box(xt - 31, -31, zr + WD_LINE + 60, xt + 31, 31, zr + WD_LINE + 105), CAM)
|
||||
add(G, "LineLensTop", cyl(25, 60, V(xt, 0, zr + WD_LINE)), LENS)
|
||||
add(G, "LineCamTopPost", box(xt - 20, 40, zr + 150, xt + 20, 80, 1460), ALU)
|
||||
add(G, "LightHangerTop", box(xt - 10, -112, zr + 130, xt + 10, 112, zr + 150), DARK)
|
||||
add(G, "CoaxHangerTop", box(xt - 10, 30, zr + 80, xt + 10, 40, zr + 130), DARK)
|
||||
add(G, "LineCamTopPlate", box(xt - 20, 31, zr + WD_LINE + 60, xt + 20, 40, zr + WD_LINE + 105), DARK)
|
||||
prof(G, "LineCamTopPost", xt + 48, -20, zr + 150, xt + 88, 20, 1460)
|
||||
add(G, "LightHangerTop", box(xt + 58, -112, zr + 130, xt + 78, 112, zr + 150), DARK)
|
||||
add(G, "CoaxHangerTop", box(xt + 58, -10, zr + 80, xt + 78, 10, zr + 130), DARK)
|
||||
add(G, "LineCamTopPlate", box(xt + 31, -20, zr + WD_LINE + 62, xt + 48, 20, zr + WD_LINE + 103), DARK)
|
||||
x0, x1 = TOP_LINE[0] - 5, TOP_LINE[1] + 5
|
||||
add(G, "CoaxLightTop", box(x0, -30, zr + 50, x1, 30, zr + 110), LIGHTBODY)
|
||||
add(G, "CoaxLightTopFace", box(x0 + 3, -26, zr + 49, x1 - 3, 26, zr + 50), LIGHT)
|
||||
@@ -225,20 +323,21 @@ for s in (-1, 1):
|
||||
b = box(x0, -15, 0, x1, 15, 30)
|
||||
b.translate(V(0, s * 100, zr + 15))
|
||||
add(G, f"DarkFieldTop{'F' if s < 0 else 'B'}", rot(b, V(0, s * 100, zr + 30), V(1, 0, 0), s * 20), LIGHTBODY)
|
||||
add(G, f"DarkFieldTopBracket{'F' if s < 0 else 'B'}", box(xt - 10, s * 100 - 5, zr + 48, xt + 10, s * 100 + 5, zr + 130), DARK)
|
||||
add(G, f"DarkFieldTopBracket{'F' if s < 0 else 'B'}", box(xt + 58, s * 100 - 5, zr + 48, xt + 78, s * 100 + 5, zr + 130), DARK)
|
||||
bx0, bx1 = BOT_LINE[0] - 5, BOT_LINE[1] + 5
|
||||
add(G, "BacklightForBottomCam", box(bx0, -20, zr + 30, bx1, 20, zr + 52), LIGHTBODY)
|
||||
add(G, "BacklightForBottomCamFace", box(bx0 + 3, -17, zr + 29, bx1 - 3, 17, zr + 30), LIGHT)
|
||||
add(G, "BacklightBracketTop", box(xb - 30, -5, zr + 52, xb - 10, 5, 1460), DARK)
|
||||
add(G, "ScanLineTop", box(TOP_LINE[0], -0.6, zr + 0.05, TOP_LINE[1], 0.6, zr + 0.5), LASER)
|
||||
add(G, "ThermalPost", box(XT0 + 90, -240, 1430, XT0 + 110, -220, 1460), DARK)
|
||||
add(G, "ThermalPost", box(XT0 + 60, -240, 1400, XT0 + 80, -220, 1460), DARK)
|
||||
add(G, "ThermalCam", rot(box(XT0 + 70, -260, 1380, XT0 + 130, -200, 1440), V(XT0 + 100, -230, 1410), V(1, 0, 0), -30), (0.95, 0.45, 0.10))
|
||||
|
||||
G = "LineScanBottom"
|
||||
zu = ZL # underside of the lifted record
|
||||
add(G, "LineCamBottom", box(xb - 31, -31, zu - WD_LINE - 105, xb + 31, 31, zu - WD_LINE - 60), CAM)
|
||||
add(G, "LineLensBottom", cyl(25, 60, V(xb, 0, zu - WD_LINE - 60)), LENS)
|
||||
add(G, "LineCamBottomPost", box(xb - 20, 40, 460, xb + 20, 80, zu - WD_LINE - 60), ALU)
|
||||
prof(G, "LineCamBottomPost", xb - 20, 40, 460, xb + 20, 80, ZB - 20)
|
||||
add(G, "CoaxHangerBottom", box(xb - 10, 30, ZB - 70, xb + 10, 40, ZB - 30), DARK)
|
||||
add(G, "LineCamBottomPlate", box(xb - 20, 31, zu - WD_LINE - 105, xb + 20, 40, zu - WD_LINE - 60), DARK)
|
||||
add(G, "CoaxLightBottom", box(bx0, -30, ZB - 80, bx1, 30, ZB - 20), LIGHTBODY)
|
||||
add(G, "CoaxLightBottomFace", box(bx0 + 3, -26, ZB - 20, bx1 - 3, 26, ZB - 19), LIGHT)
|
||||
@@ -248,6 +347,7 @@ for s in (-1, 1):
|
||||
add(G, f"DarkFieldBottom{'F' if s < 0 else 'B'}", rot(b, V(0, s * 175, zu - 25), V(1, 0, 0), -s * 8), LIGHTBODY)
|
||||
add(G, f"DarkFieldBottomBracket{'F' if s < 0 else 'B'}", box(bx0 + 40, s * 192 - 5, 870, bx0 + 60, s * 192 + 5, zu - 35), DARK)
|
||||
add(G, "BacklightForTopCam", box(x0, -20, ZB - 40, x1, 20, ZB - 15), LIGHTBODY)
|
||||
prof(G, "BacklightPost", xt - 20, -20, 460, xt + 20, 20, ZB - 40)
|
||||
add(G, "BacklightForTopCamFace", box(x0 + 3, -17, ZB - 15, x1 - 3, 17, ZB - 14), LIGHT)
|
||||
add(G, "ScanLineBottom", box(BOT_LINE[0], -0.6, zu - 0.5, BOT_LINE[1], 0.6, zu - 0.05), LASER)
|
||||
|
||||
@@ -256,7 +356,7 @@ for r in (90, 130):
|
||||
add(G, f"Laser_r{r}", box(X_IS - 23, -r - 10, ZB - 55, X_IS + 23, -r + 10, ZB - 15), (0.15, 0.35, 0.75))
|
||||
add(G, f"LaserBeam_r{r}", cyl(0.8, zu - (ZB - 15), V(X_IS, -r, ZB - 15)), LASER, 30)
|
||||
add(G, f"LaserBracket_r{r}", box(X_IS - 10, -r - 10, 830, X_IS + 10, -r + 10, ZB - 55), DARK)
|
||||
add(G, "LaserRail", box(X_IS - 20, -200, 810, X_IS + 20, -70, 830), ALU)
|
||||
prof(G, "LaserRail", X_IS - 20, -200, 790, X_IS + 20, -70, 830)
|
||||
|
||||
# ---------------------------------------------------------------- reject
|
||||
G = "Reject"
|
||||
@@ -266,15 +366,20 @@ add(G, "PusherCarriage", box(X_RJ - 35, yc - 30, 960, X_RJ + 35, yc + 30, 1000),
|
||||
add(G, "PusherArm", box(X_RJ - 10, yc - 10, ZB + 26, X_RJ + 10, yc + 10, 960), DARK)
|
||||
add(G, "PusherPaddle", box(X_RJ - 100, yc + 10, ZB + 4, X_RJ + 100, yc + 22, ZB + 26), (0.9, 0.5, 0.1))
|
||||
CX0, CX1 = X_RJ - 170, X_RJ + 170
|
||||
Y_CH, Z_CH = 205, ZB - 7 # chute top edge: just outside the back rail, below the belt plane
|
||||
chute = box(CX0, 0, 0, CX1, 360, 6)
|
||||
chute = rot(chute, V(0, 0, 0), V(1, 0, 0), -42)
|
||||
chute.translate(V(0, 165, ZB - 12))
|
||||
chute.translate(V(0, Y_CH, Z_CH))
|
||||
add(G, "RejectChute", chute, (0.55, 0.57, 0.60))
|
||||
for xw in (CX0 - 6, CX1):
|
||||
w = box(xw, 0, 0, xw + 6, 360, 80)
|
||||
w = rot(w, V(0, 0, 0), V(1, 0, 0), -42)
|
||||
w.translate(V(0, 165, ZB - 12))
|
||||
w.translate(V(0, Y_CH, Z_CH))
|
||||
add(G, f"ChuteWall{xw}", w, (0.55, 0.57, 0.60))
|
||||
import math
|
||||
_zc = Z_CH - math.tan(math.radians(42)) * (YF + 20 - Y_CH) # chute underside above the back frame line
|
||||
for i, xs_ in enumerate((CX0 + 10, CX1 - 50)):
|
||||
prof(G, f"ChuteSupport{i}", xs_, YF - 20, 140, xs_ + 40, YF + 20, math.floor(_zc))
|
||||
binb = box(CX0 - 20, 380, 0, CX1 + 20, 720, 560).cut(box(CX0 - 8, 392, 12, CX1 + 8, 708, 600))
|
||||
add(G, "RejectBin", binb, (0.85, 0.20, 0.15), 20)
|
||||
|
||||
@@ -299,17 +404,18 @@ add(G, "GuardRejectTop", box(XT1 + P, -YF - 23, 1100, L, YF + 23, 1103), CLEAR,
|
||||
|
||||
# ---------------------------------------------------------------- electrical and operator
|
||||
G = "Electrical"
|
||||
add(G, "Cabinet", box(1180, -200, 160, 1640, 170, 770), CABINET)
|
||||
add(G, "Cabinet", box(1180, -200, 140, 1640, 170, 750), CABINET)
|
||||
add(G, "CabinetHandle", box(1600, -215, 400, 1615, -200, 520), DARK)
|
||||
add(G, "StackLightPole", cyl(10, 120, V(XT1 + 20, -YF, 1503)), DARK)
|
||||
add(G, "StackLightPole", cyl(10, 123, V(XT1 + 20, YF, 1500)), DARK)
|
||||
for i, c in enumerate([GREEN, AMBER, RED]):
|
||||
add(G, f"StackLight{i}", cyl(25, 55, V(XT1 + 20, -YF, 1623 + i * 57)), c)
|
||||
add(G, "HMIArm", box(XT1 + 50, -YF - 80, 1200, XT1 + 70, -YF - 20, 1220), DARK)
|
||||
add(G, "HMI", rot(box(XT1 + 70, -YF - 100, 1130, XT1 + 330, -YF - 80, 1300), V(XT1 + 200, -YF - 90, 1215), V(1, 0, 0), -15), (0.10, 0.10, 0.12))
|
||||
add(G, "EStopBox", box(XT1 + 60, -YF - 60, 930, XT1 + 140, -YF - 20, 1010), YELLOW)
|
||||
add(G, "EStop", cyl(22, 25, V(XT1 + 100, -YF - 60, 970), V(0, -1, 0)), RED)
|
||||
add(G, "ValveTerminal", box(XT0 + 80, YF + 25, 300, XT0 + 330, YF + 75, 380), PNEU)
|
||||
add(G, "FRL", box(XT0 + 360, YF + 25, 280, XT0 + 440, YF + 85, 420), (0.2, 0.4, 0.8))
|
||||
add(G, f"StackLight{i}", cyl(25, 57, V(XT1 + 20, YF, 1623 + i * 57)), c)
|
||||
add(G, "HMIArm", box(L - 40, -YF - 80, 1225, L, -YF - 20, 1245), DARK)
|
||||
add(G, "HMI", rot(box(L - 260, -YF - 100, 1150, L, -YF - 80, 1320), V(L - 130, -YF - 90, 1235), V(1, 0, 0), -15), (0.10, 0.10, 0.12))
|
||||
add(G, "EStopBox", box(XT1, -YF - 60, 930, XT1 + 80, -YF - 20, 1010), YELLOW)
|
||||
add(G, "EStop", cyl(22, 25, V(XT1 + 40, -YF - 60, 970), V(0, -1, 0)), RED)
|
||||
add(G, "ValveTerminal", box(XT0 + 80, YF + 20, 300, XT0 + 330, YF + 70, 380), PNEU)
|
||||
add(G, "FRL", box(XT0 + 360, YF + 20, 280, XT0 + 440, YF + 80, 420), (0.2, 0.4, 0.8))
|
||||
prof(G, "PneumaticsRail", XT0 + P, YF - 20, 260, XT1, YF + 20, 300)
|
||||
|
||||
# ---------------------------------------------------------------- records
|
||||
G = "Records"
|
||||
@@ -331,9 +437,9 @@ record("RecordInfeed", X_IN, 0, ZB, (0.95, 0.85, 0.75), body=(0.80, 0.12, 0.16),
|
||||
record("RecordInspection", X_IS, 0, ZL, (0.20, 0.45, 0.80))
|
||||
record("RecordOutfeed", X_OUT, 0, ZB, (0.95, 0.60, 0.15))
|
||||
import math
|
||||
sc, a = 230, math.radians(42)
|
||||
yr = 165 + math.cos(a) * sc + 6 * math.sin(a)
|
||||
zr2 = ZB - 12 - math.sin(a) * sc + 6 * math.cos(a)
|
||||
sc, a = 200, math.radians(42)
|
||||
yr = Y_CH + math.cos(a) * sc + 6 * math.sin(a)
|
||||
zr2 = Z_CH - math.sin(a) * sc + 6 * math.cos(a)
|
||||
record("RecordReject", X_RJ, yr, zr2, (0.6, 0.6, 0.6), tilt=(V(X_RJ, yr, zr2), V(1, 0, 0), -42))
|
||||
|
||||
doc.recompute()
|
||||
@@ -345,3 +451,30 @@ for o in doc.Objects:
|
||||
bb = o.Shape.BoundBox if bb is None else bb.united(o.Shape.BoundBox)
|
||||
print("objects", n, "bbox", round(bb.XMin), round(bb.XMax), round(bb.YMin), round(bb.YMax), round(bb.ZMin), round(bb.ZMax))
|
||||
print("saved", OUT)
|
||||
|
||||
# every part should touch another one, without counting the panels (hidden in the open views);
|
||||
# list the ones that do not (gap > 1 mm)
|
||||
_panels = set(o.Name for o in groups["Enclosure"].Group)
|
||||
_parts = [o for o in doc.Objects if o.TypeId == "Part::Feature" and o.Name not in _panels]
|
||||
# connectivity: every part must reach the floor through touching parts (gap <= 1 mm)
|
||||
_adj = {o.Name: set() for o in _parts}
|
||||
for ia, oa in enumerate(_parts):
|
||||
ba = oa.Shape.BoundBox
|
||||
for ob in _parts[ia + 1:]:
|
||||
bq = ob.Shape.BoundBox
|
||||
if (bq.XMin > ba.XMax + 2 or bq.XMax < ba.XMin - 2 or bq.YMin > ba.YMax + 2 or bq.YMax < ba.YMin - 2
|
||||
or bq.ZMin > ba.ZMax + 2 or bq.ZMax < ba.ZMin - 2):
|
||||
continue
|
||||
if oa.Shape.distToShape(ob.Shape)[0] <= 1.0:
|
||||
_adj[oa.Name].add(ob.Name)
|
||||
_adj[ob.Name].add(oa.Name)
|
||||
_seen = [o.Name for o in _parts if o.Shape.BoundBox.ZMin < 1]
|
||||
_todo = list(_seen)
|
||||
_seen = set(_seen)
|
||||
while _todo:
|
||||
for nb in _adj[_todo.pop()]:
|
||||
if nb not in _seen:
|
||||
_seen.add(nb)
|
||||
_todo.append(nb)
|
||||
_floating = sorted(n for n in _adj if n not in _seen)
|
||||
print("parts not connected to the floor:", _floating if _floating else "none")
|
||||
Reference in new issue
Block a user