"""Pressure hatch after the user's reference sheet "PRESSURE HATCH" (2026-10-14): a round hinged hatch in a grey wall plate with a cut corner, a black seal ring with light slots, a white leaf with a blue window, and on the leaf's back a dark locking mechanism with eight white dogs and amber pads. Built in the live Blender through the MCP, by script: ns = {}; p = r"...\\ph_build.py"; exec(compile(open(p).read(), p, "exec"), ns); ns["build_all"]() build_all() deletes and rebuilds every hatch mesh (the file has no hand edits yet; if the user edits it by hand, change it in place instead). Axes and scale: 1 unit = 1 m, Z up. The hatch axis is the Y axis through the origin. The wall's front face is y = 0 and the hatch is seen from -Y; +X is right. The hinge is on the right; the leaf swings out toward -Y. Nothing is a boolean. Surfaces are quads except the fans at lathe centres and the flat caps of the small parts. Sizes (m): clear opening 1.00 (R 0.50); leaf 1.12 (R 0.56); seal ring 1.66 (R 0.83); wall plate 2.10 x 1.90, 6 cm thick; wall 0.20 thick (the optional bulkhead). """ import bpy, bmesh, math, os from mathutils import Vector, Matrix HERE = r"D:\HADAL - The Descent\models\pressure-hatch\build" exec(compile(open(os.path.join(HERE, "ph_lib.py"), encoding="utf-8").read(), "ph_lib.py", "exec"), globals()) MATS = ["M_PH_Grey", "M_PH_Light", "M_PH_White", "M_PH_Steel", "M_PH_Dark", "M_PH_Rubber", "M_PH_Amber", "M_PH_Glass", "M_PH_Hazard", "M_PH_Orange", "M_PH_Lens"] GREY, LIGHT, WHITE, STEEL, DARK, RUBBER, AMBER, GLASS, HAZ, ORANGE, LENS = range(len(MATS)) VIEW_COL = {"M_PH_Grey": (0.26, 0.27, 0.28), "M_PH_Light": (0.48, 0.49, 0.50), "M_PH_White": (0.80, 0.80, 0.79), "M_PH_Steel": (0.62, 0.63, 0.65), "M_PH_Dark": (0.035, 0.037, 0.04), "M_PH_Rubber": (0.015, 0.015, 0.016), "M_PH_Amber": (0.70, 0.40, 0.02), "M_PH_Glass": (0.02, 0.06, 0.45), "M_PH_Hazard": (0.8, 0.6, 0.05), "M_PH_Orange": (0.95, 0.30, 0.02), "M_PH_Lens": (0.9, 0.55, 0.15)} PIN = Vector((0.72, -0.160, 0.0)) # hinge pin axis (vertical); the leaf's pivot. The knuckle (R 0.040) clears the seal ring by 5 mm CAM_O = Vector((0.0, 0.004, 0.0)) # the cam ring's pivot on the hatch axis OPEN_DEG = 105.0 # leaf swing about the pin (+Z) CAM_DEG = -20.0 # cam turn about the hatch axis while unlocking DOG_R_LOCKED, DOG_TRAVEL, DOG_Y = 0.485, 0.08, 0.025 DOG_ANGLES = [45.0 * k for k in range(8)] W_FRONT = Vector((0.0, -1.0, 0.0)) def materials(): out = [] for n in MATS: m = bpy.data.materials.get(n) or bpy.data.materials.new(n) m.diffuse_color = VIEW_COL[n] + (1.0,) out.append(m) return out def collection(name, parent=None): c = bpy.data.collections.get(name) if c is None: c = bpy.data.collections.new(name) (parent or bpy.context.scene.collection).children.link(c) return c # ------------------------------------------------------------------ 2D outlines, in (x, z) seen from the front def densify(loop, step): out = [] n = len(loop) for i in range(n): a, b = Vector(loop[i]), Vector(loop[(i + 1) % n]) k = max(1, int(math.ceil((b - a).length / step))) for j in range(k): out.append(a.lerp(b, j / k)) return out def _dedupe(pts): out = [] for p in pts: p = Vector(p) if not out or (p - out[-1]).length > 1e-6: out.append(p) if len(out) > 2 and (out[0] - out[-1]).length < 1e-6: out.pop() return out def _arc_ccw(c, p0, p1, seg): a0 = math.atan2(p0[1] - c[1], p0[0] - c[0]) a1 = math.atan2(p1[1] - c[1], p1[0] - c[0]) while a1 < a0 - 1e-9: a1 += TAU r = (Vector(p0) - Vector(c)).length return [(c[0] + r * math.cos(a0 + (a1 - a0) * k / seg), c[1] + r * math.sin(a0 + (a1 - a0) * k / seg)) for k in range(seg + 1)] def sector(r0, r1, a0, a1, fr=0.0, cx=0.0, cz=0.0, step=3.0, seg=4): """annular sector between radii r0 < r1 and angles a0 < a1 (degrees, counter-clockwise from +X seen from the front), its four corners rounded with fr (fr = half the width makes a curved pill). Counter-clockwise.""" A0, A1 = math.radians(a0), math.radians(a1) def P(r, a): return (cx + r * math.cos(a), cz + r * math.sin(a)) def arc(r, s, e): n = max(2, int(math.ceil(abs(math.degrees(e - s)) / step))) return [P(r, s + (e - s) * k / n) for k in range(n + 1)] if fr <= 0: return _dedupe(arc(r1, A0, A1) + arc(r0, A1, A0)) rc1, rc0 = r1 - fr, r0 + fr b1, b0 = math.asin(min(1.0, fr / rc1)), math.asin(min(1.0, fr / rc0)) pts = arc(r1, A0 + b1, A1 - b1) pts += _arc_ccw(P(rc1, A1 - b1), P(r1, A1 - b1), P(rc1 * math.cos(b1), A1), seg) pts += _arc_ccw(P(rc0, A1 - b0), P(rc0 * math.cos(b0), A1), P(r0, A1 - b0), seg) pts += arc(r0, A1 - b0, A0 + b0) pts += _arc_ccw(P(rc0, A0 + b0), P(r0, A0 + b0), P(rc0 * math.cos(b0), A0), seg) pts += _arc_ccw(P(rc1, A0 + b1), P(rc1 * math.cos(b1), A0), P(r1, A0 + b1), seg) return _dedupe(pts) def poly(pts, r, seg=4): return _dedupe(fillet2(pts, r, seg, closed=True)) # ------------------------------------------------------------------ terraces: an outline raised in steps toward the front def _p3(p, y): return Vector((p[0], y, p[1])) def terrace(bm, loop, levels, y0, cap=None): """levels: [(inset, height toward the front, material)]; ring i is the outline inset by `inset` at y0 - height. The band from ring i-1 to ring i takes level i's material; the last ring is closed with `cap` (a material).""" loop = [Vector(p) for p in loop] n = len(loop) rings, flat = [], [] for off, H, _m in levels: L = _inset(loop, off) if off > 1e-9 else loop flat.append(L) rings.append([bm.verts.new(_p3(p, y0 - H)) for p in L]) for i in range(1, len(levels)): o0, H0, _ = levels[i - 1] o1, H1, m = levels[i] for k in range(n): k2 = (k + 1) % n e = loop[k2] - loop[k] nrm = Vector((e.y, 0.0, -e.x)) if nrm.length > 1e-12: nrm.normalize() out = nrm * (H1 - H0) + W_FRONT * (o1 - o0) if out.length < 1e-12: out = nrm face(bm, [rings[i - 1][k], rings[i - 1][k2], rings[i][k2], rings[i][k]], m, out=out) if cap is not None: face(bm, rings[-1], cap, out=W_FRONT) return rings, flat def pad(h, r, m, seg=3): """a raised pad with a rounded front edge""" lv = [(0.0, 0.0, m), (0.0, h - r, m)] for k in range(1, seg + 1): th = (math.pi / 2) * k / seg lv.append((r * (1 - math.cos(th)), h - r + r * math.sin(th), m)) return lv def bezel(rim_h, rim_w, floor_h, rim_m, in_m, r=0.002, seg=2): """a raised rim round a shallow floor: reads as a recess with a lip""" lv = [(0.0, 0.0, rim_m), (0.0, rim_h - r, rim_m)] for k in range(1, seg + 1): th = (math.pi / 2) * k / seg lv.append((r * (1 - math.cos(th)), rim_h - r + r * math.sin(th), rim_m)) lv.append((rim_w - r, rim_h, rim_m)) for k in range(1, seg + 1): th = (math.pi / 2) * k / seg lv.append((rim_w - r + r * math.sin(th), rim_h - r + r * math.cos(th), in_m)) lv.append((rim_w + 0.0006, floor_h, in_m)) return lv def annulus(bm, loop, hole_r, y, out, mat, steps=4, power=1.0): """flat face at y from a star-shaped outline in to a round hole on the axis""" loop = [Vector(p) for p in loop] angs = [math.atan2(p.y, p.x) for p in loop] hole = [Vector((math.cos(a), math.sin(a))) * hole_r for a in angs] rows = [[bm.verts.new(_p3(p, y)) for p in loop]] for s in range(1, steps + 1): t = (s / steps) ** power rows.append([bm.verts.new(_p3(loop[k].lerp(hole[k], t), y)) for k in range(len(loop))]) n = len(loop) for i in range(steps): for k in range(n): k2 = (k + 1) % n face(bm, [rows[i][k], rows[i][k2], rows[i + 1][k2], rows[i + 1][k]], mat, out=out) def button(bm, x, z, spec_by_mat, n=24): """small round part on an axis parallel to Y: [(mat, [(r, y, fillet)...]), ...], outer edge first""" for m, spec in spec_by_mat: lathe(bm, prof(spec, 2), n, center=(x, 0.0, z), axis='Y', mat=m) def polar(r, deg): a = math.radians(deg) return r * math.cos(a), r * math.sin(a) # ------------------------------------------------------------------ the static frame PLATE_OUTLINE = [(-0.55, -0.95), (1.05, -0.95), (1.05, 0.95), (-1.05, 0.95), (-1.05, -0.42)] PLATE_RADII = [0.05, 0.15, 0.15, 0.15, 0.05] def build_frame(bm): # wall plate: 6 cm, rounded front edge, open behind (it sits on the wall); its face runs in under the seal ring loop = densify(_dedupe(fillet2(PLATE_OUTLINE, PLATE_RADII, 6, closed=True)), 0.05) lv = [(0.0, 0.0, GREY), (0.0, 0.048, GREY)] for k in range(1, 4): th = (math.pi / 2) * k / 3 lv.append((0.012 * (1 - math.cos(th)), 0.048 + 0.012 * math.sin(th), GREY)) rings, flat = terrace(bm, loop, lv, 0.0) annulus(bm, flat[-1], 0.80, -0.06, W_FRONT, GREY, steps=4, power=0.8) # seal ring, seat, the groove the dogs lock into, bore and the back flange (one profile, split by material) N = 96 for m, spec in ( (LIGHT, [(0.83, -0.055, 0), (0.83, -0.085, 0.006), (0.79, -0.085, 0)]), (DARK, [(0.79, -0.085, 0), (0.79, -0.115, 0.010), (0.60, -0.115, 0.010), (0.60, -0.100, 0)]), (LIGHT, [(0.60, -0.100, 0), (0.585, -0.100, 0.003), (0.585, -0.090, 0)]), (DARK, [(0.585, -0.090, 0), (0.585, -0.030, 0.004), (0.50, -0.030, 0.004), (0.50, 0.0, 0.003), (0.56, 0.0, 0.003), (0.56, 0.06, 0.003), (0.50, 0.06, 0.003), (0.50, 0.20, 0)]), (GREY, [(0.50, 0.20, 0), (0.50, 0.23, 0.004), (0.66, 0.23, 0.004), (0.66, 0.20, 0)])): lathe(bm, prof(spec, 3), N, axis='Y', mat=m) # light slots in the seal ring (curved pills), clear of the hinge for k in range(9): a = 30.0 + 36.0 * k terrace(bm, sector(0.688, 0.722, a, a + 16.0, fr=0.0169), pad(0.0045, 0.0015, LIGHT), -0.114, cap=LIGHT) # hinge: two knuckle housings over the ring and a back block, the pin and its two nuts for sz in (1, -1): rbox(bm, (0.24, 0.163, 0.165), 0.012, 3, M=Matrix.Translation((0.77, -0.1435, sz * 0.1725)), mat=DARK) rbox(bm, (0.055, 0.165, 0.19), 0.010, 3, M=Matrix.Translation((0.8625, -0.1425, 0.0)), mat=DARK) tube(bm, [(PIN.x, PIN.y, -0.258), (PIN.x, PIN.y, 0.258)], 0.018, seg=16, caps=('flat', 'flat'), mat=STEEL) lathe(bm, prof([(0.0, 0.268, 0), (0.03, 0.266, 0.003), (0.03, 0.253, 0)], 2), 16, center=(PIN.x, PIN.y, 0.0), axis='Z', mat=STEEL) lathe(bm, prof([(0.03, -0.253, 0), (0.03, -0.266, 0.003), (0.0, -0.268, 0)], 2), 16, center=(PIN.x, PIN.y, 0.0), axis='Z', mat=STEEL) # round socket in the top right corner (a lamp) button(bm, 0.80, 0.72, [(GREY, [(0.115, -0.058, 0), (0.115, -0.074, 0.005), (0.092, -0.074, 0.004), (0.086, -0.063, 0)]), (DARK, [(0.086, -0.063, 0), (0.046, -0.063, 0)]), (LENS, [(0.046, -0.063, 0), (0.046, -0.068, 0.003), (0.0, -0.071, 0)])], n=40) # two triangular pockets bottom right, a foot plate at the bottom for tri in ([(0.40, -0.87), (0.72, -0.87), (0.72, -0.58)], [(0.80, -0.87), (0.97, -0.87), (0.97, -0.66)]): terrace(bm, poly(tri, 0.035), bezel(0.008, 0.012, 0.003, LIGHT, DARK), -0.059, cap=DARK) terrace(bm, rrect(0.44, 0.06, 0.018, 4, 0.08, -0.905), bezel(0.006, 0.008, 0.002, LIGHT, DARK), -0.059, cap=DARK) # screws in the plate for (x, z) in ((-0.94, 0.84), (0.50, 0.86), (-0.94, -0.30), (0.96, -0.45)): button(bm, x, z, [(STEEL, [(0.016, -0.059, 0), (0.016, -0.066, 0.003), (0.0, -0.068, 0)])], n=16) def build_bulkhead(bm): """optional: a piece of 0.20 m wall with the round hole, for renders and for walls that are not there yet""" loop = densify(rrect(3.0, 2.8, 0.0, 1), 0.10) terrace(bm, loop, [(0.0, 0.0, LIGHT), (0.0, 0.20, LIGHT)], 0.20) annulus(bm, loop, 0.64, 0.0, W_FRONT, LIGHT, steps=5, power=0.8) # hole R 0.64: the frame's bore and lock groove annulus(bm, loop, 0.64, 0.20, -W_FRONT, LIGHT, steps=5, power=0.8) # line the opening; the back flange covers the edge # ------------------------------------------------------------------ the leaf (moves: swings about the pin) def build_leaf(bm): N = 96 # front: steel band, a dark groove, the white face, a steel bezel and the window for m, spec in ( (STEEL, [(0.56, -0.035, 0), (0.56, -0.080, 0.005), (0.532, -0.080, 0.002), (0.532, -0.074, 0)]), (DARK, [(0.532, -0.074, 0), (0.524, -0.074, 0), (0.524, -0.080, 0)]), (WHITE, [(0.524, -0.080, 0), (0.524, -0.083, 0.002), (0.516, -0.086, 0.002), (0.215, -0.086, 0)]), (STEEL, [(0.215, -0.086, 0), (0.215, -0.098, 0.004), (0.200, -0.104, 0.004), (0.165, -0.104, 0.003), (0.160, -0.097, 0)]), (DARK, [(0.160, -0.097, 0), (0.146, -0.097, 0.002), (0.146, -0.090, 0)]), (GLASS, [(0.146, -0.090, 0), (0.0, -0.090, 0)]), # back: the window, the hub round it, the back plate, the rim and the gasket that meets the seat (GLASS, [(0.0, 0.055, 0), (0.12, 0.055, 0)]), (DARK, [(0.12, 0.055, 0), (0.12, 0.070, 0.004), (0.20, 0.070, 0.006), (0.20, -0.015, 0.004), (0.47, -0.015, 0.004), (0.47, -0.035, 0.003), (0.49, -0.035, 0)]), (RUBBER, [(0.49, -0.035, 0), (0.56, -0.035, 0)])): lathe(bm, prof(spec, 3), N, axis='Y', mat=m) # front details y_face, y_band = -0.085, -0.079 top_tab = [(-0.055, 0.43), (0.055, 0.43), (0.08, 0.535), (-0.08, 0.535)] terrace(bm, poly(top_tab, [0.012, 0.012, 0.02, 0.02]), pad(0.022, 0.004, DARK), y_band, cap=DARK) for (dx, dz) in ((0.0, 0.0), (-0.03, -0.025), (0.03, -0.025), (-0.03, 0.025), (0.03, 0.025)): button(bm, dx, 0.4825 + dz, [(LIGHT, [(0.009, -0.100, 0), (0.009, -0.1045, 0.002), (0.0, -0.1055, 0)])], n=12) bot_tab = [(-0.08, -0.535), (0.08, -0.535), (0.055, -0.43), (-0.055, -0.43)] terrace(bm, poly(bot_tab, [0.02, 0.02, 0.012, 0.012]), pad(0.022, 0.004, DARK), y_band, cap=DARK) for dx in (-0.026, 0.0, 0.026): terrace(bm, rrect(0.012, 0.042, 0.004, 2, dx, -0.4825), pad(0.003, 0.001, STEEL, 2), -0.1005, cap=STEEL) # left: indicator slot (orange) in a light bezel terrace(bm, sector(0.355, 0.435, 156.0, 204.0, fr=0.03), bezel(0.010, 0.010, 0.004, LIGHT, DARK), y_face, cap=DARK) terrace(bm, sector(0.372, 0.418, 160.0, 177.0, fr=0.012), pad(0.0045, 0.0015, ORANGE), y_face - 0.0035, cap=ORANGE) terrace(bm, sector(0.372, 0.418, 181.0, 200.0, fr=0.012), pad(0.0035, 0.0012, LIGHT), y_face - 0.0035, cap=LIGHT) # small gauge upper left button(bm, -0.20, 0.19, [(STEEL, [(0.042, y_face, 0), (0.042, -0.095, 0.003), (0.030, -0.097, 0.002), (0.028, -0.093, 0)]), (DARK, [(0.028, -0.093, 0), (0.0, -0.093, 0)])], n=32) # right: curved cover plate with a groove and an amber pill terrace(bm, sector(0.25, 0.38, -40.0, 32.0, fr=0.025), pad(0.008, 0.003, LIGHT), y_face, cap=LIGHT) terrace(bm, sector(0.296, 0.306, -30.0, 22.0, fr=0.0049), pad(0.0012, 0.0005, DARK, 1), y_face - 0.0075, cap=DARK) px, pz = polar(0.345, -8.0) terrace(bm, rrect(0.022, 0.072, 0.0105, 3, px, pz), pad(0.004, 0.0015, AMBER), y_face - 0.0075, cap=AMBER) # manual release valve upper right vx, vz = polar(0.32, 50.0) button(bm, vx, vz, [(DARK, [(0.055, y_face, 0), (0.055, -0.095, 0.004), (0.044, -0.095, 0)]), (STEEL, [(0.044, -0.095, 0), (0.044, -0.107, 0.004), (0.024, -0.107, 0.003), (0.022, -0.103, 0)]), (AMBER, [(0.022, -0.103, 0), (0.0, -0.103, 0)])], n=32) # upper trim plate, label, bottom slot terrace(bm, sector(0.43, 0.49, 52.0, 78.0, fr=0.02), pad(0.005, 0.002, LIGHT), y_face, cap=LIGHT) terrace(bm, rrect(0.10, 0.035, 0.006, 3, -0.02, 0.33), pad(0.002, 0.0008, AMBER, 2), y_face, cap=AMBER) terrace(bm, rrect(0.07, 0.008, 0.0035, 2, -0.02, 0.33), pad(0.001, 0.0004, DARK, 1), y_face - 0.0018, cap=DARK) terrace(bm, rrect(0.13, 0.022, 0.009, 3, 0.0, -0.33), pad(0.003, 0.001, DARK, 2), y_face, cap=DARK) # screws round the band (not under the tabs or the arm) for k in range(16): a = 11.25 + 22.5 * k if min(abs((a - c + 180.0) % 360.0 - 180.0) for c in (0.0, 90.0, 270.0)) < 20.0: continue sx, sz = polar(0.544, a) button(bm, sx, sz, [(STEEL, [(0.008, y_band, 0), (0.008, -0.083, 0.002), (0.0, -0.0845, 0)])], n=12) # hinge arm: a standoff on the leaf, the arm over the seal ring, the knuckle round the pin, a handle, a hazard plate rbox(bm, (0.13, 0.046, 0.17), 0.008, 3, M=Matrix.Translation((0.485, -0.102, 0.0)), mat=LIGHT) rbox(bm, (0.27, 0.05, 0.17), 0.010, 3, M=Matrix.Translation((0.555, -0.150, 0.0)), mat=LIGHT) lathe(bm, prof([(0.040, 0.085, 0), (0.040, -0.085, 0.004), (0.023, -0.085, 0.002), (0.023, 0.085, 0.002), (0.040, 0.085, 0)], 2), 32, center=(PIN.x, PIN.y, 0.0), axis='Z', mat=LIGHT) terrace(bm, rrect(0.11, 0.12, 0.012, 3, 0.60, 0.0), pad(0.002, 0.0008, HAZ, 2), -0.1745, cap=HAZ) tube(bm, fillet3([(0.47, -0.174, 0.055), (0.47, -0.210, 0.055), (0.47, -0.210, -0.055), (0.47, -0.174, -0.055)], 0.015, 4), 0.009, seg=12, caps=('flat', 'flat'), mat=STEEL) # back: beds and guide rails for the eight dogs, amber pads between them for phi in DOG_ANGLES: R = mat_rot_y(math.radians(phi)) d = Vector((math.cos(math.radians(phi)), 0.0, math.sin(math.radians(phi)))) t = Vector((-math.sin(math.radians(phi)), 0.0, math.cos(math.radians(phi)))) rbox(bm, (0.15, 0.020, 0.092), 0.004, 2, M=Matrix.Translation(d * 0.395 + Vector((0, -0.006, 0))) @ R, mat=DARK) for s in (1, -1): rbox(bm, (0.135, 0.032, 0.012), 0.003, 2, M=Matrix.Translation(d * 0.3975 + t * (0.040 * s) + Vector((0, 0.019, 0))) @ R, mat=DARK) pa = math.radians(phi + 22.5) dp = Vector((math.cos(pa), 0.0, math.sin(pa))) rbox(bm, (0.07, 0.009, 0.045), 0.003, 2, M=Matrix.Translation(dp * 0.42 + Vector((0, -0.0115, 0))) @ mat_rot_y(pa), mat=AMBER) def build_cam(bm): """the ring that drives the dogs, on the leaf's back round the hub; built in place, pivot CAM_O""" lathe(bm, prof([(0.275, 0.020, 0), (0.275, -0.012, 0.003), (0.212, -0.012, 0.003), (0.212, 0.020, 0.003), (0.275, 0.020, 0)], 2), 64, axis='Y', mat=STEEL) for phi in DOG_ANGLES: pa = math.radians(phi + 22.5) dp = Vector((math.cos(pa), 0.0, math.sin(pa))) rbox(bm, (0.045, 0.024, 0.05), 0.005, 2, M=Matrix.Translation(dp * 0.2855 + Vector((0, 0.006, 0))) @ mat_rot_y(pa), mat=STEEL) def build_dog(bm): """one locking dog, built at the origin pointing along +X (outward); the eight objects share this mesh""" rbox(bm, (0.12, 0.04, 0.06), 0.008, 3, mat=WHITE) tube(bm, [(-0.025, 0.019, 0.0), (-0.025, 0.030, 0.0)], 0.012, seg=16, caps=('flat', 'flat'), mat=STEEL) # ------------------------------------------------------------------ all OUR = ("SM_PressureHatch_", "PressureHatch_Root") def clear(): for ob in list(bpy.data.objects): if ob.name.startswith(OUR) and not ob.name.startswith("UCX_"): bpy.data.objects.remove(ob, do_unlink=True) for me in list(bpy.data.meshes): if me.name.startswith("SM_PressureHatch_") and me.users == 0: bpy.data.meshes.remove(me) def parent_to(ob, parent, world_loc): ob.parent = parent ob.matrix_parent_inverse.identity() ob.location = Vector(world_loc) - parent.matrix_world.translation if parent.type == 'EMPTY' else \ parent.matrix_world.inverted() @ Vector(world_loc) def build_all(): M = materials() top = collection("PressureHatch") st = collection("PH | static", top) mv = collection("PH | moving", top) clear() root = bpy.data.objects.new("PressureHatch_Root", None) root.empty_display_type = 'PLAIN_AXES' root.empty_display_size = 0.3 top.objects.link(root) if "open" not in root: root["open"] = 0.0 bm = bmesh.new(); build_frame(bm) frame = finish(bm, "SM_PressureHatch_Frame", st, M) bm = bmesh.new(); build_bulkhead(bm) bulk = finish(bm, "SM_PressureHatch_Bulkhead", st, M) bm = bmesh.new(); build_leaf(bm) leaf = finish(bm, "SM_PressureHatch_Leaf", mv, M, origin=PIN) bm = bmesh.new(); build_cam(bm) cam = finish(bm, "SM_PressureHatch_Cam", mv, M, origin=CAM_O) bm = bmesh.new(); build_dog(bm) dog = finish(bm, "SM_PressureHatch_Dog_01", mv, M) dog.data.name = "SM_PressureHatch_Dog" for ob in (frame, bulk, leaf): parent_to(ob, root, ob.location.copy()) bpy.context.view_layer.update() parent_to(cam, leaf, CAM_O) dogs = [dog] for i in range(1, len(DOG_ANGLES)): o = bpy.data.objects.new("SM_PressureHatch_Dog_%02d" % (i + 1), dog.data) mv.objects.link(o) dogs.append(o) for o, phi in zip(dogs, DOG_ANGLES): a = math.radians(phi) o["phi_deg"] = phi o.rotation_euler = (0.0, -a, 0.0) parent_to(o, leaf, (DOG_R_LOCKED * math.cos(a), DOG_Y, DOG_R_LOCKED * math.sin(a))) for o in [leaf, cam] + dogs: o["closed_location"] = list(o.location) o["closed_rotation"] = list(o.rotation_euler) bpy.context.view_layer.update() return [audit(o) for o in (frame, bulk, leaf, cam, dog)]