"""Build the robot cell after the user's picture (reference/ref_user.jpg), headless: blender.exe -b --factory-startup --python rc_build.py -> ../RobotCell.blend (rebuilt from scratch) The picture's camera was fitted to the cell's outer box (rc_fit.py: six corners, 3 px rms; rc_lib.py CAM). The scale is mine (the picture has none): the box is 2.40 m from the base rail's underside to the roof, on 0.09 m feet, which puts the control pod's buttons at 1.16 m and the emergency stop at 0.98 m. Through that camera the picture reads as: the box 3.05 x 2.88 m, 2.49 m high; corner posts 0.14 m, the front-right post 0.22 m deep on the side front a 0.49 m solid column on the left (the control pod on it), then two sliding glass leaves on a top track (the left one in front), the right post; header 2.37-2.49 m, leaves 0.32-2.30 m right side the post, a glass door 1.30 m wide hinged at its back edge (copper hinges), a solid panel back dark panels with a bolted rib and a louvre vent; its left 0.36 m is glass (the picture's back-left corner, the panel's edge and the ceiling's back edge land where the picture shows them) left side glass behind the column (seen through the left leaf in the picture) inside diamond-plate floor at 0.314 m, ceiling at 2.28 m with LED strips along the depth, a downlight robot on a black pedestal at the left; turntable (0.90 m plate) right of the middle; a copper drain tray in front. These three are drawn bigger and more from above in the picture than its camera allows (an AI picture); placed to fit the cell, near their picture positions. Moving parts are their own objects (pivots on their axes): the two front leaves (slide), the side door (swings out), the turntable's plate (turns), the emergency stop (presses), and the robot (an armature, rc_robot.py). No brand names: the maker's logo on the glass, the robot maker's name and the screen's product name are generic. """ import bpy, bmesh, math, os, sys, json from mathutils import Vector, Matrix HERE = os.path.dirname(os.path.abspath(__file__)) FOLDER = os.path.dirname(HERE) exec(open(os.path.join(HERE, "rc_lib.py")).read()) exec(open(os.path.join(HERE, "rc_robot.py")).read()) W2, D2 = W_BOX / 2.0, D_BOX / 2.0 # 1.526, 1.438 Z0, ZTOP = FOOT, FOOT + HB # 0.09, 2.49 Z_RAIL, Z_BASE, Z_FLOOR = 0.205, 0.300, 0.314 Z_CEIL, Z_BEAM, Z_ROOF = 2.280, 2.300, 2.475 PX = 1.386 # inner faces of the right posts (and -PX on the left) COL_X, COL_Y = -1.036, -0.94 # the left column's inner face and back FRY = -1.218 # the front-right post's back (the side door's jamb) HINGE = Vector((1.538, 0.080)) # the side door's hinge axis (x, y) LEAF_Y = {"L": (-1.400, -1.370), "R": (-1.285, -1.255)} # the two tracks (the left leaf in front) LEAF_Z = (0.320, 2.300) LEAF_X = {"L": (-1.010, 0.140), "R": (0.090, 1.380)} # the right leaf stops 6 mm short of the deep post TRAVEL = {"L": 1.10, "R": -1.11} # m along x when open TT = Vector((0.58, -0.12)) # turntable axis TT_Z = (0.660, 0.720) # its plate TRAY = (-0.62, 0.22, -1.05, -0.38) # copper tray x0 x1 y0 y1 (its rim on the floor) POCKET = (TRAY[0] + 0.027, TRAY[1] - 0.027, TRAY[2] + 0.027, TRAY[3] - 0.027) # its hole in the floor and the base POCKET_Z = 0.250 BODY_MATS = ["M_RC_Frame", "M_RC_Panel", "M_RC_Black", "M_RC_Glass", "M_RC_Tread", "M_RC_Copper", "M_RC_Chrome", "M_RC_Rubber", "M_RC_Gap", "M_RC_LED", "M_RC_Screen", "M_RC_Decal", "M_RC_Lamp", "M_RC_Yellow", "M_RC_Steel"] LEAF_MATS = ["M_RC_Black", "M_RC_Glass", "M_RC_Chrome", "M_RC_Decal", "M_RC_Copper"] PLATE_MATS = ["M_RC_Steel", "M_RC_Gap", "M_RC_Black"] ESTOP_MATS = ["M_RC_Red", "M_RC_Black"] # decal atlas regions (u0, v0, u1, v1) in T_RC_Decals.png, drawn by rc_textures.py DECALS = {"emblem": (0.0, 0.5, 0.25, 1.0), "START": (0.25, 0.875, 0.375, 0.9375), "STOP": (0.375, 0.875, 0.5, 0.9375), "LIGHT": (0.25, 0.8125, 0.375, 0.875), "SERVICE": (0.375, 0.8125, 0.5, 0.875), "ESTOP": (0.25, 0.6875, 0.5, 0.75), "robot": (0.5, 0.5, 1.0, 1.0)} X, Y, Zv = Vector((1, 0, 0)), Vector((0, 1, 0)), Vector((0, 0, 1)) # ------------------------------------------------------------------ helpers def oct2(a0, a1, b0, b1, c): c = max(0.0, min(c, (a1 - a0) * 0.3, (b1 - b0) * 0.3)) if c < 1e-5: return [(a0, b0), (a1, b0), (a1, b1), (a0, b1)] return [(a0 + c, b0), (a1 - c, b0), (a1, b0 + c), (a1, b1 - c), (a1 - c, b1), (a0 + c, b1), (a0, b1 - c), (a0, b0 + c)] def to3(axis, p, a): return {"x": (a, p[0], p[1]), "y": (p[0], a, p[1]), "z": (p[0], p[1], a)}[axis] def ext(mb, P, axis, a0, a1, mat, cap0=True, cap1=True): """an outline P (2D, in the plane across `axis`) extruded from a0 to a1; edges along the axis carry the outline's chamfers; the ends are plain caps (or left open where they sit against a neighbour)""" P = even(ccw(P)) l0 = [mb.v(to3(axis, p, a0)) for p in P] l1 = [mb.v(to3(axis, p, a1)) for p in P] mb.bridge(l0, l1, mat) if cap0: mb.cap(l0, P, mat) if cap1: mb.cap(l1, P, mat) def xseg(mb, P2, a0, a1, mat, ch0=0.0, ch1=0.0): """an outline P2 in (y, z) extruded along x from a0 to a1, each end capped and chamfered on its own (ch 0: plain)""" P = even(ccw(P2)) I0, ch0 = safe_inset(P, ch0) if ch0 > 0 else (P, 0.0) I1, ch1 = safe_inset(P, ch1) if ch1 > 0 else (P, 0.0) V = lambda Q, x: [mb.v((x, q[0], q[1])) for q in Q] rings = ([(V(I0, a0), I0)] if ch0 > 0 else []) + [(V(P, a0 + ch0), P), (V(P, a1 - ch1), P)] + ([(V(I1, a1), I1)] if ch1 > 0 else []) for (ra, _), (rb, _) in zip(rings, rings[1:]): mb.bridge(ra, rb, mat) mb.cap(rings[0][0], rings[0][1], mat) mb.cap(rings[-1][0], rings[-1][1], mat, flip=True) def rrect(x0, x1, y0, y1, r, n=3): """a rounded rectangle, CCW, n segments per corner (4 * (n + 1) points)""" r = max(0.001, min(r, (x1 - x0) / 2 - 1e-4, (y1 - y0) / 2 - 1e-4)) pts = [] for cx, cy, a0 in ((x1 - r, y0 + r, -90), (x1 - r, y1 - r, 0), (x0 + r, y1 - r, 90), (x0 + r, y0 + r, 180)): for k in range(n + 1): a = math.radians(a0 + 90 * k / n) pts.append((cx + r * math.cos(a), cy + r * math.sin(a))) return pts def zbox(mb, x0, x1, y0, y1, z0, z1, mat, ch=0.005, top=True): """a block standing on z0 (open underneath), chamfered round its top and its vertical edges""" box_frame(mb, (0, 0, 0), X, Y, -Zv, oct2(x0, x1, y0, y1, ch), -z1, -z0, mat, ch=ch if top else 0.0, back=False) def slab(mb, x0, x1, y0, y1, z0, z1, mat, skip=()): c = [mb.v(p) for p in [(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0), (x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1)]] F = {"-z": [0, 3, 2, 1], "+z": [4, 5, 6, 7], "-y": [0, 1, 5, 4], "+y": [2, 3, 7, 6], "-x": [3, 0, 4, 7], "+x": [1, 2, 6, 5]} for k, f in F.items(): if k not in skip: mb.f([c[i] for i in f], mat) def cbox(mb, x0, x1, y0, y1, z0, z1, mat, ch=0.004): """a block chamfered on all edges (an octagon in x-z, chamfered front and back)""" c = max(0.0, min(ch, (x1 - x0) * 0.3, (z1 - z0) * 0.3, (y1 - y0) * 0.3)) prism(mb, oct2(x0, x1, z0, z1, c), y0, y1, mat, ch=c) def xbox(mb, x0, x1, y0, y1, z0, z1, mat, ch=0.004): """the same with the octagon in y-z (chamfered at both x ends)""" c = max(0.0, min(ch, (x1 - x0) * 0.3, (z1 - z0) * 0.3, (y1 - y0) * 0.3)) box_frame(mb, (0, 0, 0), Y, Zv, X, oct2(y0, y1, z0, z1, c), x0, x1, mat, ch=c) def plate(mb, O, eu, ev, en, u0, u1, v0, v1, h, mat, ch=0.0015, back=False): """a rectangle standing h proud of a surface (O, axes eu ev in it, en out of it), open underneath""" box_frame(mb, O, eu, ev, -en, oct2(u0, u1, v0, v1, ch * 0.0), -h, 0.0, mat, ch=min(ch, h * 0.4), back=back) def decal(mb, O, eu, ev, en, u0, u1, v0, v1, rgn, mat="M_RC_Decal", lift=0.0006): """one quad over a surface (lifted 0.6 mm), UVs into an atlas region; eu reads left to right, ev up""" a, b, c_, d = rgn P = lambda u, v: mb.v(Vector(O) + eu * u + ev * v + en * lift) q = [P(u0, v0), P(u1, v0), P(u1, v1), P(u0, v1)] mb.f(q, mat, [(a, b), (c_, b), (c_, d), (a, d)]) mb.decal_n.append((len(mb.faces) - 1, Vector(en))) def handle(mb, O, en, up, length, standoff, mat, r=0.0105): """a D-pull bar: two standoffs and the grip, on a surface at O (en out of it, up along the grip)""" up = Vector(up).normalized(); en = Vector(en).normalized() a, b = Vector(O) - up * length / 2, Vector(O) + up * length / 2 pts = [a, a + en * standoff * 0.7, a + en * standoff + up * 0.02, b + en * standoff - up * 0.02, b + en * standoff * 0.7, b] tube(mb, pts, r, 12, mat, n_per=5) for p in (a, b): lathe(mb, [(0.016, 0.0), (0.016, 0.004), (0.012, 0.007)], p, en, 12, mat, cap1=True) def frame_glass(mb, axis_u, axis_n, origin, u0, u1, z0, z1, n0, n1, bw, fmat, gmat, bars="lrbt", gn=None): """a glazed frame in a vertical plane: bars of width bw round (u0..u1, z0..z1), depth n0..n1 along axis_n; the glass (gn: its depth range) 1 mm short of the bars""" eu, en = Vector(axis_u), Vector(axis_n) O = Vector(origin) def bar(a0, a1, b0, b1): box_frame(mb, O, eu, Zv, en, oct2(a0, a1, b0, b1, 0.003), n0, n1, fmat, ch=0.003) if "l" in bars: bar(u0, u0 + bw, z0, z1) if "r" in bars: bar(u1 - bw, u1, z0, z1) ul = u0 + (bw if "l" in bars else 0.0) ur = u1 - (bw if "r" in bars else 0.0) if "b" in bars: bar(ul, ur, z0, z0 + bw) if "t" in bars: bar(ul, ur, z1 - bw, z1) zb = z0 + (bw if "b" in bars else 0.0) zt = z1 - (bw if "t" in bars else 0.0) g0, g1 = gn if gn else ((n0 + n1) / 2 - 0.005, (n0 + n1) / 2 + 0.005) box_frame(mb, O, eu, Zv, en, [(ul + 0.001, zb + 0.001), (ur - 0.001, zb + 0.001), (ur - 0.001, zt - 0.001), (ul + 0.001, zt - 0.001)], g0, g1, gmat, ch=0.0) class MBD(MB): def __init__(s): super().__init__() s.decal_n = [] def orient_decals(me, mb): """single-sided patches: turn each to face where it was meant to (recalc cannot tell for an open patch)""" bm = bmesh.new() bm.from_mesh(me) bm.faces.ensure_lookup_table() flip = [bm.faces[i] for i, n in mb.decal_n if bm.faces[i].normal.dot(n) < 0] if flip: bmesh.ops.reverse_faces(bm, faces=flip) bm.to_mesh(me) bm.free() return len(flip) # ------------------------------------------------------------------ the static body def build_body(): mb = MBD() FR, PA, BK, GL, TR, CU, CH, RU, GP = ("M_RC_Frame", "M_RC_Panel", "M_RC_Black", "M_RC_Glass", "M_RC_Tread", "M_RC_Copper", "M_RC_Chrome", "M_RC_Rubber", "M_RC_Gap") # --- feet: rubber sole, copper pad, threaded stem, nut, top plate for sx in (-1, 1): for sy in (-1, 1): c = Vector((sx * (W2 - 0.10), sy * (D2 - 0.10), 0.0)) lathe(mb, [(0.064, 0.0), (0.066, 0.008), (0.072, 0.010), (0.072, 0.030), (0.066, 0.036), (0.020, 0.038), (0.016, 0.040), (0.016, 0.056)], c, Zv, 32, [RU, RU, CU, CU, CU, CU, CH], cap0=True) lathe(mb, [(0.030, 0.056), (0.030, 0.068)], c, Zv, 8, BK, cap0=True, cap1=True, phase=math.pi / 8) lathe(mb, [(0.016, 0.068), (0.016, 0.074), (0.055, 0.075), (0.055, Z0)], c, Zv, 24, [CH, BK, BK]) # --- base: plinth 0.09-0.205, the sill band above it set back 3 cm on the front, flush elsewhere, top at 0.300 # (the middle third carries the copper tray's pocket, 3 mm round the tray's body, 0.25 m deep) c = 0.006 qx0, qx1, qy0, qy1 = POCKET head = [(-D2 + c, Z0), (D2 - c, Z0), (D2, Z0 + c), (D2, Z_BASE)] tail = [(-D2 + 0.03, Z_BASE), (-D2 + 0.03, Z_RAIL), (-D2 + c, Z_RAIL), (-D2, Z_RAIL - c), (-D2, Z0 + c)] full = head + tail notch = head + [(qy1, Z_BASE), (qy1, POCKET_Z), (qy0, POCKET_Z), (qy0, Z_BASE)] + tail xseg(mb, full, -W2, qx0, FR, ch0=0.004) xseg(mb, notch, qx0, qx1, FR) xseg(mb, full, qx1, W2, FR, ch1=0.004) # --- posts, the hinge post, the left column (two boxes with a seam), all standing on the base for (x0, x1, y0, y1) in ((PX, W2, -D2, FRY), (PX, W2, 1.298, D2), (-W2, -PX, 1.298, D2)): zbox(mb, x0, x1, y0, y1, Z_BASE, Z_ROOF, FR, ch=0.005) zbox(mb, 1.446, W2, 0.085, 0.165, Z_BASE, Z_BEAM, FR, ch=0.004) zbox(mb, -W2, -1.391, -D2, COL_Y, Z_BASE, Z_ROOF, FR, ch=0.005) zbox(mb, -1.385, COL_X, -D2, COL_Y, Z_BASE, Z_ROOF, FR, ch=0.005) slab(mb, -1.391, -1.385, -D2 + 0.008, COL_Y - 0.008, Z_BASE, Z_ROOF, GP, skip=("-z", "+z", "-x", "+x")) # --- top frame (beams between the posts, ends open against them) and the roof ext(mb, oct2(-D2, -1.23, 2.37, Z_ROOF, 0.005), "x", COL_X, PX, FR, False, False) # header ext(mb, oct2(PX, W2, Z_BEAM, Z_ROOF, 0.005), "y", FRY, 1.298, FR, False, False) # right ext(mb, oct2(1.298, D2, Z_BEAM, Z_ROOF, 0.005), "x", -PX, PX, FR, False, False) # back ext(mb, oct2(-W2, -PX, Z_BEAM, Z_ROOF, 0.005), "y", COL_Y, 1.298, FR, False, False) # left zbox(mb, -W2, W2, -D2, D2, Z_ROOF, ZTOP, FR, ch=0.006) # --- the leaves' top track under the header: a channel with two runs (outer lip, divider, inner lip) slab(mb, COL_X, PX, -1.430, -1.230, 2.355, 2.370, BK, skip=("+z", "-x", "+x")) for y0, y1 in ((-1.430, -1.415), (-1.335, -1.320), (-1.245, -1.230)): slab(mb, COL_X, PX, y0, y1, 2.310, 2.355, BK, skip=("+z", "-x", "+x")) # --- front sill, the leaves' stop block and a guide rail (picture: u 520-660 under the leaves' meeting) slab(mb, COL_X, PX, -D2 + 0.03, -1.215, Z_BASE, 0.305, BK, skip=("-z", "-x", "+x")) cbox(mb, 0.105, 0.145, -1.360, -1.295, 0.305, 0.360, BK, ch=0.004) cbox(mb, 0.145, 0.450, -1.330, -1.312, 0.305, 0.316, BK, ch=0.003) # --- floor (diamond plate) and ceiling, cut round the posts and the column rects = [(COL_X, PX, -1.215, 1.37), (-1.496, COL_X, COL_Y, 1.298), (-PX, COL_X, 1.298, 1.37), (PX, 1.476, FRY, 1.298)] floor = [(COL_X, PX, -1.215, qy0), (COL_X, PX, qy1, 1.37), (COL_X, qx0, qy0, qy1), (qx1, PX, qy0, qy1)] + rects[1:] for (x0, x1, y0, y1) in floor: slab(mb, x0, x1, y0, y1, Z_BASE, Z_FLOOR, TR, skip=("-z",)) for (x0, x1, y0, y1) in rects: slab(mb, x0, x1, y0, y1, Z_CEIL, Z_BEAM, PA, skip=("+z",)) # --- back wall: glass on the left part, then panels with a bolted rib, a kick plate, the louvre vent s1 = CAM.on_y(705, 400, 1.37).x s2 = CAM.on_y(729, 400, 1.37).x # outer skin (not in the picture): four panels with 6 mm seams over a backing, so the back is not one slab slab(mb, -1.03, PX, 1.390, D2 - 0.006, Z_BASE, Z_BEAM, GP, skip=("-z", "+z", "-x", "+x")) edges = [-1.03 + (PX + 1.03) * k / 4 for k in range(5)] for k in range(4): slab(mb, edges[k] + (0.003 if k else 0.0), edges[k + 1] - (0.003 if k < 3 else 0.0), 1.392, D2, Z_BASE, Z_BEAM, FR, skip=("-z", "+z", "-y") + (("-x",) if k == 0 else ()) + (("+x",) if k == 3 else ())) for x0, x1 in ((-1.03, s1 - 0.003), (s2 + 0.003, PX)): slab(mb, x0, x1, 1.370, 1.390, Z_FLOOR, Z_CEIL, PA, skip=("+y", "+z", "-z")) cbox(mb, s1, s2, 1.358, 1.390, Z_FLOOR, Z_CEIL, PA, ch=0.004) for zb in (CAM.on_y(718, 262, 1.36).z, CAM.on_y(718, 460, 1.36).z): for xb in (s1 + 0.006, (s1 + s2) / 2, s2 - 0.006): bolt(mb, Vector((xb, 1.358, zb)), -Y, 0.0055, 0.004, CH, nseg=8) slab(mb, -1.03, PX, 1.356, 1.370, Z_FLOOR, 0.400, BK, skip=("+y", "-z")) # kick plate frame_glass(mb, X, Y, (0, 0, 0), -PX, -1.03, Z_BASE, Z_BEAM, 1.398, 1.428, 0.034, BK, GL) vx0, vx1, vz0, vz1 = -0.05, 0.63, 0.63, 0.88 for (a0, a1, b0, b1) in ((vx0, vx1, vz0, vz0 + 0.025), (vx0, vx1, vz1 - 0.025, vz1), (vx0, vx0 + 0.025, vz0 + 0.025, vz1 - 0.025), (vx1 - 0.025, vx1, vz0 + 0.025, vz1 - 0.025)): cbox(mb, a0, a1, 1.352, 1.370, b0, b1, PA, ch=0.004) slab(mb, vx0 + 0.025, vx1 - 0.025, 1.366, 1.369, vz0 + 0.025, vz1 - 0.025, GP, skip=("+y",)) n_sl = 8 for k in range(n_sl): zc = vz0 + 0.025 + (vz1 - vz0 - 0.05) * (k + 0.5) / n_sl ang = math.radians(38) ev = Vector((0, -math.sin(ang), math.cos(ang))) en = Vector((0, -math.cos(ang), -math.sin(ang))) box_frame(mb, Vector((0, 1.360, zc)), X, ev, en, [(vx0 + 0.026, -0.012), (vx1 - 0.026, -0.012), (vx1 - 0.026, 0.012), (vx0 + 0.026, 0.012)], -0.0015, 0.0015, FR, ch=0.0) # --- left side: glass behind the column frame_glass(mb, Y, X, (0, 0, 0), COL_Y, 0.160, Z_BASE, Z_BEAM, -W2, -1.496, 0.034, BK, GL, bars="lbt") frame_glass(mb, Y, X, (0, 0, 0), 0.194, 1.298, Z_BASE, Z_BEAM, -W2, -1.496, 0.034, BK, GL, bars="rbt") zbox(mb, -W2, -1.496, 0.160, 0.194, Z_BASE, Z_BEAM, BK, ch=0.003) # mullion # --- right side: the door's sill and transom, the latch keepers on the post, the solid rear panel, hinges slab(mb, 1.476, W2, FRY, 0.085, Z_BASE, 0.330, BK, skip=("-z", "-y", "+y")) slab(mb, 1.476, W2, FRY, 0.085, 2.225, Z_BEAM, BK, skip=("+z", "-y", "+y")) for z0_, z1_ in ((2.10, 2.22), (0.36, 0.48)): xbox(mb, W2, W2 + 0.014, -1.252, -1.228, z0_, z1_, CU, ch=0.005) for zz in (z0_ + 0.02, z1_ - 0.02): bolt(mb, Vector((W2 + 0.014, -1.240, zz)), X, 0.0045, 0.003, CH, nseg=8) slab(mb, 1.496, W2, 0.165, 1.298, Z_BASE, Z_BEAM, FR, skip=("-z", "+z", "-y", "+y")) slab(mb, 1.4965, 1.5255, 0.165, 1.298, 1.290, 1.296, GP, skip=("-y", "+y", "-z", "+z", "-x")) # a seam for zc in (2.05, 0.55): lathe(mb, [(0.012, zc - 0.022), (0.012, zc + 0.022)], (HINGE.x, HINGE.y, 0), Zv, 16, CU, cap0=True, cap1=True) xbox(mb, W2, 1.533, 0.093, 0.135, zc - 0.07, zc + 0.07, CU, ch=0.002) slab(mb, W2, 1.534, 0.086, 0.094, zc - 0.022, zc + 0.022, CU, skip=("-x",)) for zz in (zc - 0.045, zc + 0.045): bolt(mb, Vector((1.533, 0.114, zz)), X, 0.004, 0.003, CH, nseg=8) # --- front right: interlock switches (black housing on the post, copper actuator on its front) for z0_, z1_ in ((2.16, 2.30), (0.335, 0.475)): cbox(mb, 1.300, PX, -1.435, -1.405, z0_, z1_, BK, ch=0.004) cbox(mb, 1.390, 1.414, -1.452, -D2, z0_ + 0.03, z1_ - 0.01, CU, ch=0.006) for zz in (z0_ + 0.045, z1_ - 0.025): bolt(mb, Vector((1.402, -1.452, zz)), -Y, 0.0045, 0.003, CH, nseg=8) # --- ceiling: LED strips along the depth in black housings, where the picture shows them (rc_build: probed on the # ceiling through the camera: three in the front row, two in the second), a downlight by the back wall for xl, yl, ln in ((-0.26, -0.90, 0.60), (0.19, -0.90, 0.60), (0.88, -0.90, 0.60), (-0.26, -0.03, 0.50), (0.88, -0.03, 0.50)): slab(mb, xl - 0.055, xl + 0.055, yl - ln / 2 - 0.04, yl + ln / 2 + 0.04, Z_CEIL - 0.020, Z_CEIL, BK, skip=("+z",)) slab(mb, xl - 0.032, xl + 0.032, yl - ln / 2, yl + ln / 2, Z_CEIL - 0.024, Z_CEIL - 0.020, "M_RC_LED", skip=("+z",)) dl = CAM.on_y(852, 241, 1.22) lathe(mb, [(0.055, Z_CEIL), (0.055, Z_CEIL - 0.012), (0.045, Z_CEIL - 0.016), (0.040, Z_CEIL - 0.016)], (dl.x, 1.22, 0), Zv, 24, [BK, BK, "M_RC_LED"], cap1=True) # --- robot pedestal: bottom plate, body, top plate with anchor bolts rx, ry = ROB.x, ROB.y zbox(mb, rx - 0.38, rx + 0.38, ry - 0.38, ry + 0.38, Z_FLOOR, Z_FLOOR + 0.022, BK, ch=0.004) zbox(mb, rx - 0.33, rx + 0.33, ry - 0.33, ry + 0.33, Z_FLOOR + 0.022, ROB.z - 0.028, BK, ch=0.006) zbox(mb, rx - 0.37, rx + 0.37, ry - 0.37, ry + 0.37, ROB.z - 0.028, ROB.z, BK, ch=0.004) for sx in (-1, 1): for sy in (-1, 1): bolt(mb, Vector((rx + sx * 0.335, ry + sy * 0.335, ROB.z)), Zv, 0.014, 0.012, CH, nseg=8) bolt(mb, Vector((rx + sx * 0.355, ry + sy * 0.355, Z_FLOOR + 0.022)), Zv, 0.010, 0.010, CH, nseg=8) # --- turntable drive: base plate, housing, a neck under the plate tx, ty = TT.x, TT.y zbox(mb, tx - 0.34, tx + 0.34, ty - 0.34, ty + 0.34, Z_FLOOR, Z_FLOOR + 0.030, BK, ch=0.004) zbox(mb, tx - 0.27, tx + 0.27, ty - 0.27, ty + 0.27, Z_FLOOR + 0.030, 0.620, BK, ch=0.008) for sx in (-1, 1): for sy in (-1, 1): bolt(mb, Vector((tx + sx * 0.31, ty + sy * 0.31, Z_FLOOR + 0.030)), Zv, 0.010, 0.010, CH, nseg=8) lathe(mb, [(0.21, 0.620), (0.21, 0.640), (0.19, 0.645), (0.19, TT_Z[0] - 0.003)], (tx, ty, 0), Zv, 40, BK, cap1=True) # --- copper drain tray sunk in the floor (picture: a rim on the floor, a step inside, a deep pan): one closed # sheet, rounded corners; inside: flange, wall, ledge, wall, bottom; outside: under the flange, down, the bottom x0, x1, y0, y1 = TRAY loops = [ # (inset from the tray's outline, z, corner radius) (0.000, Z_FLOOR + 0.0003, 0.030), (0.002, Z_FLOOR + 0.0055, 0.028), (0.028, Z_FLOOR + 0.0055, 0.016), (0.032, Z_FLOOR + 0.002, 0.014), (0.034, 0.296, 0.012), (0.048, 0.295, 0.010), (0.052, 0.262, 0.008)] under = [(0.030, Z_FLOOR + 0.0003, 0.014), (0.030, 0.290, 0.014), (0.056, 0.288, 0.006), (0.056, 0.258, 0.006)] ring = lambda ins, z, r: [mb.v((p[0], p[1], z)) for p in rrect(x0 + ins, x1 - ins, y0 + ins, y1 - ins, r)] top = [ring(*l) for l in loops] for a, b in zip(top, top[1:]): mb.bridge(a, b, CU) mb.cap(top[-1], rrect(x0 + loops[-1][0], x1 - loops[-1][0], y0 + loops[-1][0], y1 - loops[-1][0], loops[-1][2]), CU) bot = [top[0]] + [ring(*l) for l in under] for a, b in zip(bot, bot[1:]): mb.bridge(b, a, CU) mb.cap(bot[-1], rrect(x0 + under[-1][0], x1 - under[-1][0], y0 + under[-1][0], y1 - under[-1][0], under[-1][2]), CU, flip=True) lathe(mb, [(0.040, 0.2622), (0.030, 0.2622), (0.026, 0.2624)], ((x0 + x1) / 2, (y0 + y1) / 2, 0), Zv, 24, [CU, GP], cap1=True) # --- the control pod on the column: an inclined face (screen, lamps, buttons), a box below (emergency stop) yb = -D2 - 0.0025 prof = [(yb, 0.830), (-1.584, 0.830), (-1.590, 0.836), (-1.590, 1.064), (-1.586, 1.074), (-1.512, 1.742), (-1.504, 1.750), (yb, 1.750)] box_frame(mb, (0, 0, 0), Y, Zv, X, prof, -1.505, COL_X - 0.002, BK, ch=0.006) A, Bp = Vector((0, -1.510, 1.750)), Vector((0, -1.590, 1.070)) eu_f = (A - Bp).normalized() # up the inclined face en_f = X.cross(eu_f).normalized() # out of it if en_f.y > 0: en_f = -en_f xm = (-1.505 + COL_X) / 2 O_f = Vector((xm, Bp.y, Bp.z)) sv = lambda z: (z - Bp.z) / eu_f.z plate(mb, O_f, X, eu_f, en_f, -0.165, 0.165, sv(1.300), sv(1.738), 0.006, BK, ch=0.002) # bezel decal(mb, O_f + en_f * 0.006, X, eu_f, en_f, -0.150, 0.150, sv(1.318), sv(1.722), (0.0, 0.0, 1.0, 1.0), mat="M_RC_Screen") for k, lab in enumerate(("START", "STOP", "LIGHT", "SERVICE")): xk = -0.1275 + 0.085 * k plate(mb, O_f, X, eu_f, en_f, xk - 0.026, xk + 0.026, sv(1.135), sv(1.192), 0.003, CH, ch=0.001) plate(mb, O_f + en_f * 0.003, X, eu_f, en_f, xk - 0.018, xk + 0.018, sv(1.142), sv(1.185), 0.010, BK, ch=0.003) plate(mb, O_f, X, eu_f, en_f, xk - 0.014, xk + 0.014, sv(1.205), sv(1.220), 0.004, "M_RC_Lamp", ch=0.0015) decal(mb, O_f, X, eu_f, en_f, xk - 0.040, xk + 0.040, sv(1.098), sv(1.118), DECALS[lab]) es = Vector((-1.360, -1.590, 0.975)) lathe(mb, [(0.050, 0.0), (0.050, 0.004), (0.028, 0.004)], es, -Y, 32, "M_RC_Yellow") lathe(mb, [(0.028, 0.0), (0.028, 0.016), (0.024, 0.018), (0.015, 0.018), (0.015, 0.004)], es, -Y, 24, BK) decal(mb, Vector((xm, -1.590, 0.0)), X, Zv, -Y, -0.12, 0.12, 0.905, 0.925, DECALS["ESTOP"]) return mb, dict(back_rib=(round(s1, 3), round(s2, 3)), downlight=[round(dl.x, 3), 1.22]) # ------------------------------------------------------------------ moving parts def build_leaf(key): mb = MBD() x0, x1 = LEAF_X[key] y0, y1 = LEAF_Y[key] z0, z1 = LEAF_Z frame_glass(mb, X, Y, (0, 0, 0), x0, x1, z0, z1, y0, y1, 0.036, "M_RC_Black", "M_RC_Glass") for xe in (x0 + 0.02, x1 - 0.09): # roller carriages into the track cbox(mb, xe, xe + 0.07, y0 + 0.004, y1 - 0.004, z1, 2.345, "M_RC_Black", ch=0.004) for xe in (x0 + 0.004, x1 - 0.074): # hanger plates on the leaf's top corners (picture) cbox(mb, xe, xe + 0.07, y0 - 0.008, y0, z1 - 0.17, z1 - 0.005, "M_RC_Black", ch=0.003) zh = (CAM.on_y(240, 450, -1.46).z + CAM.on_y(240, 515, -1.46).z) / 2 if key == "L": for xh in (x0 + 0.105, x1 - 0.055): handle(mb, (xh, y0, zh), -Y, Zv, 0.22, 0.050, "M_RC_Chrome") # emblem on the glass (picture: u 250-355, v 185-260): generic ring mark and "CELL 01" ce = CAM.on_y(302, 222, y0) decal(mb, Vector((ce.x, y0 + 0.010, ce.z)), X, Zv, -Y, -0.18, 0.18, -0.18, 0.18, DECALS["emblem"]) else: handle(mb, (x1 - 0.115, y0, zh), -Y, Zv, 0.22, 0.050, "M_RC_Chrome") return mb def build_side_door(): mb = MBD() xo, xi = 1.516, 1.486 yf, yb = -1.210, 0.075 z0, z1 = 0.340, 2.215 frame_glass(mb, Y, X, (0, 0, 0), yf, yb, z0, z1, xi, xo, 0.040, "M_RC_Black", "M_RC_Glass") zh = (CAM.on_x(1045, 450, 1.526).z + CAM.on_x(1045, 525, 1.526).z) / 2 yh = CAM.on_x(1044, 488, 1.526).y handle(mb, (xo, yh, zh), X, Zv, 0.22, 0.050, "M_RC_Chrome") for zc in (2.05, 0.55): # the door's hinge leaves and knuckles cbox(mb, xo, xo + 0.014, 0.025, 0.067, zc - 0.07, zc + 0.07, "M_RC_Copper", ch=0.002) for zz in (zc - 0.045, zc + 0.045): bolt(mb, Vector((xo + 0.014, 0.046, zz)), X, 0.004, 0.003, "M_RC_Chrome", nseg=8) for k0, k1 in ((zc - 0.068, zc - 0.025), (zc + 0.025, zc + 0.068)): lathe(mb, [(0.012, k0), (0.012, k1)], (HINGE.x, HINGE.y, 0), Zv, 16, "M_RC_Copper", cap0=True, cap1=True) slab(mb, xo, HINGE.x, 0.060, HINGE.y - 0.006, k0, k1, "M_RC_Copper", skip=("-x",)) return mb def build_plate(): mb = MBD() tx, ty = TT.x, TT.y R = 0.450 lathe(mb, [(0.20, TT_Z[0]), (R - 0.004, TT_Z[0]), (R, TT_Z[0] + 0.004), (R, TT_Z[1] - 0.004), (R - 0.004, TT_Z[1]), (0.06, TT_Z[1]), (0.06, TT_Z[1] - 0.006)], (tx, ty, 0), Zv, 64, "M_RC_Steel", cap0=True, cap1=True) for k in range(8): # radial T-slots and bolt holes (dark inlays on the top) a = 2 * math.pi * k / 8 d, s = Vector((math.cos(a), math.sin(a), 0)), Vector((-math.sin(a), math.cos(a), 0)) O = Vector((tx, ty, TT_Z[1])) box_frame(mb, O, d, s, -Zv, [(0.10, -0.009), (R - 0.05, -0.009), (R - 0.05, 0.009), (0.10, 0.009)], -0.0006, 0.0, "M_RC_Gap", ch=0.0, back=False) a2 = a + math.pi / 8 for rr in (0.22, 0.36): c = O + Vector((math.cos(a2), math.sin(a2), 0)) * rr lathe(mb, [(0.011, 0.0), (0.011, 0.0006)], c, Zv, 12, "M_RC_Gap", cap1=True) return mb def build_estop(): mb = MBD() es = Vector((-1.360, -1.590, 0.975)) lathe(mb, [(0.012, 0.010), (0.012, 0.026), (0.034, 0.028), (0.035, 0.036), (0.031, 0.046), (0.020, 0.052)], es, -Y, 32, ["M_RC_Black", "M_RC_Red", "M_RC_Red", "M_RC_Red", "M_RC_Red"], cap0=True, cap1=True) return mb # ------------------------------------------------------------------ scene def coll(name, parent=None): c = bpy.data.collections.get(name) or bpy.data.collections.new(name) p = parent or bpy.context.scene.collection if c.name not in [x.name for x in p.children]: p.children.link(c) return c def obj(name, mb, mats, col, origin=None): me, bad = mb.to_mesh(name, mats) flipped = orient_decals(me, mb) if getattr(mb, "decal_n", None) else 0 ob = bpy.data.objects.new(name, me) col.objects.link(ob) if origin is not None: me.transform(Matrix.Translation(-Vector(origin))) ob.location = origin me.shade_smooth() try: me.set_sharp_from_angle(angle=math.radians(32)) except Exception: pass return ob, dict(bad_faces=bad, decals_flipped=flipped) def main(): for o in list(bpy.data.objects): bpy.data.objects.remove(o) for d in (bpy.data.meshes, bpy.data.armatures, bpy.data.cameras, bpy.data.lights, bpy.data.actions): for x in list(d): d.remove(x) for c in list(bpy.data.collections): bpy.data.collections.remove(c) for n in set(BODY_MATS + LEAF_MATS + PLATE_MATS + ESTOP_MATS + ROBOT_MATS): if n not in bpy.data.materials: bpy.data.materials.new(n) sc = bpy.context.scene sc.unit_settings.system = 'METRIC' top = coll("RobotCell") c_static, c_moving, c_robot = coll("RC | static", top), coll("RC | moving", top), coll("RC | robot", top) coll("RC | collision", top) rep = {"faces": {}} root = bpy.data.objects.new("RobotCell_Root", None) root.empty_display_type = 'PLAIN_AXES' root.empty_display_size = 0.3 top.objects.link(root) mb, info = build_body() body, rep["faces"]["body"] = obj("SM_RobotCell_Body", mb, BODY_MATS, c_static) rep.update(info) parts = [body] for key, name in (("L", "SM_RobotCell_DoorLeft"), ("R", "SM_RobotCell_DoorRight")): x0, x1 = LEAF_X[key] ob, rep["faces"][name] = obj(name, build_leaf(key), LEAF_MATS, c_moving, origin=((x0 + x1) / 2, sum(LEAF_Y[key]) / 2, LEAF_Z[0])) ob["travel_m"] = TRAVEL[key] parts.append(ob) ob, rep["faces"]["side"] = obj("SM_RobotCell_DoorSide", build_side_door(), LEAF_MATS, c_moving, origin=(HINGE.x, HINGE.y, 0.340)) ob["open_deg"] = 100.0 parts.append(ob) ob, rep["faces"]["plate"] = obj("SM_RobotCell_TurntablePlate", build_plate(), PLATE_MATS, c_moving, origin=(TT.x, TT.y, TT_Z[0])) parts.append(ob) ob, rep["faces"]["estop"] = obj("SM_RobotCell_EStop", build_estop(), ESTOP_MATS, c_moving, origin=(-1.360, -1.590, 0.975)) ob["press_m"] = 0.007 parts.append(ob) for p in parts: p.parent = root # the robot: armature at the pedestal's top, the mesh rigid-skinned to it arm = build_armature("RC_Robot_Rig", c_robot) arm.location = ROB arm.parent = root rmb, rinfo = build_robot_mesh() me, bad = rmb.to_mesh("SK_RobotCell_Robot", ROBOT_MATS) flipped = 0 bm = bmesh.new(); bm.from_mesh(me); bm.faces.ensure_lookup_table() di = ROBOT_MATS.index("M_RC_Decal") flip = [f for f in bm.faces if f.material_index == di and f.normal.y > 0] if flip: bmesh.ops.reverse_faces(bm, faces=flip) flipped = len(flip) bm.to_mesh(me); bm.free() rob = bpy.data.objects.new("SK_RobotCell_Robot", me) c_robot.objects.link(rob) me.shade_smooth() me.set_sharp_from_angle(angle=math.radians(32)) groups = {} for i, g in enumerate(rmb.grp): for bn, w in g.items(): groups.setdefault(bn, []).append((i, w)) for bn, lst in groups.items(): vg = rob.vertex_groups.new(name=bn) for i, w in lst: vg.add([i], w, 'REPLACE') rob.parent = arm mod = rob.modifiers.new("Armature", 'ARMATURE') mod.object = arm rep["faces"]["robot"] = dict(bad_faces=bad, decals_flipped=flipped, groups={k: len(v) for k, v in groups.items()}) # the picture's camera, kept in the file cam = bpy.data.objects.new("RC_PictureCamera", bpy.data.cameras.new("RC_PictureCamera")) coll("Studio").objects.link(cam) cam.matrix_world = CAM.matrix_world() cam.data.lens, cam.data.sensor_width, cam.data.sensor_fit = LENS, SENSOR, 'HORIZONTAL' cam.data.clip_start, cam.data.clip_end = 0.1, 60.0 sc.camera = cam sc.render.resolution_x, sc.render.resolution_y = RES rep["camera"] = dict(location=[round(c, 4) for c in CAM.C], lens_mm=round(LENS, 2)) rep["audit"] = {o.name: audit(o) for o in parts + [rob]} exec(open(os.path.join(HERE, "rc_rig.py")).read(), dict(globals(), __name__="rc_rig")) path = os.path.join(FOLDER, "RobotCell.blend") bpy.ops.wm.save_as_mainfile(filepath=path) with open(os.path.join(HERE, "build_report.json"), "w") as fh: json.dump(rep, fh, indent=1) print("BUILD OK", json.dumps(rep)[:3000]) if __name__ == "__main__": main()