""" The captain's quarters over the WHOLE starboard room beside the control room corridor. The user, 2026-10-13, after the cabin of models/captains-quarters had been set into the room as it was (7.2 m of the room's 14.4 m, the rest left empty), with the reference picture of the black cabin: "what is this? this is a small room. you said it wont fit thats why I said try to fit. make it use all the space previously spared just generate if necessary here is the referance picture" So the room is filled from end to end and as far out as the hull lets it, in that cabin's own panels and materials: * the model's cabin stays where captains_cabin.port() put it: it is the middle bay (bunk alcove, work station, door, bench with the chart). Its two end walls move out to the room's ends: the window wall 3.55 m forward (to the bridge end), the locker wall with its three doors 3.6 m aft; * the two bays between are made here, with the model's own library (cq_lib.py: plates on a dark backing, rings, quilting; read from models/captains-quarters/build, nothing is written there). They are deeper than the cabin: their floor runs 1.1 m further out, to where the cabin's bunk alcove ends (4.4 m from the corridor's wall, the depth of the model's footprint). The hull leans in over that strip, so it has a knee wall and a canted panel up to the ceiling instead of a full-height wall: - forward, by the window: a lounge. A padded bench under the cant with a light cove over it, a low table, a second chair, a sideboard under a lit shelf on the corridor side; - aft, before the lockers: the mess. The same padded bench as a banquette, a table, two chairs, and on the corridor side a sideboard under a status display; - floor plates, ceiling plates with two light fixtures in each bay, and at each junction with the cabin a pilaster on the corridor side, a beam under the ceiling and a strip in the floor; * the model's lettering is one mesh for all walls, so it is taken apart here: what belongs to the window wall and to the locker wall moves with them. The user, the same afternoon, with the corridor's starboard side door selected in the open file: "the entrance should be via the "Door_A_BP4.230" build according to that" That door is the Engineer pack's: its opening is 1.56 m wide and 2.61 m high and begins 0.23 m above the sill (a coaming). The model's own door behind it was 1.0 x 2.1 m, 0.43 m lower, and a second door 0.4 m after the first. So the room is built to the pack's door: * the model's sliding door is taken out, and its door wall is drawn again here with the model's own numbers (plates, chart display, bench) but with the doorway made to that door: 1.64 m wide, up to 2.50 m above the cabin's floor, a frame round it up to the ceiling, the reveal lined as far as the pack's door frame; * inside the door a landing at the corridor's level, 2.2 x 0.9 m, with one lit step (0.20 m) down to the cabin's floor: coaming and step are then the same on both sides of the door; * (first: a panel behind the frame closed the 0.44 m of the door's opening that stood above the cabin's 2.60 m ceiling. The user, with pictures of the door seen over the cabin's roof: "extend the room height. the door look half cut fix that by extending the room height". So the room is higher now:) * the room's roof is one slope now: from the cabin model's 2.60 m at the bunk wall up to 4.03 m at the corridor's wall, 7 cm under the top of the pack's wall pieces there (the red line in the user's picture; the hull's hidden crest skin begins at that top). Measured against hull and roof pod as one (shipfit.Envelope: the hull's skin under the pod is not a boundary), the slope keeps 0.19 m or more; the tightest place is still the model's bunk wall top (0.10 m). The model's flat ceiling is out of the ship; its fixtures and vent are laid on the slope. The doorway takes the pack's whole door, with a metre of wall over it; the panel is gone. The model's window wall and locker wall get the gable over them. Everything is drawn in the cabin's own frame (+X along the bunk wall towards the lockers, +Y into the bunk wall, origin on the floor at the cabin's middle) and set down with the cabin's matrix, so the cabin and its extension are one room with one origin. In the ship that frame is turned half a turn: the cabin's -X is the bow. import captains_cabin, captains_suite captains_cabin.port() # the cabin (if it is not in the room yet) captains_suite.extend() # the rest of the room captains_suite.remove() # back to the cabin alone """ import math import os import sys import bpy from mathutils import Matrix, Vector import captains_cabin as cc import quarters TAG = "captains_suite" MODEL_BUILD = os.path.join(os.path.dirname(cc.CABIN_FILE), "build") LF = 3.55 # the forward bay (5 cm short of a corridor bay: the window's light well must clear the bridge bulkhead) LA = 3.60 # the aft bay KH = 1.60 # height of the knee wall on the hull side of the two bays; the cant runs from there to the ceiling HC = 4.03 # the room's height at the corridor's wall (ship z 6.97): 7 cm under the top of the pack's wall # pieces there. At 7.04 the roof cut 4 cm into the hull's crest skin in the forward bay; that skin # is hidden under the roof pod (a Mask), but nothing of the room is to pass through it. # From there the roof slopes down to the model's 2.60 m at the bunk wall. FIX_ROWS = (0.80, 2.45) # where the two rows of light fixtures lie up the slope (from the bunk wall) POST = 0.12 # the frame at each junction with the cabin: pilaster, beam and floor strip, on the bay's side of the seam OFF = 0.001 # what stands on a surface of the model keeps this far off it (nothing of the model is touched) EPS = 0.0005 # two faces cross only if each reaches further than this through the other's plane PRE = "SM_CQS_" # meshes made here ("captain's quarters, ship") # watts. The forward bay has the window; the aft bay has only its own lamps, so they are stronger there LAMPS = {"ceiling": {"Fore": 26.0, "Aft": 55.0}, "cove": 14.0} BENCH = {"Fore": (0.48, 3.07), "Aft": (0.50, 3.10)} # along the knee wall, from the bay's low-x end SIDEBOARD = {"Fore": (0.60, 3.10), "Aft": (0.70, 3.10)} # along the corridor wall, from the bay's high-x end # (the aft one stops 0.70 m short of the lockers: the third locker door swings past its corner with 10 cm to spare) LOUNGE_TABLE = (1.30, 0.60, 0.405) # length, width, height MESS_TABLE = (1.72, 0.82, 0.762) TIES = {"Fore": (0.62, 1.05), "Aft": (3.02, 1.05)} # a tie-down fitting in each bay's floor (u, v of its floor frame) # the way in: the corridor's starboard side door, the pack's (measured in the open file with rays through its frame) ENTRANCE = "Door_A_BP4.230" # its leaf; the user named it DOORWAY = "EN_SM_Doorway_Small_A" # the mesh of the frame it stands in DOOR_CLEAR = 1.56 # the frame's clear width (x 24.08 .. 25.64) DOOR_COAMING = 0.23 # its opening begins this far above the sill ... DOOR_TOP = 2.84 # ... and ends this far above it OPEN_W = 1.64 # the cabin wall's opening: 4 cm more than the door's on each side HEAD_OVER = 0.06 # ... and its head this far above the door's own LANDING = (2.20, 0.90) # the landing inside the door: width, depth from the wall _bq = None def lib(): """The model's own build module (helpers and constants; its main() is never called).""" global _bq if _bq is None: keep_path, keep_flag = list(sys.path), sys.dont_write_bytecode sys.dont_write_bytecode = True # nothing is written into the other session's folder try: sys.path.append(MODEL_BUILD) import build_quarters as bq # (it puts its own folder first on sys.path; taken back below) finally: sys.path[:] = keep_path sys.dont_write_bytecode = keep_flag _bq = bq return _bq class Bay: """One of the two bays made here, in the cabin's frame: where its surfaces lie. Along the bay every frame's u runs with +X from the bay's low-x end, except the corridor wall's (it faces the other way, so its u runs from the high-x end).""" def __init__(self, key, x0, length, cabin_high): bq = lib() F = bq.Frame YD, YB, YK, H = bq.YD, bq.YB, bq.YO, bq.H self.key, self.x0, self.L, self.cabin_high = key, x0, length, cabin_high self.W = YK - YB # deeper than the cabin by this: 1.1 m self.SL = math.hypot(self.W, H - KH) # the cant, from the knee wall's top to the ceiling self.FK = F((x0, YK, 0), (1, 0, 0), (0, 0, 1)) # knee wall, hull side (w = -Y) self.FS = F((x0, YK, KH), (1, 0, 0), (0, -self.W, H - KH)) # the cant above it (w = into the room) self.SM = math.hypot(YB - YD, HC - H) # the roof's slope, bunk wall's top to corridor wall's top self.FM = F((x0, YB, H), (1, 0, 0), (0, YD - YB, HC - H)) # the roof (w = into the room) self.FD = F((x0 + length, YD, 0), (-1, 0, 0), (0, 0, 1)) # corridor side (w = +Y) self.FF = F((x0, YD, 0), (1, 0, 0), (0, 1, 0)) # floor (w = +Z) self.FLo = F((x0, YB, 0), (0, 1, 0), (0, 0, 1)) # end wall at low x, beyond the cabin's depth (w = +X) self.FHi = F((x0 + length, YK, 0), (0, -1, 0), (0, 0, 1)) # end wall at high x, the same (w = -X) e = POST + 0.004 if cabin_high is None: # the cabin itself (only its roof is drawn here) self.ua, self.ub, self.frame, self.frame_d = 0.02, length - 0.02, None, None self.da, self.db = 0.02, length - 0.02 elif cabin_high: # the forward bay: the window at u = 0, the cabin at u = L self.ua, self.ub = 0.02, length - e - 0.010 # plating of floor and ceiling self.frame = (length - e, length + 0.016) # the junction's frame (it laps 16 mm over the model's bare edge) self.da, self.db = e + 0.010, length - 0.02 # the same two in the corridor wall's u self.frame_d = (-0.016, e) else: # the aft bay: the cabin at u = 0, the lockers at u = L self.ua, self.ub = e + 0.010, length - 0.02 self.frame = (-0.016, e) self.da, self.db = 0.02, length - e - 0.010 self.frame_d = (length - e, length + 0.016) def bays(): bq = lib() return {"Fore": Bay("Fore", -bq.HX - LF, LF, True), "Aft": Bay("Aft", bq.HX, LA, False)} def middle(): bq = lib() return Bay("Mid", -bq.HX, 2 * bq.HX, None) FIXTURES = {"Mid": (0.90, 4.50)} # where the model's own ceiling has its fixtures, along the cabin def fixtures(b): """Along a bay: where its light fixtures begin (each 1.8 m long), in both rows up the slope.""" return FIXTURES.get(b.key) or ((b.ua + b.ub) / 2.0 - 0.90,) def entrance(m=None): """The corridor's side door the room is entered by, in the cabin's frame: x = its axis, y = the room face of the frame it stands in, z = its sill. Read from the file's own objects.""" def side_door(o): # the leaf of the corridor's starboard side door, by where it stands at = o.matrix_world.translation return (o.type == "MESH" and o.data.name.split(".")[0] == "EN_SM_Door_Small_A" and abs(at.x - cc.DOOR_X) < 0.05 and -3.2 < at.y < -1.0) door = bpy.data.objects.get(ENTRANCE) if door is None or not side_door(door): # a file built anew numbers its objects afresh (there the name belongs to the door at the corridor's bridge # end): the same leaf is found by its place door = next((o for o in _scene_of(bpy.data.collections[cc.ROOM]).objects if side_door(o)), None) if door is None: raise RuntimeError("the entrance door %s is not in the file" % ENTRANCE) if m is None: m = cc.placement()[0] at = door.matrix_world.translation frame = None for o in door.users_collection[0].objects: if o.type != "MESH" or o.data.name.split(".")[0] != DOORWAY: continue lo, hi = _box(o) if lo.x <= at.x <= hi.x and lo.y - 0.01 <= at.y <= hi.y + 0.01: frame = o break if frame is None: raise RuntimeError("no %s round the door %s" % (DOORWAY, ENTRANCE)) lo, hi = _box(frame) face = lo.y if at.y < 0 else hi.y # the frame's face on the room's side p = m.inverted() @ Vector((at.x, face, quarters.Z_F)) return {"x": p.x, "y": p.y, "z": p.z, "door": door.name, "frame": frame.name, "in_ship": {"axis_x": round(at.x, 3), "frame_face_y": round(face, 3), "sill_z": round(quarters.Z_F, 3)}} # ---- pieces in the model's idiom --------------------------------------------------------------------------------- def _bench(P, F, u0, u1, depth=0.60, seed=11): """The model's bench (plinth, body, cushions, padded back with its trims), on any wall frame, any length.""" bq = lib() T, SAT, MAT_, MATT = bq.T, bq.SAT, bq.MAT_, bq.MATT P.box(F, u0 + 0.06, T + 0.001, u1 - 0.06, 0.10, T, depth - 0.06, MAT_, c=0.004) P.box(F, u0, 0.10, u1, 0.36, T, depth, SAT, c=0.012, segs=2) Fs = bq.Frame(F.p(0, 0.36, 0), F.w, F.u) # u = out from the wall, v = along it, w = up n = max(1, int(round((u1 - u0) / 1.2))) ln = (u1 - u0 - 0.04 - 0.02 * (n - 1)) / n for k in range(n): v0 = u0 + 0.02 + k * (ln + 0.02) P.prism(Fs, bq.shape(0.10, v0, depth - 0.01, v0 + ln, r=0.035, seg=4), 0.0, 0.10, MATT, c=0.03, segs=3) cols = max(3, int(round((u1 - u0 - 0.042) / 0.262))) bq.quilt(P, F.at(dw=0.03), u0 + 0.021, 0.501, u1 - 0.021, 1.039, cols, 4, puff=0.040, seed=seed) P.box(F, u0, 0.46, u1, 0.499, T, 0.05, SAT, c=0.004) P.box(F, u0, 1.041, u1, 1.085, T, 0.06, SAT, c=0.006, segs=2) for ua in (u0, u1 - 0.019): P.box(F, ua, 0.50, ua + 0.019, 1.04, T, 0.05, SAT, c=0.003) P.box(F, u0 + 0.02, 0.50, u1 - 0.02, 1.04, T, 0.03, MAT_, c=0.0) # board behind the padding def _sideboard(P, F, u0, u1, depth=0.42, top=0.82): """A low cabinet against a wall: plinth, carcass, a gunmetal top, door panels with pulls.""" bq = lib() T, SAT, MAT_, GUN = bq.T, bq.SAT, bq.MAT_, bq.GUN P.box(F, u0 + 0.05, T + 0.001, u1 - 0.05, 0.10, T, depth - 0.06, MAT_, c=0.004) P.box(F, u0, 0.10, u1, top, T, depth, SAT, c=0.010, segs=2) P.box(F, u0 - 0.012, top + 0.001, u1 + 0.012, top + 0.030, T, depth + 0.016, GUN, c=0.006, segs=2) Fc = F.at(dw=depth) n = max(2, int(round((u1 - u0) / 0.62))) wd = (u1 - u0 - 0.04) / n for k in range(n): a = u0 + 0.02 + k * wd P.box(Fc, a + 0.008, 0.135, a + wd - 0.008, top - 0.035, 0.0, 0.008, MAT_, c=0.003, r=0.012, seg=3) P.box(Fc, a + wd / 2 - 0.07, top - 0.105, a + wd / 2 + 0.07, top - 0.085, 0.008, 0.017, GUN, c=0.003) def _display(P, F, u0, v0, u1, v1, cell): """A framed screen like the model's chart display; (u0, v0, u1, v1) is the picture, one cell of the screen sheet.""" bq = lib() P.ring(F, bq.shape(u0 - 0.079, v0 - 0.079, u1 + 0.079, v1 + 0.079, r=0.05, seg=4), bq.shape(u0 - 0.009, v0 - 0.009, u1 + 0.009, v1 + 0.009, r=0.025, seg=4), bq.T, 0.045, bq.SAT, c=0.008, segs=2, reveal_w=0.030, floor_mat=bq.BACK) x0, y0, x1, y1 = bq.at(cell) P.quad([F.p(u0, v0, 0.0305), F.p(u1, v0, 0.0305), F.p(u1, v1, 0.0305), F.p(u0, v1, 0.0305)], bq.SCREEN, uvs=[(x0, y0), (x1, y0), (x1, y1), (x0, y1)]) def _cartridges(P, F, us, v0): """The model's vent cartridges (as in the header over the bunk).""" bq = lib() for u0 in us: P.box(F, u0, v0, u0 + 0.50, v0 + 0.17, bq.T, 0.040, bq.GUN, c=0.006, segs=2, r=0.02, seg=3) bq.slats(P, F, u0 + 0.04, v0 + 0.025, u0 + 0.36, v0 + 0.145, 5, 0.040, 0.045, bq.MAT_) P.box(F, u0 + 0.40, v0 + 0.077, u0 + 0.46, v0 + 0.093, 0.040, 0.043, bq.LRED, c=0.001) def _tie_down(P, FF, u, v): bq = lib() T = bq.T P.prism(FF, bq.circle(u, v, 0.075, 24), T, 0.020, bq.STEEL, c=0.003) P.prism(FF, bq.circle(u, v, 0.050, 24), 0.020, 0.023, bq.MAT_, c=0.002) P.box(FF, u - 0.035, v - 0.006, u + 0.035, v + 0.006, 0.023, 0.026, bq.STEEL, c=0.001) def _table(P, length, width, height, feet): """A table at its own origin (floor, middle), long side along X: steel foot plates bolted down, gunmetal legs with a bracket each, a satin top.""" bq = lib() Ff = bq.Frame((0, 0, 0), (1, 0, 0), (0, 1, 0)) for x in feet: P.prism(Ff, bq.shape(x - 0.13, -0.19, x + 0.13, 0.19, r=-0.03), 0.0, 0.014, bq.STEEL, c=0.003) for dx, dy in ((0.09, 0.14), (0.09, -0.14), (-0.09, 0.14), (-0.09, -0.14)): P.prism(Ff, bq.circle(x + dx, dy, 0.014, 6), 0.014, 0.024, bq.GUN, c=0.002) P.box(Ff, x - 0.045, -0.12, x + 0.045, 0.12, 0.014, height - 0.050, bq.GUN, c=0.0, r=0.014, seg=3, cap=False) P.box(Ff, x - 0.16, -0.20, x + 0.16, 0.20, height - 0.050, height - 0.036, bq.GUN, c=0.0, cap=False, back=True) P.prism(Ff.at(dw=height - 0.036), bq.shape(-length / 2, -width / 2, length / 2, width / 2, r=0.06, seg=4), 0.0, 0.036, bq.SAT, c=0.010, segs=2, back=True) def _gable(P, F, h0, h1, ua, ub): """A wall under the cant, 1.1 m wide: h0, h1 = its height at u = 0 and at its far edge. Plates from ua to ub.""" bq = lib() wd = bq.YO - bq.YB g = bq.G / 2 def top(u): return h0 + (h1 - h0) * u / wd P.quad([F.p(0, 0, 0), F.p(wd, 0, 0), F.p(wd, h1, 0), F.p(0, h0, 0)], bq.BACK) bq.plates(P, F, [(ua, 0.08, ub, 0.29, bq.MAT_, 0.022), (ua, 0.29, ub, 1.17)]) P.prism(F, [(ua + g, 1.17 + g), (ub - g, 1.17 + g), (ub - g, top(ub - g) - 0.03), (ua + g, top(ua + g) - 0.03)], 0.0, bq.T, bq.SAT, c=0.003) # ---- the parts made here (cabin frame) --------------------------------------------------------------------------- def _spots(): """Where the loose things stand, in the cabin's frame.""" bq = lib() B = bays() front = bq.YO - 0.60 # the benches' front edge f, a = B["Fore"], B["Aft"] lounge = (f.x0 + sum(BENCH["Fore"]) / 2, front - 0.40 - LOUNGE_TABLE[1] / 2) mess = (a.x0 + sum(BENCH["Aft"]) / 2, front - 0.05 - MESS_TABLE[1] / 2) chair_y = mess[1] - MESS_TABLE[1] / 2 - 0.25 return {"lounge_table": lounge, "mess_table": mess, "chairs": [("lounge", lounge[0] - 0.55, lounge[1] - 0.92, 60.0), ("mess 1", mess[0] - 0.46, chair_y, 90.0), ("mess 2", mess[0] + 0.46, chair_y, 90.0)]} def parts(E=None): """[(name, function(Part), kind)]. kind "room": drawn in the cabin's frame; "lounge_table"/"mess_table": drawn at its own origin and set down at that spot; "decal": lettering, no weighted normals. E: the entrance door in the cabin's frame (entrance()); without it, where it stands in the ship today.""" bq = lib() E = E or {"x": -1.8, "y": -2.305, "z": 0.20} HX, MATT, LGREEN, SCREEN = bq.HX, bq.MATT, bq.LGREEN, bq.SCREEN Frame, shape, circle = bq.Frame, bq.shape, bq.circle YD, YB, YK, H, T, DEPTH = bq.YD, bq.YB, bq.YO, bq.H, bq.T, bq.DEPTH SAT, MAT_, GUN, FLOOR = bq.SAT, bq.MAT_, bq.GUN, bq.FLOOR LWARM, LCOOL = bq.LWARM, bq.LCOOL grid, plates, backing, slats = bq.cells_grid, bq.plates, bq.backing, bq.slats B = bays() fore, aft = B["Fore"], B["Aft"] z0 = T + 0.001 spots = _spots() def knee_wall(P, b, seed): """The hull side of a bay: the knee wall with the bench and a light cove over it.""" L = b.L u0, u1 = BENCH[b.key] backing(P, b.FK, 0, 0, L, KH) cells = [] for ca, cb in ((0.02, u0 - 0.02), (u1 + 0.02, L - 0.02)): cells += [(ca, 0.08, cb, 0.29, MAT_, 0.022), (ca, 0.29, cb, 1.085), (ca, 1.085, cb, KH - 0.02)] us = [u0 - 0.02 + (u1 - u0 + 0.04) * i / 3.0 for i in range(4)] cells += grid(us, [1.10, KH - 0.115]) plates(P, b.FK, cells) _bench(P, b.FK, u0, u1, seed=seed) P.box(b.FK, u0, KH - 0.105, u1, KH - 0.02, 0.0, 0.085, SAT, c=0.008, segs=2) # cove, as over the bunk's padding P.box(b.FK, u0 + 0.30, KH - 0.078, u1 - 0.30, KH - 0.050, 0.085, 0.093, LWARM, c=0.002) def cant(P, b): """The canted panel from the knee wall's top up to the ceiling, with two reading lights and a vent.""" L = b.L u0, u1 = BENCH[b.key] backing(P, b.FS, 0, 0, L, b.SL) plates(P, b.FS, grid([0.02, L / 3.0, 2.0 * L / 3.0, L - 0.02], [0.03, b.SL - 0.03])) um = (u0 + u1) / 2.0 for u in (u0 + 0.55, u1 - 0.55): # two reading lights over the seat P.prism(b.FS, circle(u, 0.42, 0.050, 16), T, T + 0.020, GUN, c=0.006, segs=2) P.prism(b.FS, circle(u, 0.42, 0.034, 16), T + 0.020, T + 0.022, LWARM, c=0.001) P.box(b.FS, um - 0.40, 0.74, um + 0.40, 1.08, T, T + 0.012, MAT_, c=0.004, r=0.03, seg=3) # a vent, as in the cabin's ceiling slats(P, b.FS, um - 0.35, 0.78, um + 0.35, 1.04, 7, T + 0.012, T + 0.018, GUN) def pilaster(P, b): P.box(b.FD, b.frame_d[0], z0, b.frame_d[1], HC - 0.055, OFF, 0.06, SAT, c=0.008, segs=2) # (its head stays under the slope) def corridor_fore(P): b = fore backing(P, b.FD, 0, 0, b.L, HC) us = [b.da + (b.db - b.da) * i / 4.0 for i in range(5)] plates(P, b.FD, grid(us, [0.08, 1.18]) + grid(us, [1.18, 2.22]) + grid([us[0], us[2], us[4]], [2.22, 2.57, 3.32, HC - 0.03])) pilaster(P, b) s0, s1 = SIDEBOARD["Fore"] _sideboard(P, b.FD, s0, s1) P.box(b.FD, s0, 0.87, s1, 1.425, T, 0.024, MAT_, c=0.003) # back panel P.box(b.FD, s0 + 0.10, 1.432, s1 - 0.10, 1.455, T, 0.036, LWARM, c=0.002) # light bar under the shelf P.box(b.FD, s0, 1.46, s1, 1.495, T, 0.22, SAT, c=0.006, segs=2) # shelf _cartridges(P, b.FD, (s0 + 0.30, s1 - 0.80), 1.80) def corridor_aft(P): b = aft backing(P, b.FD, 0, 0, b.L, HC) s0, s1 = SIDEBOARD["Aft"] um = (s0 + s1) / 2.0 us = [b.da, um - 0.88, um, um + 0.88, b.db] cells = grid(us, [0.08, 1.18]) + grid([us[0], us[2], us[4]], [2.22, 2.57, 3.32, HC - 0.03]) cells += [(us[0], 1.18, us[1], 2.22), (us[1], 1.18, us[3], 2.22), (us[3], 1.18, us[4], 2.22)] plates(P, b.FD, cells) pilaster(P, b) _sideboard(P, b.FD, s0, s1) _display(P, b.FD, um - 0.71, 1.34, um + 0.71, 2.038, "screen_desk") # 1.42 x 0.698: the cell's own shape def corner_guard(P, x, bay_low): """An angle over the outside corner where the cabin's bunk wall ends and a bay's return wall begins. bay_low: the bay lies towards -X of the corner.""" e, leg, d = 0.016, 0.016, OFF Fz = Frame((0, 0, 0), (1, 0, 0), (0, 1, 0)) if bay_low: # two legs (convex pieces): one along the bunk wall's face, one along the return wall's legs = ((x - e, YB - e, x + leg, YB - d), (x - e, YB - d, x - d, YB + leg)) else: legs = ((x - leg, YB - e, x + e, YB - d), (x + d, YB - d, x + e, YB + leg)) for a in legs: P.box(Fz, a[0], a[1], a[2], a[3], z0, H - 0.03, GUN, c=0.002) def rail(P, F, u0, u1): """The model's equipment rail (as right of the bunk).""" P.prism(F, shape(u0, 1.22, u1, 1.50, r=-0.05), T, 0.050, MAT_, c=0.006, segs=2) P.box(F, u0 + 0.07, 1.335, u1 - 0.07, 1.385, 0.050, 0.056, GUN, c=0.002) def end_top(P, F, wall_at_zero): """The gable over the model's 2.60 m end wall, under the roof's slope. F's u runs across the room; wall_at_zero: u = 0 is the corridor's wall (else the bunk wall's plane).""" g = bq.G / 2 k = (HC - H) / DEPTH lo = H + 0.03 def top(u): # the roof over u, less the gap the plates keep to it return (HC - u * k if wall_at_zero else H + u * k) - 0.035 P.quad([F.p(u, v, 0) for u, v in ([(0, H), (DEPTH, H), (0, HC)] if wall_at_zero else [(0, H), (DEPTH, H), (DEPTH, HC)])], bq.BACK) cuts = [0.02, 1.10, 2.20, DEPTH - 0.30] if wall_at_zero else [0.30, 1.10, 2.20, DEPTH - 0.02] for a, e in zip(cuts[:-1], cuts[1:]): P.prism(F, [(a + g, lo + g), (e - g, lo + g), (e - g, top(e - g)), (a + g, top(a + g))], 0.0, T, SAT, c=0.003) def gables_fore(P): b = fore wd = b.W end_top(P, Frame((b.x0, YD, 0), (0, 1, 0), (0, 0, 1)), True) # over the model's window wall # beside the window (low x): the wall carries on under the cant; a pilaster covers the bare edge the model's # window wall keeps for the bunk wall's pillar _gable(P, b.FLo, H, KH, 0.02, wd - 0.02) P.box(b.FLo, -0.096, z0, -0.004, H - T - 0.001, OFF, 0.05, SAT, c=0.008, segs=2) slats(P, b.FLo, 0.16, 0.36, wd - 0.16, 0.56, 5, T, T + 0.006, GUN) # the return wall at the cabin (high x) _gable(P, b.FHi, KH, H, 0.02, wd - 0.02) rail(P, b.FHi, 0.20, 0.90) corner_guard(P, b.x0 + b.L, True) def gables_aft(P): b = aft wd = b.W end_top(P, Frame((b.x0 + b.L, YB, 0), (0, -1, 0), (0, 0, 1)), False) # over the model's locker wall # the return wall at the cabin (low x) _gable(P, b.FLo, H, KH, 0.02, wd - 0.02) rail(P, b.FLo, 0.20, 0.90) corner_guard(P, b.x0, False) # beside the lockers (high x), with a pilaster over the bare edge of the model's locker wall _gable(P, b.FHi, KH, H, 0.02, wd - 0.02) P.box(b.FHi, wd + 0.004, z0, wd + 0.056, H - T - 0.001, OFF, 0.05, SAT, c=0.008, segs=2) slats(P, b.FHi, 0.16, 0.36, wd - 0.16, 0.56, 5, T, T + 0.006, GUN) def floor(P, b): backing(P, b.FF, 0, 0, b.L, YK - YD) us = [b.ua + (b.ub - b.ua) * i / 3.0 for i in range(4)] cells = grid(us, [0.02, 0.46, 1.64, 2.82, 3.29], FLOOR) # beyond the cabin's depth the floor runs the bay's whole length (the return wall stands there, not the cabin) ws = [0.02 + (b.L - 0.04) * i / 3.0 for i in range(4)] cells += grid(ws, [3.31, 3.85, 4.38], FLOOR) plates(P, b.FF, cells, mat=FLOOR, c=0.003) P.box(b.FF, b.frame[0], 0.066, b.frame[1], 3.28, OFF, T + 0.002, GUN, c=0.003) # the junction's strip (from the pilaster's foot) _tie_down(P, b.FF, *TIES[b.key]) def roof(P, b, vent=False): """A bay's ceiling: one slope from the bunk wall's top up to the corridor wall's top, with the model's light fixtures in two rows, the model's vent between them, and the junction's beam.""" L = b.L us = [0.02, 1.2, 2.4, 3.6, 4.8, 6.0, 7.18] if b.frame is None else [b.ua + (b.ub - b.ua) * i / 3.0 for i in range(4)] fx = fixtures(b) backing(P, b.FM, 0, 0, L, b.SM) r0, r1 = FIX_ROWS cells = grid(us, [0.03, r0]) + grid(us, [r0 + 0.32, (r0 + 0.32 + r1) / 2.0, r1]) + grid(us, [r1 + 0.32, b.SM - 0.03]) for v0 in FIX_ROWS: at = us[0] for uf in fx: cells.append((at, v0, uf, v0 + 0.32)) at = uf + 1.80 P.ring(b.FM, shape(uf + 0.004, v0 + 0.004, uf + 1.796, v0 + 0.316, r=0.03, seg=3), shape(uf + 0.05, v0 + 0.07, uf + 1.75, v0 + 0.25, r=0.02, seg=3), 0.0, 0.034, MAT_, c=0.005, reveal_w=0.018, floor_mat=LCOOL) cells.append((at, v0, us[-1], v0 + 0.32)) plates(P, b.FM, [c for c in cells if c[2] - c[0] > 0.05]) if vent: # the model's ceiling vent P.box(b.FM, 3.20, 1.22, 4.00, 1.68, T, T + 0.012, MAT_, c=0.004, r=0.03, seg=3) slats(P, b.FM, 3.25, 1.27, 3.95, 1.63, 9, T + 0.012, T + 0.018, GUN) if b.frame is not None: # (it stops 11 cm short of the corridor's wall: its foot leans towards the wall and the pilaster stands there) P.box(b.FM, b.frame[0], 0.02, b.frame[1], b.SM - 0.11, OFF, 0.06, SAT, c=0.008, segs=2) def door_wall(P): """The model's door wall (build_quarters.wall_door: its plates, chart display and bench, number for number) with the doorway made to the corridor's side door instead of the model's own 1.0 x 2.1 m door.""" FD = bq.FD ua = HX - E["x"] # the door's axis along the wall (5.40) i0, i1 = ua - OPEN_W / 2, ua + OPEN_W / 2 # the opening o0, o1 = i0 - 0.20, i1 + 0.20 # its frame head = E["z"] + DOOR_TOP + HEAD_OVER # 3.10: the pack's whole door opening shows top = head + 0.20 # the frame's head backing(P, FD, 0, 0, 7.2, HC, holes=[(i0, 0.0, i1, head)]) cells = [] cells += grid([0.02, 0.85, 2.05, 3.25, 3.80, o0 - 0.02], [0.08, 1.18]) cells += grid([0.02, 0.85, 3.25, 3.80, o0 - 0.02], [1.18, 2.22]) cells += grid([0.02, 2.05, 3.80, o0 - 0.02], [2.22, 2.57, 3.32, HC - 0.03]) cells += grid([o1 + 0.02, 7.18], [0.08, 1.18, 2.22, 2.57, 3.32, HC - 0.03]) cells += [(o0 - 0.02, top + 0.02, o1 + 0.02, HC - 0.03)] # over the door's frame plates(P, FD, cells) # the frame: it stands on the landing and has 0.8 m of wall over it zb, ci, co = E["z"] + 0.002, 0.06, 0.07 fr = [(o0, zb), (i0, zb), (i0, head - ci), (i0 + ci, head), (i1 - ci, head), (i1, head - ci), (i1, zb), (o1, zb), (o1, top - co), (o1 - co, top), (o0 + co, top), (o0, top - co)] P.prism(FD, fr, 0.0, 0.06, SAT, c=0.012, segs=2) # the reveal, lined back to the pack's door frame d = (bq.YD - E["y"]) - 0.0015 dj = [(i0, 0.0), (i0, head - ci), (i0 + ci, head), (i1 - ci, head), (i1, head - ci), (i1, 0.0)] for a, b in zip(dj[:-1], dj[1:]): P.quad([FD.p(a[0], a[1], 0.0), FD.p(a[0], a[1], -d), FD.p(b[0], b[1], -d), FD.p(b[0], b[1], 0.0)], MAT_) # control pad, beside the frame P.box(FD, o0 - 0.20, 1.26, o0 - 0.08, 1.48, T, 0.036, GUN, c=0.005, segs=2, r=0.015, seg=3) P.box(FD, o0 - 0.18, 1.37, o0 - 0.10, 1.46, 0.036, 0.038, LGREEN, c=0.001, r=0.006, seg=3) for i in range(2): P.prism(FD, circle(o0 - 0.16 + 0.04 * i, 1.31, 0.012, 10), 0.036, 0.040, MAT_, c=0.0015) # ---- from here on the model's own wall, unchanged: chart display over the bench P.ring(FD, shape(0.91, 1.26, 3.19, 2.08, r=0.05, seg=4), shape(0.98, 1.33, 3.12, 2.01, r=0.025, seg=4), T, 0.045, SAT, c=0.008, segs=2, reveal_w=0.030, floor_mat=bq.BACK) x0, y0, x1, y1 = bq.at("screen_chart") P.quad([FD.p(0.989, 1.339, 0.0305), FD.p(3.111, 1.339, 0.0305), FD.p(3.111, 2.001, 0.0305), FD.p(0.989, 2.001, 0.0305)], SCREEN, uvs=[(x0, y0), (x1, y0), (x1, y1), (x0, y1)]) # bench: plinth, body, two cushions, padded back P.box(FD, 0.91, T + 0.001, 3.19, 0.10, T, 0.54, MAT_, c=0.004) P.box(FD, 0.85, 0.10, 3.25, 0.36, T, 0.60, SAT, c=0.012, segs=2) Fs = Frame(FD.p(0, 0.36, 0), FD.w, FD.u) for v0 in (0.87, 2.06): P.prism(Fs, shape(0.10, v0, 0.59, v0 + 1.17, r=0.035, seg=4), 0.0, 0.10, MATT, c=0.03, segs=3) bq.quilt(P, FD.at(dw=0.03), 0.871, 0.501, 3.229, 1.039, 9, 4, puff=0.040, seed=11) P.box(FD, 0.85, 0.46, 3.25, 0.499, T, 0.05, SAT, c=0.004) P.box(FD, 0.85, 1.041, 3.25, 1.085, T, 0.06, SAT, c=0.006, segs=2) for u0 in (0.85, 3.231): P.box(FD, u0, 0.50, u0 + 0.019, 1.04, T, 0.05, SAT, c=0.003) P.box(FD, 0.87, 0.50, 3.23, 1.04, T, 0.03, MAT_, c=0.0) def entry(P): """Inside the corridor's side door: a landing at the corridor's level with one lit step down to the cabin's floor.""" ax, yk, zl = E["x"], E["y"], E["z"] w0, w1 = ax - OPEN_W / 2 + 0.001, ax + OPEN_W / 2 - 0.001 # through the wall: the opening's width p0, p1 = ax - LANDING[0] / 2, ax + LANDING[0] / 2 # in the room ya, yb, yc = yk + 0.0015, YD + 0.016, YD + LANDING[1] Fz = Frame((0, 0, 0), (1, 0, 0), (0, 1, 0)) # two boxes, not one T-shaped outline: the cap of a concave outline is cut into triangles that can reach # outside it (here: through the wall beside the doorway) P.box(Fz, w0, ya, w1, yb - 0.001, T + OFF, zl - T, SAT, c=0.004) P.box(Fz, p0, yb, p1, yc, T + OFF, zl - T, SAT, c=0.004) Ft = Fz.at(dw=zl - T) n = 0.034 # the nosing along the three free edges plates(P, Ft, [(w0, ya, w1, yb), (p0 + n, yb, ax, yc - n), (ax, yb, p1 - n, yc - n)], mat=FLOOR, c=0.003) for a, b, c, d in ((p0, yc - n, p1, yc), (p0, yb, p0 + n, yc - n), (p1 - n, yb, p1, yc - n)): P.box(Ft, a + 0.001, b + 0.001, c - 0.001, d - 0.001, 0.0, T, GUN, c=0.003) Fr = Frame((p1, yc, 0), (-1, 0, 0), (0, 0, 1)) # the step's riser, seen from the room (w = +Y) P.box(Fr, 0.20, 0.080, LANDING[0] - 0.20, 0.096, 0.0, 0.004, LWARM, c=0.001) def props(P): def slate(x, y, z, yaw): c, s = math.cos(math.radians(yaw)), math.sin(math.radians(yaw)) F = Frame((x, y, z + OFF), (c, s, 0), (-s, c, 0)) P.prism(F, shape(-0.13, -0.09, 0.13, 0.09, r=0.016, seg=3), 0.0, 0.011, GUN, c=0.003) P.box(F, -0.115, -0.075, 0.115, 0.075, 0.011, 0.012, MAT_, c=0.0) def mug(x, y, z): F = Frame((x, y, z + OFF), (1, 0, 0), (0, 1, 0)) P.prism(F, circle(0, 0, 0.040, 20), 0.0, 0.095, SAT, c=0.004) P.prism(F, circle(0, 0, 0.033, 20), 0.095, 0.0955, bq.BACK, c=0.0) lx, ly = spots["lounge_table"] zl = T + OFF + LOUNGE_TABLE[2] slate(lx - 0.22, ly + 0.04, zl, 14.0) mug(lx + 0.34, ly - 0.10, zl) mx, my = spots["mess_table"] zm = T + OFF + MESS_TABLE[2] slate(mx - 0.42, my - 0.14, zm, -9.0) mug(mx + 0.50, my + 0.16, zm) mug(mx - 0.12, my - 0.22, zm) s0, s1 = SIDEBOARD["Fore"] xs = fore.x0 + fore.L mug(xs - s0 - 0.45, YD + 0.24, 0.85) # on the lounge sideboard slate(xs - s1 + 0.55, YD + 0.23, 0.85, 4.0) for k in range(3): # on the shelf over it mug(xs - s0 - 0.90 - 0.13 * k, YD + 0.12, 1.495) def decals(P): e = 0.0012 def d(fr, cu, cv, h, name, w): wd = h * bq.ATLAS.get(name + "_aspect", 1.0) P.decal(fr, cu - wd / 2, cv - h / 2, cu + wd / 2, cv + h / 2, w, bq.DECAL, bq.at(name)) for b in (fore, aft): u, v = TIES[b.key] d(b.FF, u, v + 0.20, 0.03, "txt_tie", T + e) # over the door, on the frame's head (the model's EXIT stood where the taller doorway is now) d(bq.FD, HX - E["x"], E["z"] + DOOR_TOP + HEAD_OVER + 0.10, 0.05, "txt_exit", 0.06 + e) return [("Fore_Wall_Hull", lambda P: knee_wall(P, fore, 23), "room"), ("Fore_Cant", lambda P: cant(P, fore), "room"), ("Fore_Wall_Corridor", corridor_fore, "room"), ("Fore_Gables", gables_fore, "room"), ("Fore_Floor", lambda P: floor(P, fore), "room"), ("Fore_Ceiling", lambda P: roof(P, fore), "room"), ("Mid_Ceiling", lambda P: roof(P, middle(), vent=True), "room"), ("Aft_Wall_Hull", lambda P: knee_wall(P, aft, 31), "room"), ("Aft_Cant", lambda P: cant(P, aft), "room"), ("Aft_Wall_Corridor", corridor_aft, "room"), ("Aft_Gables", gables_aft, "room"), ("Aft_Floor", lambda P: floor(P, aft), "room"), ("Aft_Ceiling", lambda P: roof(P, aft), "room"), ("Wall_Door", door_wall, "room"), ("Entry", entry, "room"), ("Lounge_Table", lambda P: _table(P, LOUNGE_TABLE[0], LOUNGE_TABLE[1], LOUNGE_TABLE[2], (-0.40, 0.40)), "lounge_table"), ("Mess_Table", lambda P: _table(P, MESS_TABLE[0], MESS_TABLE[1], MESS_TABLE[2], (-0.52, 0.52)), "mess_table"), ("Props", props, "room"), ("Decals", decals, "decal")] # ---- in the room ------------------------------------------------------------------------------------------------- def suite_objects(): return [o for o in bpy.data.objects if o.get("made_by") == TAG] def exists(): return any(o.type == "MESH" for o in suite_objects()) def _scene_of(coll): for sc in bpy.data.scenes: if coll == sc.collection or coll in sc.collection.children_recursive: return sc return bpy.context.scene def _apply_modifiers(ob, sc): if not ob.modifiers: return with bpy.context.temp_override(scene=sc, view_layer=sc.view_layers[0], object=ob, active_object=ob, selected_objects=[ob]): for md in list(ob.modifiers): bpy.ops.object.modifier_apply(modifier=md.name) if ob.modifiers: raise RuntimeError("the modifiers of %s were not applied" % ob.name) def _part_of(obs, name): return next((o for o in obs if o.get("model_part") == name), None) def _room_meshes(): return [o for o in bpy.data.collections[cc.ROOM].objects if o.type == "MESH" and o.get("made_by") in (TAG, cc.TAG)] def _decal_groups(me, door_x=None): """The model's lettering mesh as three meshes: on the window wall (faces look along +X), on the locker wall (-X), and the rest. door_x: leave out the lettering over the model's own door there (EXIT: the doorway is taller now).""" uv = me.uv_layers.active.data out = {} for key, pred in (("window wall", lambda n: n.x > 0.7), ("locker wall", lambda n: n.x < -0.7), ("room", lambda n: abs(n.x) <= 0.7)): verts, faces, uvs = [], [], [] for p in me.polygons: if not pred(p.normal): continue if door_x is not None and p.normal.y > 0.7 and abs(p.center.x - door_x) < 0.3 and p.center.z > 2.2: out["left_out"] = out.get("left_out", 0) + 1 continue base = len(verts) verts += [tuple(me.vertices[v].co) for v in p.vertices] faces.append(tuple(range(base, base + len(p.vertices)))) uvs += [tuple(uv[li].uv) for li in p.loop_indices] if not faces: continue nm = bpy.data.meshes.new(PRE + "Decals_" + key.replace(" ", "_")) nm.from_pydata(verts, [], faces) layer = nm.uv_layers.new(name="UVMap") for i, t in enumerate(uvs): layer.data[i].uv = t for mt in me.materials: nm.materials.append(mt) nm["made_for"] = "M-Ship v2 captain's quarters: the model's SM_CQ_Decals taken apart by wall (build/captains_suite.py)" out[key] = nm return out def _take_out(ob): data = ob.data bpy.data.objects.remove(ob) if isinstance(data, bpy.types.Mesh) and data.users == 0 and not data.name.startswith("SM_CQ_"): bpy.data.meshes.remove(data) elif isinstance(data, bpy.types.Light) and data.users == 0: bpy.data.lights.remove(data) def _tag(ob, part=None): ob["zone"] = cc.ROOM ob["made_by"] = TAG if part: ob["model_part"] = part return ob def _prepare(rep, m): """What is done to the cabin's own parts, each step only if it has not been done yet: its two end walls out to the room's ends; its sliding door, door wall, flat ceiling (with the four lamps under it), lettering mesh and the threshold step out of the ship (they are drawn again here to the corridor's door and the higher roof).""" lights = bpy.data.collections[cc.ROOM + " | lights"] cabin = [o for o in cc.cabin_objects() if o.type == "MESH"] model = bpy.data.scenes[cc.SCENE] fore, aft = Matrix.Translation((-LF, 0.0, 0.0)), Matrix.Translation((LA, 0.0, 0.0)) for name, shift in (("SM_CQ_Wall_Window", fore), ("SM_CQ_Wall_Locker", aft), ("SM_CQ_Locker_Door_1", aft), ("SM_CQ_Locker_Door_2", aft), ("SM_CQ_Locker_Door_3", aft)): ob = _part_of(cabin, name) if ob is None: raise RuntimeError("the cabin has no part " + name) if ob.get("suite_moved_m") is None: ob["suite_moved_m"] = -LF if shift is fore else LA ob.matrix_world = m @ shift @ cc.src_world(model.objects[name]) rep["moved"][name] = [round(v, 3) for v in ob.matrix_world.translation] lamp = next((o for o in lights.objects if o.name.startswith("Captain's cabin | window")), None) if lamp is not None and lamp.get("suite_moved_m") is None: mw = lamp.matrix_world.copy() mw.translation = mw.translation + (m.to_3x3() @ Vector((-LF, 0.0, 0.0))) lamp.matrix_world = mw lamp["suite_moved_m"] = -LF rep["moved"][lamp.name] = [round(v, 3) for v in mw.translation] going = [_part_of(cabin, name) for name in ("SM_CQ_Door_Leaf", "SM_CQ_Wall_Door", "SM_CQ_Ceiling", "SM_CQ_Decals")] going.append(bpy.data.objects.get("Captain's cabin | threshold step")) going += [o for o in lights.objects if o.get("made_by") == cc.TAG and o.name.startswith("Captain's cabin | ceiling fixture")] for ob in going: if ob is not None: rep["taken_out"].append(ob.name) _take_out(ob) def _build(rep, m, E): """Everything that is made here: lettering by wall, the parts, more of the model's chair, lamps, markers.""" bq = lib() root = bpy.data.objects[cc.ROOT] coll = bpy.data.collections[cc.ROOM] lights = bpy.data.collections[cc.ROOM + " | lights"] sc = _scene_of(coll) model = bpy.data.scenes[cc.SCENE] fore, aft = Matrix.Translation((-LF, 0.0, 0.0)), Matrix.Translation((LA, 0.0, 0.0)) # the model's lettering, wall by wall (from the model's own mesh) src = model.objects["SM_CQ_Decals"] own = cc.src_world(src) groups = _decal_groups(src.data, E["x"]) rep["decals"]["left_out"] = groups.pop("left_out", 0) for key, me in groups.items(): ob = bpy.data.objects.new("Captain's cabin | lettering, " + key, me) coll.objects.link(ob) ob.parent = root ob.matrix_world = m @ (fore if key == "window wall" else aft if key == "locker wall" else Matrix.Identity(4)) @ own _tag(ob) rep["decals"][key] = len(me.polygons) # the parts made here spots = _spots() for name, fn, kind in parts(E): P = bq.Part(PRE + name) fn(P) ob = P.build(coll, weighted=(kind != "decal")) _apply_modifiers(ob, sc) ob.name = "Captain's cabin | " + PRE + name ob.data["made_for"] = "M-Ship v2 captain's quarters, made round the model's cabin (build/captains_suite.py)" ob.parent = root if kind in spots: x, y = spots[kind] ob.matrix_world = m @ Matrix.Translation((x, y, bq.T + OFF)) else: ob.matrix_world = m _tag(ob) a = bq.audit(ob) rep["made"][PRE + name] = {"triangles": a["triangles"], "ngons": a["ngons"], "zero_area": a["zero_area"], "uv": a["uv"], "custom_normals": bool(ob.data.has_custom_normals)} # more of the model's chair base, seat = model.objects["SM_CQ_Chair_Base"], model.objects["SM_CQ_Chair_Seat"] for cname, x, y, yaw in spots["chairs"]: b = bpy.data.objects.new("Captain's cabin | SM_CQ_Chair_Base (%s)" % cname, base.data) coll.objects.link(b) b.parent = root b.matrix_world = m @ Matrix.Translation((x, y, bq.T + OFF)) @ Matrix.Rotation(math.radians(yaw), 4, "Z") s = bpy.data.objects.new("Captain's cabin | SM_CQ_Chair_Seat (%s)" % cname, seat.data) coll.objects.link(s) s.parent = b s.matrix_parent_inverse = seat.matrix_parent_inverse.copy() s.matrix_basis = seat.matrix_basis.copy() for o, sr in ((b, base), (s, seat)): for k in sr.keys(): if not k.startswith("_"): o[k] = sr[k] _tag(o, sr.name) rep["chairs_added"] = len(spots["chairs"]) # lamps: one under every light fixture of the roof (they glow by themselves only in Cycles), one in each bench's cove m3 = m.to_3x3() def ship(x, y, z): return m @ Vector((x, y, z)) def under(name, F, uf, v0, watts): z = -(m3 @ F.w) x = m3 @ F.u rot = Matrix((x, z.cross(x), z)).transposed().to_4x4() ob = cc._lamp(lights, root, name, "AREA", m @ F.p(uf + 0.90, v0 + 0.16, 0.06), watts, cc.COOL, size=(1.70, 0.18), rot=rot) ob["made_by"] = TAG n = 0 for b in list(bays().values()) + [middle()]: watts = LAMPS["ceiling"].get(b.key, cc.LAMPS["ceiling"]) k = 0 for uf in fixtures(b): for v0 in FIX_ROWS: k += 1 n += 1 under("Captain's cabin | ceiling fixture, %s %d" % (b.key.lower(), k), b.FM, uf, v0, watts) if b.key in BENCH: u0, u1 = BENCH[b.key] ob = cc._lamp(lights, root, "Captain's cabin | bench cove, %s" % b.key.lower(), "AREA", ship(b.x0 + (u0 + u1) / 2.0, bq.YO - 0.14, KH - 0.13), LAMPS["cove"], cc.WARM, size=(u1 - u0 - 0.6, 0.05), shadow=False) ob["made_by"] = TAG n += 1 rep["lamps_made"] = n # markers mx, my = spots["mess_table"] lx, ly = spots["lounge_table"] for name, role, p in (("Captain's mess table", "table", ship(mx, my, bq.T + MESS_TABLE[2])), ("Captain's lounge", "seat", ship(lx, bq.YO - 0.30, 0.50))): e = quarters.marker(root, name, p, role) e["made_by"] = TAG e["zone"] = cc.ROOM bpy.context.view_layer.update() obs = _room_meshes() lo, hi = cc._wbounds(obs) rep["room_in_ship"] = {"x": [round(lo.x, 3), round(hi.x, 3)], "y": [round(lo.y, 3), round(hi.y, 3)], "z": [round(lo.z, 3), round(hi.z, 3)], "objects": len(obs)} return rep def _start(): m, where = cc.placement() E = entrance(m) if abs(E["x"] - (-1.8)) > 0.01 or abs(E["z"] - cc.DROP) > 0.01: raise RuntimeError("the cabin does not stand on the door %s: %r" % (ENTRANCE, E)) return m, E, {"placement": where, "entrance": E, "moved": {}, "made": {}, "decals": {}, "taken_out": []} def extend(): """Fill the rest of the room round the cabin. Returns a report.""" if not cc.exists(): raise RuntimeError("the cabin is not in the room (captains_cabin.port() first)") if exists(): raise RuntimeError("the room is already filled (captains_suite.rebuild() draws it again, remove() takes it out)") m, E, rep = _start() _prepare(rep, m) return _build(rep, m, E) def rebuild(): """Draw everything made here again (after a change to the numbers at the top), in a room that is already filled. The cabin's own parts stay where they are.""" if not exists(): raise RuntimeError("nothing to draw again (captains_suite.extend() fills the room)") m, E, rep = _start() rep["removed"] = _clear() _prepare(rep, m) return _build(rep, m, E) def _clear(): """Everything made here goes: parts, lettering pieces, the extra chairs, lamps, markers.""" n = 0 for ob in suite_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) for me in [x for x in bpy.data.meshes if x.name.startswith(PRE) and x.users == 0]: bpy.data.meshes.remove(me) return n def remove(): """Take the added bays out and put the cabin back as the model has it.""" n = _clear() cc.remove() cc.port() return n # ---- checks ------------------------------------------------------------------------------------------------------ def _tris(o): me = o.data me.calc_loop_triangles() mw = o.matrix_world return [mw @ v.co for v in me.vertices], [tuple(t.vertices) for t in me.loop_triangles] def _cross(a, b): """How two meshes meet: (pairs of faces that cross, pairs that only touch or lie in one plane). A pair crosses when each face reaches more than EPS through the other's plane.""" from mathutils.bvhtree import BVHTree (va, ta), (vb, tb) = a, b if not ta or not tb: return 0, 0 pairs = BVHTree.FromPolygons([tuple(v) for v in va], ta, epsilon=0.0).overlap(BVHTree.FromPolygons([tuple(v) for v in vb], tb, epsilon=0.0)) def straddles(p, q): nrm = (q[1] - q[0]).cross(q[2] - q[0]) if nrm.length < 1e-12: return False nrm.normalize() d = [(x - q[0]).dot(nrm) for x in p] return max(d) > EPS and min(d) < -EPS cross = touch = 0 for i, j in pairs: p = [va[k] for k in ta[i]] q = [vb[k] for k in tb[j]] if straddles(p, q) and straddles(q, p): cross += 1 else: touch += 1 return cross, touch def _box(o): cs = [o.matrix_world @ Vector(c) for c in o.bound_box] return (Vector((min(c.x for c in cs), min(c.y for c in cs), min(c.z for c in cs))), Vector((max(c.x for c in cs), max(c.y for c in cs), max(c.z for c in cs)))) def _apart(a, b): return any(a[0][i] > b[1][i] or b[0][i] > a[1][i] for i in range(3)) def check(others=True): """For check_interior.py: does the room reach from end to end, is everything of it inside the hull, does a part cross another part of the room, and (others=True) does anything of the room cross anything else in the ship.""" out = {"present": exists()} if not out["present"]: return out import shipfit shell = quarters.Shell() env = None room = _room_meshes() mine = [o for o in room if o.get("made_by") == TAG] worst, near = (1e9, None), {} for o in room: mw = o.matrix_world pts = [mw @ v.co for v in o.data.vertices] ds = [shell.depth(q) for q in pts] d = min(ds) if d < 0.10: # the hull's skin alone says "outside" up under the roof pod, where its crest is hidden and the pod is the # ship's skin: ask the two shells as one for every point that is near or beyond the hull's if env is None: env = shipfit.Envelope() d = min((dd if dd >= 0.30 else (env.clearance(q) if env.inside(q) else -env.clearance(q))) for dd, q in zip(ds, pts)) if d < worst[0]: worst = (d, o.name) if d < 0.10: near[o.name] = round(d, 3) lo, hi = cc._wbounds(room) out["objects"] = {"all": len(room), "of_the_model": sum(1 for o in room if o.get("model_part")), "made_in_the_ship": sum(1 for o in room if not o.get("model_part"))} out["in_ship"] = {"x": [round(lo.x, 3), round(hi.x, 3)], "y": [round(lo.y, 3), round(hi.y, 3)], "z": [round(lo.z, 3), round(hi.z, 3)]} out["room_length_m"] = round(LF + LA + 2 * lib().HX, 3) out["room_height_m"] = {"at_the_corridor's_wall": HC, "at_the_bunk_wall": lib().H, "at_the_knee_wall_of_the_two_bays": KH} out["least_depth_inside_the_hull_m"] = [round(worst[0], 4), worst[1]] # hull and roof pod as one skin out["parts_closer_than_10_cm_to_the_hull"] = near bulk = bpy.data.objects.get("Bulkhead | bridge aft") if bulk is not None: out["clear_of_the_bridge_bulkhead_m"] = round(_box(bulk)[0].x - hi.x, 3) tris, boxes = {}, {o.name: _box(o) for o in room} def tri(o): if o.name not in tris: tris[o.name] = _tris(o) return tris[o.name] crossing, touching = [], 0 for a in mine: for b in room: if b is a or (b in mine and b.name <= a.name) or _apart(boxes[a.name], boxes[b.name]): continue c, t = _cross(tri(a), tri(b)) touching += t if c: crossing.append([a.name, b.name, c]) out["parts_of_the_room_that_cross_each_other"] = crossing out["face_pairs_that_only_touch"] = touching if others: sc = _scene_of(bpy.data.collections[cc.ROOM]) skip = set(o.name for o in room) found, seen, names = [], 0, [] for o in sc.objects: if o.type != "MESH" or o.name in skip or o.hide_render or o.data.name.startswith("DECAL"): continue bo = _box(o) if _apart(bo, (lo, hi)): continue hit = [r for r in room if not _apart(bo, boxes[r.name])] if not hit: continue seen += 1 names.append(o.name) to = _tris(o) for r in hit: c, t = _cross(tri(r), to) if c: found.append([r.name, o.name, c]) out["other_objects_near_the_room"] = seen out["other_objects_near_the_room_names"] = names[:60] out["room_parts_that_cross_something_else"] = found # the way in: straight through the corridor's side door. Rays from inside its frame into the room, over the door's # whole clear opening: none may meet a part of the room (nothing of the room cuts the door) from mathutils.bvhtree import BVHTree E = entrance() ship = E["in_ship"] verts, faces = [], [] for o in room: v, t = tri(o) base = len(verts) verts += [tuple(x) for x in v] faces += [tuple(base + i for i in f) for f in t] tree = BVHTree.FromPolygons(verts, faces, epsilon=0.0) side = -1.0 if ship["frame_face_y"] < 0 else 1.0 floor_z = ship["sill_z"] - E["z"] n_way = n_blocked = n_top = n_open = 0 x = ship["axis_x"] - DOOR_CLEAR / 2 + 0.02 while x < ship["axis_x"] + DOOR_CLEAR / 2 - 0.01: z = ship["sill_z"] + DOOR_COAMING + 0.02 while z < ship["sill_z"] + DOOR_TOP - 0.01: hit = tree.ray_cast(Vector((x, ship["frame_face_y"] - side * 0.05, z)), Vector((0.0, side, 0.0)), 1.2)[0] n_way += 1 n_blocked += hit is not None z += 0.05 x += 0.04 entry = next((o for o in room if o.data.name == PRE + "Entry"), None) level = None if entry is not None: tops = [(entry.matrix_world @ entry.data.vertices[p.vertices[0]].co).z for p in entry.data.polygons if p.normal.z > 0.9 and p.area > 0.05] level = round(max(tops) - ship["sill_z"], 4) if tops else None out["entrance"] = {"door": E["door"], "frame": E["frame"], "in_ship": ship, "second_door_behind_it": any(o.get("model_part") == "SM_CQ_Door_Leaf" for o in room), "landing_above_the_sill_m": level, "rays_through_the_door's_whole_opening": n_way, "of_them_blocked_by_the_room": n_blocked, "room_height_at_the_door_m": HC, "doorway_head_m": round(E["z"] + DOOR_TOP + HEAD_OVER, 3)} lights = bpy.data.collections.get(cc.ROOM + " | lights") out["lamps"] = len(lights.objects) if lights else 0 return out