""" Crew quarters and captain's quarters on the command deck, either side of the control room corridor. The user, 2026-10-13, with two red rectangles on the command deck plan (the dark areas between the corridor's walls and the hull, from the sub bay's bulkhead to the bridge's): "I want rooms here create me rooms for Crew quarters and captains quarters" What is there: beside the corridor the deck is 6 m wide each side, but the hull curves in over it (it meets the roof pod's foot 3.3 m above the deck, 5.2 m from the centre line). The packs' walls are 3.9 to 4.8 m tall, so none of them can stand on the outer side. The user's rule for that, from 2026-10-12: "if its hard to make around the ships shape you can build rectangle hull lining or heptagon, or hexagon". So each room is a straight lining with a folded section that follows the hull: a knee wall, three folds and a flat ceiling strip along the corridor's wall. * the lining is made here: satin white panels on a dark backing between titanium frames 1.2 m apart (the dark is the Props pack's plain dark material, the titanium the ship's own; the white is a paint made here, because the pack's plain light material is a polished metal and every lamp showed in it); * the floor is the Props pack's floor panel on a deck made for this hull; the doors are the two side doors the corridor already had; * the furniture is the Props pack's (desks, cabinets, shelves, wall cabinets, stools); neither pack has a bed, so the berths and the captain's bed are made here; * crew quarters to port: six berths in alcoves under the hull's slope, each with a locker; a storage wall and a mess bench along the corridor's wall; * captain's quarters to starboard: since the same afternoon the cabin that another session modelled from the user's reference picture (captains_cabin.py sets it in; CAPTAIN = "cabin"). The first version, made here, is kept behind CAPTAIN = "lined": bed at the aft end behind a shelf, a briefing table, the desk at the fore end. Frames: ship frame (+X bow, +Y port, Z up, metres). Every mesh made here is drawn for the PORT side, in a local frame with z = 0 on the floor; starboard pieces are the same meshes turned half a turn about Z (no mirrored scales), which is why the repeating ones are symmetric fore and aft. import quarters quarters.build() # in the open file; refuses if the rooms are already there quarters.remove() # takes them out again """ import math import bmesh import bpy from mathutils import Matrix, Vector import shipfit # ---- plan ------------------------------------------------------------------------------------------------------- ROOT = "M_SHIP_INTERIOR_S2" DECK = "10.5 | Command deck" MARKERS = "10.8 | Markers (game hooks)" ROOMS = (("crew", 1.0, "10.5 | Command deck | Crew quarters"), ("captain", -1.0, "10.5 | Command deck | Captain's quarters")) X_A, X_B = 15.86, 30.26 # the rooms' end walls = the corridor car's own two ends (four 3.6 m bays) PITCH = 1.2 # frame spacing: twelve fields FIELDS = 12 DOOR_FIELDS = (6, 7, 8) # x 23.06..26.66: the corridor's side door stands in the lining there Y_IN = 3.0 # the lining's face on the corridor side (the side door's frame ends at 2.998) Z_F = 3.14 # floor = the corridor's door sills H = 3.46 # ceiling above the floor (z 6.60: over the lamp above the side door) BREAKS = (0.90, 1.96, 2.91) # above the floor: top of the knee wall, and the vault's two folds CLEAR = 0.10 # the lining's face stays this far inside the hull's skin, at least REC = 0.03 # the dark backing lies this far behind the panels' face GAP = 0.02 # shadow gap between two panels RIB_W = 0.10 # a frame's width (half of it on each of two neighbouring fields) RIB_OUT = 0.03 # a frame stands this far proud of the panels CHAMFER = 0.010 SKIRT = 0.10 FLOOR_T = 0.207 # the Props floor panel's thickness BERTH = 2.4 # a berth with its locker Y_BERTH = 6.55 # the lockers' fronts NAME = "Quarters | " CAPTAIN = "cabin" # the captain's room: "cabin" = only the deck here, the cabin of models/captains-quarters is # set in by captains_cabin.port() (the user, 2026-10-13: "Port Captains quarters design from # my other session into the room ... delete anything inside"); "lined" = this file's own # first version (white folded lining, bed, briefing table, desk) def log(msg): print("[quarters] " + str(msg)) # ---- the hull as these rooms see it ----------------------------------------------------------------------------- class Shell(object): """The hull's drawn faces, without the ones the pod-opening mask hides (they lie above z 6.76, over these rooms' ceilings). Unlike shipfit.Envelope, hull skin under the pod's foot counts: it is a real surface in the room.""" def __init__(self, hull="M_SHIP_Hull"): self.tree = shipfit.drawn_tree(bpy.data.objects[hull], evaluated=True) def depth(self, p): """How far p lies inside the skin (negative: outside).""" p = Vector(p) loc, nrm, idx, d = self.tree.find_nearest(p) return d if (p - loc).dot(nrm) < 0.0 else -d def solve_section(shell=None, step=0.01): """The room's section on the port side as (y, z above the floor), going round the lining from the foot of the corridor-side wall, up, across and down to the foot of the knee wall. One section for the whole length: every fold is as far out as the hull allows at its narrowest station, with CLEAR to spare.""" shell = shell or Shell() xs = [X_A - 0.35 + 0.5 * k for k in range(int((X_B + 0.45 - X_A + 0.35) / 0.5) + 2)] xs = [x for x in xs if x <= X_B + 0.45] + [X_B + 0.45] def reach(zl): y = 9.5 while y > Y_IN + 0.5: if all(shell.depth((x, y, Z_F + zl)) >= CLEAR for x in xs): return math.floor(y / step) * step y -= step / 2 raise RuntimeError("no room at %.2f m above the floor" % zl) zk, za, zb = BREAKS yk, ya, yb, yc = reach(zk), reach(za), reach(zb), reach(H) yk = min(yk, reach(0.0), reach(zk * 0.5)) pts = [(Y_IN, 0.0), (Y_IN, H), (yc, H), (yb, zb), (ya, za), (yk, zk), (yk, 0.0)] worst = 1e9 for (y0, z0), (y1, z1) in zip(pts[1:-1], pts[2:]): for k in range(1, 8): t = k / 8.0 for x in xs: worst = min(worst, shell.depth((x, y0 + (y1 - y0) * t, Z_F + z0 + (z1 - z0) * t))) if worst < CLEAR - 0.02: raise RuntimeError("a fold of the section comes within %.3f m of the hull" % worst) return {"pts": [(round(y, 3), round(z, 3)) for y, z in pts], "least_depth_between_folds": round(worst, 3)} def vault_z(sec, y): """Height of the lining's face above the floor at distance y from the centre line.""" pts = sec["pts"][1:] # ceiling corner ... knee foot, y increasing if y <= pts[0][0]: return pts[0][1] for (y0, z0), (y1, z1) in zip(pts[:-1], pts[1:]): if y0 <= y <= y1 and y1 > y0: return z0 + (z1 - z0) * (y - y0) / (y1 - y0) return 0.0 # ---- materials --------------------------------------------------------------------------------------------------- def _own_material(name, color, rough, metal=0.0, sheen=0.0): m = bpy.data.materials.get(name) if m is None: m = bpy.data.materials.new(name) m.use_nodes = True b = next(n for n in m.node_tree.nodes if n.bl_idname == "ShaderNodeBsdfPrincipled") b.inputs["Base Color"].default_value = tuple(color) + (1.0,) b.inputs["Roughness"].default_value = rough b.inputs["Metallic"].default_value = metal if sheen and "Sheen Weight" in b.inputs: b.inputs["Sheen Weight"].default_value = sheen m.diffuse_color = tuple(color) + (1.0,) return m def materials(): need = ("PR_MI_Base_Light", "PR_MI_Base_Dark", "PR_MI_Light_White", "EN_MI_FloorPadding", "PBR | Brushed titanium", "PBR | Graphite lower hull") missing = [n for n in need if bpy.data.materials.get(n) is None] if missing: raise RuntimeError("materials missing in this file: %s" % missing) g = bpy.data.materials.get return {"light": _own_material("QUARTERS | lining, satin white", (0.74, 0.75, 0.75), 0.48), "dark": g("PR_MI_Base_Dark"), "glow": g("PR_MI_Light_White"), "quilt": g("EN_MI_FloorPadding"), "frame": g("PBR | Brushed titanium"), "deck": g("PBR | Graphite lower hull"), "mattress": _own_material("QUARTERS | mattress ticking", (0.30, 0.33, 0.37), 0.92, sheen=0.15), "pillow": _own_material("QUARTERS | pillow", (0.78, 0.79, 0.78), 0.95, sheen=0.2), "blanket": _own_material("QUARTERS | blanket, crew (slate)", (0.07, 0.10, 0.135), 0.9, sheen=0.2), "blanket_captain": _own_material("QUARTERS | blanket, captain (dark red)", (0.15, 0.018, 0.022), 0.88, sheen=0.12)} # ---- a small mesh builder: quads, flat or smooth, face-frame UVs ------------------------------------------------- class MB(object): def __init__(self, mats, uv_scale=None): self.bm = bmesh.new() self.mats = list(mats) self.uv_scale = uv_scale or {} self._cache = None def part(self): """Start a new shell: vertices are shared inside a part and never between two parts.""" self._cache = {} def _v(self, co): k = (round(co[0], 5), round(co[1], 5), round(co[2], 5)) v = self._cache.get(k) if v is None: v = self.bm.verts.new(co) self._cache[k] = v return v def face(self, pts, mat, toward, smooth=False): """A face through pts that looks along `toward` (a direction).""" vs = [self._v(p) for p in pts] if len(set(vs)) < 3: return None try: f = self.bm.faces.new(vs) except ValueError: return None f.material_index = self.mats.index(mat) f.smooth = smooth f.normal_update() if f.normal.dot(Vector(toward)) < 0.0: f.normal_flip() return f def box(self, lo, hi, mat, faces="xXyYzZ", mat_of=None): """Axis-aligned box; `faces` names the sides to make (lower case = the low side of that axis).""" self.part() (x0, y0, z0), (x1, y1, z1) = lo, hi sides = {"x": ([(x0, y0, z0), (x0, y1, z0), (x0, y1, z1), (x0, y0, z1)], (-1, 0, 0)), "X": ([(x1, y0, z0), (x1, y1, z0), (x1, y1, z1), (x1, y0, z1)], (1, 0, 0)), "y": ([(x0, y0, z0), (x1, y0, z0), (x1, y0, z1), (x0, y0, z1)], (0, -1, 0)), "Y": ([(x0, y1, z0), (x1, y1, z0), (x1, y1, z1), (x0, y1, z1)], (0, 1, 0)), "z": ([(x0, y0, z0), (x1, y0, z0), (x1, y1, z0), (x0, y1, z0)], (0, 0, -1)), "Z": ([(x0, y0, z1), (x1, y0, z1), (x1, y1, z1), (x0, y1, z1)], (0, 0, 1))} for k in faces: pts, n = sides[k] self.face(pts, (mat_of or {}).get(k, mat), n) def tile(self, corners, n, mat, height=REC, inset=GAP / 2, chamfer=CHAMFER, side_mat=None): """A raised panel: `corners` (four points, in order) lie on the backing; the panel's outline is that quad with every edge moved in by `inset` (one number, or one per edge), its face `height` above the backing along n, with a chamfered rim. Nine quads.""" self.part() n = Vector(n).normalized() c = [Vector(p) for p in corners] u = (c[1] - c[0]).normalized() w = n.cross(u).normalized() o = c[0] p2 = [((p - o).dot(u), (p - o).dot(w)) for p in c] ins = [inset] * 4 if isinstance(inset, (int, float)) else list(inset) base = _inset(p2, ins) top = _inset(base, [chamfer] * 4) def w3(q, h): return o + u * q[0] + w * q[1] + n * h side_mat = side_mat or mat cm = Vector((sum(q[0] for q in base) / 4.0, sum(q[1] for q in base) / 4.0)) for i in range(4): j = (i + 1) % 4 mid = (Vector(base[i]) + Vector(base[j])) / 2.0 - cm out = u * mid[0] + w * mid[1] self.face([w3(base[i], 0.0), w3(base[j], 0.0), w3(base[j], height - chamfer), w3(base[i], height - chamfer)], side_mat, out) self.face([w3(base[i], height - chamfer), w3(base[j], height - chamfer), w3(top[j], height), w3(top[i], height)], mat, out + n) self.face([w3(q, height) for q in top], mat, n) def cushion(self, lo, hi, mat, r=0.03, cut=0.03, crown=0.0): """A soft slab (mattress, pillow, folded blanket): corners cut in plan, the top edge rounded in two steps, smooth shaded. No underside.""" self.part() (x0, y0, z0), (x1, y1, z1) = lo, hi def ring(d, z): a, b, c2, e = x0 + d, y0 + d, x1 - d, y1 - d k = max(cut - d * 0.4, 0.004) return [(a + k, b, z), (c2 - k, b, z), (c2, b + k, z), (c2, e - k, z), (c2 - k, e, z), (a + k, e, z), (a, e - k, z), (a, b + k, z)] rings = [ring(0.0, z0), ring(0.0, z1 - r), ring(0.293 * r, z1 - 0.293 * r), ring(r, z1)] cx, cy = (x0 + x1) / 2.0, (y0 + y1) / 2.0 for ra, rb in zip(rings[:-1], rings[1:]): for i in range(8): j = (i + 1) % 8 mid = (Vector(ra[i]) + Vector(ra[j])) / 2.0 out = Vector((mid.x - cx, mid.y - cy, 0.0)) if rb is rings[-1]: out = out.normalized() + Vector((0, 0, 1.0)) self.face([ra[i], ra[j], rb[j], rb[i]], mat, out, smooth=True) t = rings[-1] if crown > 0.0: # the top swells a little towards its middle m0, m1 = (x0 + (x1 - x0) * 0.3, y0 + (y1 - y0) * 0.3), (x1 - (x1 - x0) * 0.3, y1 - (y1 - y0) * 0.3) mid = [(m0[0], m0[1], z1 + crown), (m1[0], m0[1], z1 + crown), (m1[0], m1[1], z1 + crown), (m0[0], m1[1], z1 + crown)] self.face(mid, mat, (0, 0, 1), smooth=True) self.face([t[0], t[1], mid[1], mid[0]], mat, (0, 0, 1), smooth=True) self.face([t[1], t[2], t[3], mid[1]], mat, (0, 0, 1), smooth=True) # corner self.face([t[3], t[4], mid[2], mid[1]], mat, (0, 0, 1), smooth=True) self.face([t[4], t[5], mid[3], mid[2]], mat, (0, 0, 1), smooth=True) self.face([t[5], t[6], t[7], mid[3]], mat, (0, 0, 1), smooth=True) # corner self.face([t[7], t[0], mid[0], mid[3]], mat, (0, 0, 1), smooth=True) else: self.face([t[0], t[1], t[2], t[7]], mat, (0, 0, 1), smooth=True) self.face([t[7], t[2], t[3], t[6]], mat, (0, 0, 1), smooth=True) self.face([t[6], t[3], t[4], t[5]], mat, (0, 0, 1), smooth=True) def finish(self, name, note=None): bm = self.bm bm.normal_update() uv = bm.loops.layers.uv.verify() for f in bm.faces: n = f.normal t = Vector((1.0, 0.0, 0.0)) if abs(n.x) < 0.9 else Vector((0.0, 1.0, 0.0)) t = (t - n * t.dot(n)).normalized() b = n.cross(t) s = self.uv_scale.get(self.mats[f.material_index].name, 0.3) for l in f.loops: l[uv].uv = (l.vert.co.dot(t) * s, l.vert.co.dot(b) * s) me = bpy.data.meshes.new(name) bm.to_mesh(me) bm.free() for m in self.mats: me.materials.append(m) # leave out the slots no face uses used = sorted({p.material_index for p in me.polygons}) if len(used) < len(self.mats): remap = {old: new for new, old in enumerate(used)} for p in me.polygons: p.material_index = remap[p.material_index] keep = [self.mats[i] for i in used] me.materials.clear() for m in keep: me.materials.append(m) me["made_for"] = "M-Ship v2 command deck quarters (build/quarters.py)" if note: me["note"] = note return me def _inset(poly, dist): """Move every edge of a convex 2D polygon in by its own distance; the new corners.""" n = len(poly) area = sum(poly[i][0] * poly[(i + 1) % n][1] - poly[(i + 1) % n][0] * poly[i][1] for i in range(n)) sgn = 1.0 if area > 0 else -1.0 lines = [] for i in range(n): a, b = Vector(poly[i]), Vector(poly[(i + 1) % n]) d = (b - a).normalized() nrm = Vector((-d.y, d.x)) * sgn lines.append((a + nrm * dist[i], d)) out = [] for i in range(n): (p, d), (q, e) = lines[i - 1], lines[i] den = d.x * e.y - d.y * e.x if abs(den) < 1e-9: out.append((q.x, q.y)) continue t = ((q.x - p.x) * e.y - (q.y - p.y) * e.x) / den out.append((p.x + d.x * t, p.y + d.y * t)) return out def _offset_path(pts, d): """A (y, z) polyline moved sideways by d (positive: towards the room, which lies to the right of the path's direction turned a quarter turn, see _normal), with mitred corners.""" lines = [] for a, b in zip(pts[:-1], pts[1:]): a, b = Vector(a), Vector(b) t = (b - a).normalized() n = Vector((t.y, -t.x)) lines.append((a + n * d, t, b + n * d)) out = [tuple(lines[0][0])] for (p, t, pe), (q, e, qe) in zip(lines[:-1], lines[1:]): den = t.x * e.y - t.y * e.x if abs(den) < 1e-9: out.append(tuple(q)) continue k = ((q.x - p.x) * e.y - (q.y - p.y) * e.x) / den out.append((p.x + t.x * k, p.y + t.y * k)) out.append(tuple(lines[-1][2])) return out def _normal(a, b): """Room-side normal of the lining path from a to b, in (y, z).""" t = (Vector(b) - Vector(a)).normalized() return Vector((t.y, -t.x)) # ---- the lining -------------------------------------------------------------------------------------------------- def lining_bay(sec, mats, wall=True, name="lining bay"): """One 1.2 m field of the lining, round the whole section: corridor-side wall (left out in the door fields), ceiling strip, the vault's three folds and the knee wall. Dark backing, one raised panel per fold and four up the wall, half a frame at each side, skirtings. Symmetric fore and aft. Open at the back, like the packs' own wall pieces.""" L, D, F, G = mats["light"], mats["dark"], mats["frame"], mats["glow"] mb = MB([L, D, F, G]) pts = sec["pts"] zk, za, zb = BREAKS path = pts if wall else pts[1:] back = _offset_path(path, -REC) front = _offset_path(path, RIB_OUT) hx = PITCH / 2.0 # backing mb.part() for i, (a, b) in enumerate(zip(back[:-1], back[1:])): n = _normal(path[i], path[i + 1]) mb.face([(-hx, a[0], a[1]), (hx, a[0], a[1]), (hx, b[0], b[1]), (-hx, b[0], b[1])], D, (0.0, n.x, n.y)) # half frames for s in (-1.0, 1.0): xo, xi = s * hx, s * (hx - RIB_W / 2.0) mb.part() for i, (a, b) in enumerate(zip(front[:-1], front[1:])): n = _normal(path[i], path[i + 1]) ba, bb = back[i], back[i + 1] mb.face([(xo, a[0], a[1]), (xi, a[0], a[1]), (xi, b[0], b[1]), (xo, b[0], b[1])], F, (0.0, n.x, n.y)) mb.face([(xi, a[0], a[1]), (xi, b[0], b[1]), (xi, bb[0], bb[1]), (xi, ba[0], ba[1])], F, (-s, 0.0, 0.0)) # panels x0, x1 = -hx + RIB_W / 2.0 + GAP / 2.0, hx - RIB_W / 2.0 - GAP / 2.0 def panel(a, b, n): """a, b: (y, z) on the path; the panel's cell runs from a to b on the backing.""" a3 = Vector((0.0, a[0], a[1])) - Vector((0.0, n.x, n.y)) * REC b3 = Vector((0.0, b[0], b[1])) - Vector((0.0, n.x, n.y)) * REC mb.tile([a3 + Vector((x0, 0, 0)), a3 + Vector((x1, 0, 0)), b3 + Vector((x1, 0, 0)), b3 + Vector((x0, 0, 0))], (0.0, n.x, n.y), L, side_mat=L) if wall: n = _normal(pts[0], pts[1]) rows = [SKIRT + 0.005, zk, za, zb, H] for r0, r1 in zip(rows[:-1], rows[1:]): panel((Y_IN, r0), (Y_IN, r1), n) mb.box((-hx, Y_IN - REC, 0.0), (hx, Y_IN + 0.012, SKIRT), D, faces="YZ") for a, b in zip(pts[1:-2], pts[2:-1]): panel(a, b, _normal(a, b)) yk = pts[-1][0] panel((yk, zk), (yk, SKIRT + 0.005), _normal(pts[-2], pts[-1])) # a light line in the fold between the ceiling strip and the vault (the gap between the two panels there) c = Vector(pts[2]) n1, n2 = _normal(pts[1], pts[2]), _normal(pts[2], pts[3]) t1, t2 = (Vector(pts[1]) - c).normalized(), (Vector(pts[3]) - c).normalized() d = REC - 0.012 a = c + t1 * (GAP / 2.0 + 0.004) - n1 * d b = c + t2 * (GAP / 2.0 + 0.004) - n2 * d nm = (n1 + n2).normalized() mb.part() mb.face([(x0, a.x, a.y), (x1, a.x, a.y), (x1, b.x, b.y), (x0, b.x, b.y)], G, (0.0, nm.x, nm.y)) # skirting at the knee wall: it also covers the floor panels' edge mb.box((-hx, yk - 0.17, 0.0), (hx, yk + REC, SKIRT), D, faces="yZ") return mb.finish(NAME + name, "one 1.2 m field of the rooms' lining; port side as drawn, starboard turned half a turn about Z") def end_wall(sec, mats, faces_bow=True, name="end wall", quilt=()): """The wall across a room's end: the section's shape, panelled on the lining's lines (columns under the vault's folds, rows at their heights). faces_bow: its room side looks towards +X. quilt: cells (column, row) that are padded instead of plain.""" L, D, Q = mats["light"], mats["dark"], mats["quilt"] mb = MB([L, D, Q], uv_scale={Q.name: 1.4}) s = 1.0 if faces_bow else -1.0 n = (s, 0.0, 0.0) pts = sec["pts"] zk, za, zb = BREAKS yc, yb, ya, yk = pts[2][0], pts[3][0], pts[4][0], pts[5][0] xb = -s * REC # backing: the section as four quads mb.part() for quad in ([(Y_IN, 0), (yc, 0), (yc, H), (Y_IN, H)], [(yc, 0), (yb, 0), (yb, zb), (yc, H)], [(yb, 0), (ya, 0), (ya, za), (yb, zb)], [(ya, 0), (yk, 0), (yk, zk), (ya, za)]): mb.face([(xb, y, z) for y, z in quad], D, n) edge = RIB_OUT + GAP # at the perimeter the first frame of the lining covers this much g = GAP / 2.0 cells = [] # (column, row, corners (y, z), per-edge insets) cells += [(0, r, [(Y_IN, z0), (yc, z0), (yc, z1), (Y_IN, z1)], [edge if r == 0 else g, g, edge if r == 3 else g, edge]) for r, (z0, z1) in enumerate(zip((0.0, zk, za, zb), (zk, za, zb, H)))] cells += [(1, 0, [(yc, 0.0), (yb, 0.0), (yb, zk), (yc, zk)], [edge, g, g, g]), (1, 1, [(yc, zk), (yb, zk), (yb, za), (yc, za)], [g, g, g, g]), (1, 2, [(yc, za), (yb, za), (yb, zb), (yc, H)], [g, g, edge, g]), (2, 0, [(yb, 0.0), (ya, 0.0), (ya, zk), (yb, zk)], [edge, g, g, g]), (2, 1, [(yb, zk), (ya, zk), (ya, za), (yb, zb)], [g, g, edge, g]), (3, 0, [(ya, 0.0), (yk, 0.0), (yk, zk), (ya, za)], [edge, edge, edge, g])] for col, row, quad, ins in cells: m = Q if (col, row) in quilt else L mb.tile([(xb, y, z) for y, z in quad], n, m, inset=ins, side_mat=L if m is Q else None) return mb.finish(NAME + name, "a room's end wall; port side as drawn, starboard turned half a turn about Z") # ---- the berth --------------------------------------------------------------------------------------------------- def berth(sec, mats, name="berth"): """A crew berth with its locker, 2.4 m of the outer wall: tall locker at the head (its side is the partition to the next berth), bed box with two drawers, a leeboard, a shelf along the knee wall. x 0..2.4 along the wall.""" L, D, F = mats["light"], mats["dark"], mats["frame"] mb = MB([L, D, F]) pts = sec["pts"] yk, ya = pts[5][0], pts[4][0] zk, za, zb = BREAKS yf = Y_BERTH # the locker's front top_f = vault_z(sec, yf) - 0.04 # its height at the front (the frames stand 3 cm proud of the vault) top_a = za - 0.04 lw = 0.40 # the locker's width yb0 = yk - RIB_OUT - 0.006 # its back: just clear of the frames ysk = yk - 0.17 - 0.004 # ... and of the lining's skirting, below # locker: front, and the two sides that follow the vault mb.box((0.0, yf, 0.0), (lw, yf + 0.02, SKIRT), D, faces="y") # recessed plinth mb.part() mb.face([(0.0, yf - 0.005, SKIRT), (lw, yf - 0.005, SKIRT), (lw, yf - 0.005, top_f), (0.0, yf - 0.005, top_f)], D, (0, -1, 0)) mb.tile([(0.0, yf - 0.005, SKIRT), (lw, yf - 0.005, SKIRT), (lw, yf - 0.005, 1.32), (0.0, yf - 0.005, 1.32)], (0, -1, 0), L, height=0.022, inset=[0.012, 0.014, 0.008, 0.014]) mb.tile([(0.0, yf - 0.005, 1.32), (lw, yf - 0.005, 1.32), (lw, yf - 0.005, top_f), (0.0, yf - 0.005, top_f)], (0, -1, 0), L, height=0.022, inset=[0.008, 0.014, 0.012, 0.014]) for hz in (1.20, 1.44): # two pull bars mb.box((lw - 0.085, yf - 0.047, hz - 0.07), (lw - 0.06, yf - 0.027, hz + 0.07), F, faces="xXyzZ") for x, sx in ((0.0, -1.0), (lw, 1.0)): mb.part() mb.face([(x, yf - 0.005, SKIRT), (x, ya, SKIRT), (x, ya, top_a), (x, yf - 0.005, top_f)], L, (sx, 0, 0)) mb.face([(x, ya, SKIRT), (x, yb0, SKIRT), (x, yb0, zk - 0.04), (x, ya, top_a)], L, (sx, 0, 0)) mb.face([(x, yf - 0.005, 0.0), (x, ysk, 0.0), (x, ysk, SKIRT), (x, yf - 0.005, SKIRT)], D, (sx, 0, 0)) # bed box x0, x1 = lw, BERTH y0, y1 = yf + 0.05, yk - 0.34 # front, and where the shelf begins zt = 0.36 mb.box((x0, y0 + 0.03, 0.0), (x1, y0 + 0.05, SKIRT), D, faces="y") mb.part() mb.face([(x0, y0, SKIRT), (x1, y0, SKIRT), (x1, y0, zt), (x0, y0, zt)], D, (0, -1, 0)) mb.face([(x0, y0, zt), (x1, y0, zt), (x1, y1, zt), (x0, y1, zt)], L, (0, 0, 1)) xm = (x0 + x1) / 2.0 for xa, xb in ((x0, xm), (xm, x1)): mb.tile([(xa, y0, SKIRT), (xb, y0, SKIRT), (xb, y0, zt), (xa, y0, zt)], (0, -1, 0), L, height=0.022, inset=[0.012, 0.012, 0.012, 0.012]) xc = (xa + xb) / 2.0 mb.box((xc - 0.09, y0 - 0.042, 0.215), (xc + 0.09, y0 - 0.022, 0.240), F, faces="xXyzZ") # leeboard along the head half mb.box((x0 + 0.03, y0, zt), (x0 + 1.05, y0 + 0.028, zt + 0.19), L, faces="xXyYZ") # shelf along the knee wall mb.box((x0, y1, 0.0), (x1, yb0, 0.74), L, faces="yZ") return mb.finish(NAME + name, "crew berth with locker: 2.4 m of the outer wall; the bedding is a mesh of its own") def bedding(sec, mats, name="bedding"): """Mattress, pillow and folded blanket of a berth (same frame as berth()).""" M, P, B = mats["mattress"], mats["pillow"], mats["blanket"] mb = MB([M, P, B]) yk = sec["pts"][5][0] x0, x1 = 0.40, BERTH y0, y1 = Y_BERTH + 0.05, yk - 0.34 zt = 0.36 mb.cushion((x0 + 0.045, y0 + 0.04, zt), (x1 - 0.03, y1 - 0.012, zt + 0.14), M, r=0.035, cut=0.04) mb.cushion((x0 + 0.11, y0 + 0.17, zt + 0.14), (x0 + 0.56, y1 - 0.13, zt + 0.235), P, r=0.045, cut=0.07, crown=0.018) mb.cushion((x1 - 0.62, y0 + 0.10, zt + 0.14), (x1 - 0.09, y1 - 0.07, zt + 0.20), B, r=0.02, cut=0.03) mb.cushion((x1 - 0.59, y0 + 0.13, zt + 0.20), (x1 - 0.12, y1 - 0.10, zt + 0.255), B, r=0.02, cut=0.03) return mb.finish(NAME + name, "soft goods of a crew berth") # ---- the captain's bed ------------------------------------------------------------------------------------------- BED_W, BED_L = 1.60, 2.12 HEADBOARD = 0.09 def captains_bed(mats, name="captain's bed"): """A wide bed for the captain's quarters. Frame: origin on the floor at the middle of the head end, +X towards the foot; symmetric about its length, so it needs no mirroring on the starboard side. Padded headboard, a bedside table each side.""" L, D, F, Q = mats["light"], mats["dark"], mats["frame"], mats["quilt"] mb = MB([L, D, F, Q], uv_scale={Q.name: 1.4}) hw = BED_W / 2.0 zt = 0.34 hb = HEADBOARD # where the bed begins: the headboard slab (5 cm) and its padding # headboard: padded panel in a light frame mb.box((0.0, -hw - 0.55, 0.0), (hb - 0.04, hw + 0.55, 1.30), L, faces="XyYZ") mb.tile([(hb - 0.04, -hw - 0.02, zt + 0.10), (hb - 0.04, hw + 0.02, zt + 0.10), (hb - 0.04, hw + 0.02, 1.24), (hb - 0.04, -hw - 0.02, 1.24)], (1, 0, 0), Q, height=0.035, inset=0.0, chamfer=0.016, side_mat=Q) # bed box on a recessed plinth, a drawer each side and one at the foot mb.box((hb, -hw + 0.06, 0.0), (BED_L - 0.06, hw - 0.06, SKIRT), D, faces="XyY") mb.box((hb, -hw, SKIRT), (BED_L, hw, zt), D, faces="XyYZz") for sy in (-1.0, 1.0): y = sy * hw for xa, xb in ((hb + 0.55, hb + 1.25), (hb + 1.25, BED_L)): mb.tile([(xa, y, SKIRT), (xb, y, SKIRT), (xb, y, zt), (xa, y, zt)], (0, sy, 0), L, height=0.022, inset=0.012) xc = (xa + xb) / 2.0 ya, yb = sorted((y + sy * 0.022, y + sy * 0.042)) mb.box((xc - 0.09, ya, 0.20), (xc + 0.09, yb, 0.225), F, faces="xXyYzZ") mb.tile([(BED_L, -hw, SKIRT), (BED_L, hw, SKIRT), (BED_L, hw, zt), (BED_L, -hw, zt)], (1, 0, 0), L, height=0.022, inset=0.012) # bedside tables for sy in (-1.0, 1.0): ya, yb = sorted((sy * (hw + 0.06), sy * (hw + 0.53))) mb.box((hb, ya + 0.03, 0.0), (hb + 0.42, yb - 0.03, 0.06), D, faces="XyY") mb.box((hb, ya, 0.06), (hb + 0.46, yb, 0.52), L, faces="xXyYZ") mb.tile([(hb + 0.46, ya, 0.06), (hb + 0.46, yb, 0.06), (hb + 0.46, yb, 0.30), (hb + 0.46, ya, 0.30)], (1, 0, 0), L, height=0.018, inset=0.012) mb.tile([(hb + 0.46, ya, 0.30), (hb + 0.46, yb, 0.30), (hb + 0.46, yb, 0.52), (hb + 0.46, ya, 0.52)], (1, 0, 0), L, height=0.018, inset=0.012) return mb.finish(NAME + name, "captain's bed with headboard and two bedside tables; symmetric about its length") def captains_bedding(mats, name="captain's bedding"): M, P, B = mats["mattress"], mats["pillow"], mats["blanket_captain"] mb = MB([M, P, B]) hw = BED_W / 2.0 zt, hb = 0.34, HEADBOARD mb.cushion((hb + 0.01, -hw + 0.03, zt), (BED_L - 0.03, hw - 0.03, zt + 0.17), M, r=0.04, cut=0.05) for sy in (-1.0, 1.0): ya, yb = sorted((sy * 0.06, sy * (hw - 0.10))) mb.cushion((hb + 0.07, ya, zt + 0.17), (hb + 0.52, yb, zt + 0.27), P, r=0.045, cut=0.07, crown=0.02) # the cover, turned down below the pillows, and a folded throw across the foot mb.cushion((hb + 0.62, -hw + 0.012, zt + 0.10), (BED_L - 0.012, hw - 0.012, zt + 0.195), B, r=0.03, cut=0.05) mb.cushion((BED_L - 0.62, -hw + 0.06, zt + 0.195), (BED_L - 0.12, hw - 0.06, zt + 0.245), P, r=0.02, cut=0.03) return mb.finish(NAME + name, "soft goods of the captain's bed") # ---- placing ----------------------------------------------------------------------------------------------------- def get_collection(name, parent): c = bpy.data.collections.get(name) if c is None: c = bpy.data.collections.new(name) parent.children.link(c) return c def _link(ob, coll, root, m, zone): coll.objects.link(ob) if root is not None: ob.parent = root ob.matrix_world = m ob["zone"] = zone return ob def place_mesh(me, name, coll, root, m, zone): return _link(bpy.data.objects.new(name, me), coll, root, m, zone) def _corners(me): xs = [v.co.x for v in me.vertices] ys = [v.co.y for v in me.vertices] zs = [v.co.z for v in me.vertices] return [Vector((x, y, z)) for x in (min(xs), max(xs)) for y in (min(ys), max(ys)) for z in (min(zs), max(zs))] _bounds_cache = {} def put(kit_mesh, coll, root, zone, at, yaw=0.0, anchor="---", name=None, tilt=None, override=None): """One piece of a pack by its mesh name. `at` is where the piece's bounding box (after turning it by `yaw` degrees about Z, and by `tilt` = (axis, degrees) before that) touches: per axis '-' its low side, '0' its middle, '+' its high side.""" me = bpy.data.meshes.get(kit_mesh) if me is None: raise RuntimeError("kit mesh not in this file: " + kit_mesh) r = Matrix.Rotation(math.radians(yaw), 4, "Z") if tilt is not None: r = r @ Matrix.Rotation(math.radians(tilt[1]), 4, tilt[0]) if kit_mesh not in _bounds_cache: _bounds_cache[kit_mesh] = _corners(me) cs = [r @ c for c in _bounds_cache[kit_mesh]] ref = Vector() for i, a in enumerate(anchor): lo, hi = min(c[i] for c in cs), max(c[i] for c in cs) ref[i] = lo if a == "-" else hi if a == "+" else (lo + hi) / 2.0 m = Matrix.Translation(Vector(at) - ref) @ r ob = _link(bpy.data.objects.new(name or kit_mesh, me), coll, root, m, zone) if "ue_mesh" in me: ob["ue_mesh"] = me["ue_mesh"] if override: for idx, mat in override.items(): ob.material_slots[idx].link = "OBJECT" ob.material_slots[idx].material = mat return ob def area_lamp(coll, root, name, loc, watts, size, color=(1.0, 0.95, 0.88), zone="", shadow=True): d = bpy.data.lights.new(name, "AREA") d.shape = "RECTANGLE" d.size, d.size_y = size d.energy = watts d.color = color d.use_shadow = shadow try: d.shadow_maximum_resolution = 0.02 except Exception: # noqa: BLE001 pass ob = bpy.data.objects.new(name, d) _link(ob, coll, root, Matrix.Translation(loc), zone) # an area lamp shines down its -Z ob["ue_light_class"] = "RectLightComponent" return ob def point_lamp(coll, root, name, loc, watts, color=(1.0, 0.86, 0.70), radius=0.05, zone="", shadow=False): d = bpy.data.lights.new(name, "POINT") d.energy = watts d.color = color d.shadow_soft_size = radius d.use_shadow = shadow ob = bpy.data.objects.new(name, d) _link(ob, coll, root, Matrix.Translation(loc), zone) ob["ue_light_class"] = "PointLightComponent" return ob def marker(root, name, loc, role): coll = bpy.data.collections[MARKERS] e = bpy.data.objects.new("MARKER | " + name, None) e.empty_display_type = "PLAIN_AXES" e.empty_display_size = 0.5 coll.objects.link(e) e.parent = root e.matrix_world = Matrix.Translation(loc) e["ue_role"] = role e["made_by"] = "quarters" return e HALF_TURN = Matrix.Rotation(math.pi, 4, "Z") def exists(): return any(bpy.data.collections.get(c) is not None and len(bpy.data.collections[c].all_objects) > 0 for _, _, c in ROOMS) def remove(): """Take the two rooms out again (their objects, lamps, markers, collections; the meshes made here).""" n = 0 for _, _, cname in ROOMS: for sub in (cname + " | lights", cname): c = bpy.data.collections.get(sub) if c is None: continue for ob in list(c.objects): data = ob.data bpy.data.objects.remove(ob) n += 1 if isinstance(data, bpy.types.Light) and data.users == 0: bpy.data.lights.remove(data) bpy.data.collections.remove(c) for ob in [o for o in bpy.data.objects if o.name.startswith("MARKER | ") and o.get("made_by") == "quarters"]: bpy.data.objects.remove(ob) n += 1 for me in [m for m in bpy.data.meshes if m.name.startswith(NAME) and m.users == 0]: bpy.data.meshes.remove(me) return n def check(): """For check_interior.py: are the rooms there, how far inside the hull is everything of them (every vertex; the parts made here along their edges too), what are the meshes made here like, and does a piece of the packs stand in the lining.""" from mathutils.bvhtree import BVHTree out = {"present": exists()} if not out["present"]: return out shell = Shell() new = [] for key, s, cname in ROOMS: coll = bpy.data.collections[cname] lights = bpy.data.collections.get(cname + " | lights") objs = [o for o in coll.objects if o.type == "MESH"] new += objs out[key] = {"collection": cname, "mesh_objects": len(objs), "of_the_packs": sum(1 for o in objs if o.get("ue_mesh")), "of_the_cabin_model": sum(1 for o in objs if o.get("model_part")), "made_here": sum(1 for o in objs if not o.get("ue_mesh") and not o.get("model_part")), "lamps": len(lights.objects) if lights else 0} worst, near, n = (1e9, None), [], 0 for o in new: mw, me = o.matrix_world, o.data pts = [mw @ v.co for v in me.vertices] if not o.get("ue_mesh"): for e in me.edges: a, b = pts[e.vertices[0]], pts[e.vertices[1]] k = max(1, int((b - a).length / 0.05)) pts += [a.lerp(b, j / k) for j in range(1, k)] d = min(shell.depth(p) for p in pts) n += len(pts) if d < worst[0]: worst = (d, o.name) if d < 0.02: near.append([o.name, round(d, 4)]) out["points_measured"] = n out["least_depth_inside_the_hull_m"] = [round(worst[0], 4), worst[1]] out["objects_closer_than_2_cm_to_the_hull_or_outside"] = near made = {} for o in new: if not o.get("ue_mesh") and not o.get("model_part"): made.setdefault(o.data.name, []).append(o) out["meshes_made_here"] = {} for name, obs in sorted(made.items()): a = shipfit.audit([obs[0]]) out["meshes_made_here"][name] = {"faces": a["faces"], "triangles": a["tris"], "ngons": a["ngons"], "without_uv": a["no_uv"], "instances": len(obs)} # a piece of the packs that crosses the lining (ceiling fittings and floor panels touch it by design) trees = {} def tree(o): if o.name not in trees: me = o.data me.calc_loop_triangles() mw = o.matrix_world trees[o.name] = BVHTree.FromPolygons([tuple(mw @ v.co) for v in me.vertices], [tuple(t.vertices) for t in me.loop_triangles], epsilon=0.0) return trees[o.name] lining = [o for o in new if o.name.startswith("Lining |")] crossing = [] for o in new: if not o.get("ue_mesh") or o.name.startswith(("Floor panel", "Ceiling light", "Ceiling vent", "Smoke detector", "Sprinkler")): continue for l in lining: k = len(tree(o).overlap(tree(l))) if k: crossing.append([o.name, l.name, k]) out["pack_pieces_that_cross_the_lining"] = crossing out["markers"] = sorted(o.name for o in bpy.data.objects if o.name.startswith("MARKER | ") and o.get("made_by") == "quarters") return out def build(furnish=True, captain=None): """Build both rooms in the open file. Returns a report. captain: see CAPTAIN.""" captain = captain or CAPTAIN if exists(): raise RuntimeError("the quarters are already in this file (quarters.remove() takes them out)") root = bpy.data.objects[ROOT] deck = bpy.data.collections[DECK] mats = materials() sec = solve_section() pts = sec["pts"] yc, yb, ya, yk = pts[2][0], pts[3][0], pts[4][0], pts[5][0] zk, za, zb = BREAKS rep = {"section_port_y_z_above_floor": pts, "least_depth_inside_hull_m": sec["least_depth_between_folds"], "rooms": {}} me_bay = lining_bay(sec, mats, True, "lining bay") me_door = lining_bay(sec, mats, False, "lining bay (door)") me_end_a = end_wall(sec, mats, True, "end wall (faces the bow)") me_end_b = end_wall(sec, mats, False, "end wall (faces the stern)") me_end_q = end_wall(sec, mats, False, "end wall (faces the stern, padded)", quilt=tuple((c, r) for c, rows in ((0, 4), (1, 3), (2, 2), (3, 1)) for r in range(rows))) me_berth = berth(sec, mats) me_bedding = bedding(sec, mats) me_bed = captains_bed(mats) me_bed_soft = captains_bedding(mats) for key, s, cname in ROOMS: coll = get_collection(cname, deck) lights = get_collection(cname + " | lights", coll) zone = cname turn = Matrix.Identity(4) if s > 0 else HALF_TURN n0 = len(coll.objects) def T(x, y=0.0, z=Z_F): return Matrix.Translation((x, y, z)) # the deck under the room, and the pack's floor panels on it y_in, y_out = 2.62, yk + 0.12 ob = shipfit.slab("Deck | %s" % ("crew quarters" if key == "crew" else "captain's quarters"), coll, root, X_A - 0.35, X_B + 0.45, min(s * y_in, s * y_out), max(s * y_in, s * y_out), Z_F - FLOOR_T, 0.15, mats["deck"]) ob["zone"] = zone if key == "captain" and captain == "cabin": rep["rooms"][key] = {"collection": cname, "objects": len(coll.objects) - n0, "lamps": 0, "note": "deck only; captains_cabin.port() sets the cabin in"} continue for i in range(4): xa = X_A + 3.6 * i put("PR_SM_Floor_A", coll, root, zone, (xa, s * 2.95, Z_F), yaw=90.0, anchor="-" + ("-" if s > 0 else "+") + "+", name="Floor panel") for j in range(2): put("PR_SM_Floor_C", coll, root, zone, (xa + 1.8 * j, s * 6.15, Z_F), yaw=90.0, anchor="-" + ("-" if s > 0 else "+") + "+", name="Floor panel (half)") # the lining for i in range(FIELDS): xc = X_A + (i + 0.5) * PITCH door = i in DOOR_FIELDS place_mesh(me_door if door else me_bay, "Lining | field %02d%s" % (i + 1, " (door)" if door else ""), coll, root, T(xc) @ turn, zone) if s > 0: place_mesh(me_end_a, "Lining | aft end wall", coll, root, T(X_A), zone) place_mesh(me_end_b, "Lining | fore end wall", coll, root, T(X_B), zone) else: place_mesh(me_end_q, "Lining | aft end wall", coll, root, T(X_A) @ turn, zone) place_mesh(me_end_a, "Lining | fore end wall", coll, root, T(X_B) @ turn, zone) # light fittings on the ceiling strip, one every two fields, each with its lamp y_strip = s * (Y_IN + yc) / 2.0 for k in range(6): xc = X_A + PITCH + 2.0 * PITCH * k # (each fitting hangs on a frame, across two fields) put("PR_SM_CeilingLight", coll, root, zone, (xc, y_strip, Z_F + H - RIB_OUT), yaw=90.0, anchor="00+", name="Ceiling light") area_lamp(lights, root, "%s | ceiling %d" % (cname.split(" | ")[-1], k + 1), (xc, y_strip, Z_F + H - RIB_OUT - 0.18), CEILING_W, (2.0, 0.22), zone=zone) zc = Z_F + H for i, kind in ((0, "PR_SM_Vent_B"), (2, "PR_SM_SmkDetector"), (4, "PR_SM_Vent_B"), (5, "PR_SM_FireSprinkler"), (7, "PR_SM_Vent_B"), (9, "PR_SM_SmkDetector"), (11, "PR_SM_Vent_B"), (6, "PR_SM_FireSprinkler"), (10, "PR_SM_FireSprinkler"), (1, "PR_SM_FireSprinkler")): xc = X_A + (i + 0.5) * PITCH put(kind, coll, root, zone, (xc, y_strip + s * (0.30 if kind != "PR_SM_Vent_B" else 0.0), zc), tilt=("X", 180.0), anchor="00+", name={"PR_SM_Vent_B": "Ceiling vent", "PR_SM_SmkDetector": "Smoke detector", "PR_SM_FireSprinkler": "Sprinkler"}[kind]) if furnish: if key == "crew": _furnish_crew(coll, lights, root, zone, sec, mats, me_berth, me_bedding) else: _furnish_captain(coll, lights, root, zone, sec, mats, me_bed, me_bed_soft) rep["rooms"][key] = {"collection": cname, "objects": len(coll.objects) - n0, "lamps": len(lights.objects), "inside_m": {"length": round(X_B - X_A, 2), "floor_width": round(yk - Y_IN, 2), "ceiling": H}} for me in (me_bay, me_door, me_end_a, me_end_b, me_end_q, me_berth, me_bedding, me_bed, me_bed_soft): if me.users == 0: bpy.data.meshes.remove(me) bpy.context.view_layer.update() return rep CEILING_W = 130.0 # an area lamp under each ceiling fitting READING_W = 5.0 def _furnish_crew(coll, lights, root, zone, sec, mats, me_berth, me_bedding): """Port room. Outer wall: six berths. Corridor wall, aft of the door: shelf, two cabinets with wall cabinets over them. Fore of the door: the mess bench with three stools.""" yk = sec["pts"][5][0] for k in range(6): x0 = X_A + BERTH * k m = Matrix.Translation((x0, 0.0, Z_F)) place_mesh(me_berth, "Berth %d" % (k + 1), coll, root, m, zone) place_mesh(me_bedding, "Berth %d | bedding" % (k + 1), coll, root, m, zone) # reading light on the locker's side, over the pillow put("HW_SM_Light_A", coll, root, zone, (x0 + 0.40, yk - 0.80, Z_F + 1.08), anchor="-0-", name="Berth %d | reading light" % (k + 1)) point_lamp(lights, root, "Crew quarters | berth %d reading light" % (k + 1), (x0 + 0.56, yk - 0.80, Z_F + 1.02), READING_W, zone=zone) marker(root, "Crew quarters bunk %d" % (k + 1), (x0 + 0.40 + 1.0, Y_BERTH + 0.5, Z_F + 0.50), "bed") y_w = Y_IN + 0.035 # furniture backs stand this far out, clear of the skirting put("PR_SM_Shelf_A_v1", coll, root, zone, (X_A + 0.25, y_w, Z_F), yaw=180.0, anchor="---", name="Stores shelf") for i, x in enumerate((18.15, 20.20)): put("PR_SM_Cabinet_A", coll, root, zone, (x, y_w, Z_F), yaw=180.0, anchor="---", name="Cabinet %d" % (i + 1)) for i in range(6): put("PR_SM_WallCab_2D" if i % 2 else "PR_SM_WallCab_1D", coll, root, zone, (18.17 + 0.675 * i, Y_IN + RIB_OUT + 0.002, Z_F + 1.50), yaw=180.0, anchor="---", name="Wall cabinet %d" % (i + 1)) put("PR_SM_WasteBin", coll, root, zone, (22.62, Y_IN + 0.30, Z_F), anchor="00-", name="Waste bin") # the mess bench, fore of the door put("PR_SM_Desk_B_v1", coll, root, zone, (26.96, y_w, Z_F), yaw=180.0, anchor="---", name="Mess bench") for i, x in enumerate((27.55, 28.46, 29.37)): put("PR_SM_Stool", coll, root, zone, (x, Y_IN + 1.42, Z_F), anchor="00-", yaw=20.0 * (i - 1), name="Stool %d" % (i + 1)) top = Z_F + DESK_TOP put("PR_SM_Laptop", coll, root, zone, (28.10, Y_IN + 0.50, top), yaw=180.0, anchor="00-", name="Laptop") put("PR_SM_GlassCup", coll, root, zone, (28.86, Y_IN + 0.62, top), anchor="00-", name="Cup") put("HW_SM_Datapad", coll, root, zone, (29.30, Y_IN + 0.55, top), yaw=25.0, anchor="00-", name="Datapad") put("PR_SM_WallCab_2D", coll, root, zone, (27.20, Y_IN + RIB_OUT + 0.002, Z_F + 1.50), yaw=180.0, anchor="---", name="Wall cabinet 7") put("PR_SM_WallCab_1D", coll, root, zone, (27.875, Y_IN + RIB_OUT + 0.002, Z_F + 1.50), yaw=180.0, anchor="---", name="Wall cabinet 8") put("EP_SM_VOIPPhone", coll, root, zone, (29.62, Y_IN + 0.002, Z_F + 1.42), yaw=-90.0, anchor="0-0", name="Phone") # stores by the aft end wall, and on the shelf put("HW_SM_Crate_A", coll, root, zone, (X_A + 0.12, 4.35, Z_F), anchor="---", name="Crate") put("EP_SM_Case_A", coll, root, zone, (16.95, y_w + 0.35, Z_F + SHELF_LEVELS[3]), anchor="00-", name="Case on the shelf") put("PR_SM_FirstAidKit", coll, root, zone, (16.45, y_w + 0.30, Z_F + SHELF_LEVELS[2]), anchor="00-", name="First aid kit") put("PR_SM_Laptop_Closed", coll, root, zone, (17.35, y_w + 0.30, Z_F + SHELF_LEVELS[2]), yaw=8.0, anchor="00-", name="Laptop (closed)") marker(root, "Crew quarters mess bench", (28.46, Y_IN + 0.9, Z_F), "table") SHELF_LEVELS = (0.201, 0.601, 1.001, 1.401) # the Props wire shelf's four boards above the floor (measured) DESK_TOP = 0.797 # the Props desks' and cabinets' top above the floor (measured on the meshes: 0.796) def _furnish_captain(coll, lights, root, zone, sec, mats, me_bed, me_bed_soft): """Starboard room. Aft: the bed against the end wall, a dresser with wall cabinets on the corridor wall, a shelf across as a screen. Middle: two desks back to back as a briefing table. Fore: the captain's desk, facing aft, with shelves behind it on the end wall. A sideboard under the slope opposite the door.""" yb_c = -5.60 # the bed's centre line m = Matrix.Translation((X_A + 0.012, yb_c, Z_F)) place_mesh(me_bed, "Captain's bed", coll, root, m, zone) place_mesh(me_bed_soft, "Captain's bed | bedding", coll, root, m, zone) marker(root, "Captain's bed", (X_A + 1.1, yb_c, Z_F + 0.52), "bed") point_lamp(lights, root, "Captain's quarters | bedside", (X_A + 0.45, yb_c - BED_W / 2 - 0.30, Z_F + 0.95), READING_W, zone=zone) point_lamp(lights, root, "Captain's quarters | bedside 2", (X_A + 0.45, yb_c + BED_W / 2 + 0.30, Z_F + 0.95), READING_W, zone=zone) y_w = -(Y_IN + 0.035) # dresser and wall cabinets on the corridor wall put("PR_SM_Cabinet_A", coll, root, zone, (18.40, y_w, Z_F), anchor="-+-", name="Dresser") for i in range(3): put("PR_SM_WallCab_2D" if i == 1 else "PR_SM_WallCab_1D", coll, root, zone, (18.42 + 0.675 * i, -(Y_IN + RIB_OUT + 0.002), Z_F + 1.50), anchor="-+-", name="Wall cabinet %d" % (i + 1)) # a shelf across the room as a screen between bed and table put("PR_SM_Shelf_A_v1", coll, root, zone, (18.75, -7.36, Z_F), yaw=90.0, anchor="---", name="Screen shelf") # briefing table: two desks back to back, two stools a side put("PR_SM_Desk_A_v2", coll, root, zone, (20.55, -5.45, Z_F), yaw=180.0, anchor="---", name="Briefing table (inboard half)") put("PR_SM_Desk_A_v2", coll, root, zone, (20.55, -5.45, Z_F), anchor="-+-", name="Briefing table (outboard half)") for i, (x, y) in enumerate(((21.05, -4.05), (22.05, -4.05), (21.05, -6.85), (22.05, -6.85))): put("PR_SM_Stool", coll, root, zone, (x, y, Z_F), anchor="00-", yaw=15.0 * ((i % 2) * 2 - 1), name="Briefing stool %d" % (i + 1)) top = Z_F + DESK_TOP put("HW_SM_Datapad", coll, root, zone, (21.2, -4.85, top), yaw=-20.0, anchor="00-", name="Datapad") put("PR_SM_GlassCup", coll, root, zone, (22.1, -6.0, top), anchor="00-", name="Cup") # sideboard under the slope, opposite the door put("PR_SM_Cabinet_A", coll, root, zone, (23.86, -(sec["pts"][5][0] - 0.20), Z_F), yaw=180.0, anchor="---", name="Sideboard") # the captain's desk at the fore end, facing aft; shelves behind it put("PR_SM_Desk_A_v1", coll, root, zone, (27.60, -6.95, Z_F), yaw=90.0, anchor="---", name="Captain's desk") put("PR_SM_Stool", coll, root, zone, (28.98, -5.45, Z_F), anchor="00-", name="Captain's seat") for i, y in enumerate((-4.75, -6.15)): put("PR_SM_Stool", coll, root, zone, (26.98, y, Z_F), anchor="00-", yaw=30.0 * (i * 2 - 1), name="Visitor's stool %d" % (i + 1)) put("PR_SM_Laptop", coll, root, zone, (28.07, -5.45, top), yaw=-90.0, anchor="00-", name="Laptop") put("PR_SM_TableLamp", coll, root, zone, (28.15, -4.30, top), yaw=90.0, anchor="00-", name="Desk lamp") put("HW_SM_Datapad", coll, root, zone, (27.97, -6.30, top), yaw=70.0, anchor="00-", name="Datapad 2") point_lamp(lights, root, "Captain's quarters | desk lamp", (28.07, -4.45, top + 0.42), 18.0, color=(0.75, 0.90, 1.0), zone=zone) for i, y in enumerate((-3.30, -5.20)): put("PR_SM_Shelf_A_v1" if i == 0 else "PR_SM_Shelf_A_v2", coll, root, zone, (X_B - 0.04, y, Z_F), yaw=-90.0, anchor="++-", name="Shelf %d" % (i + 1)) put("PR_SM_WasteBin", coll, root, zone, (28.05, -3.50, Z_F), anchor="00-", name="Waste bin") xs_ = X_B - 0.04 - 0.32 # the middle of the shelves' depth put("PR_SM_Laptop_Closed", coll, root, zone, (xs_, -4.05, Z_F + SHELF_LEVELS[2]), yaw=-90.0, anchor="00-", name="Laptop (closed)") put("HW_SM_Datapad", coll, root, zone, (xs_, -4.75, Z_F + SHELF_LEVELS[2]), yaw=-70.0, anchor="00-", name="Datapad 3") put("PR_SM_FirstAidKit", coll, root, zone, (xs_, -3.70, Z_F + SHELF_LEVELS[1]), anchor="00-", name="First aid kit") put("EP_SM_Case_A", coll, root, zone, (xs_, -5.87, Z_F + SHELF_LEVELS[3]), yaw=90.0, anchor="00-", name="Case on the shelf") put("HW_SM_Datapad", coll, root, zone, (19.07, -6.20, Z_F + SHELF_LEVELS[2]), yaw=15.0, anchor="00-", name="Datapad 4") marker(root, "Captain's desk", (28.05, -5.45, Z_F), "desk")