""" The fore main deck raised, the sensor room extended, a lift room beside it. The user, 2026-10-13, with a side view of the fore main deck (its whole block outlined, arrows up to the underside of the command deck), the deck plan (the block outlined; forward of it two rectangles, "sensor room" over the middle and the port side, "lift room" to starboard, a link between them) and a view of the space under the block: "this whole section should be moved up and stick to the [command deck] so that I can utilize the space underneath better. this way we can make another room beneath so its gonna be 3 floor I will add a lift later on, sensor room needs to be extended". apply() does it to the file as it is (zone 10.4 found by mesh and place), the same for the open, hand-edited file and for one built again by build_interior.py: spine the fore passage was six bays with a junction cube (the sensor room) at its bow end. It ends one bay earlier now: its fifth bay is taken out, its last bay and the door behind it move one bay aft, the cube goes. (A car's last bay is dressed as an end; the fifth was a middle bay.) raise everything of the zone, with its markers, goes up until its roof meets the lowest thing of the command deck above it: the control room corridor's wall pieces, which reach 0.9 m below that deck. bow forward of the door, at the new level, three two-bay cars of the same pack: one on the centre line and one to port, joined into one room (the walls between them left out; a beam on two piers where they stood, sill plates over the two gutters): the sensor room. One to starboard with a door from the sensor room: the lift room. The lift is the user's to add. door the door from the sub bay is 1.4 m above the bay's deck now: the bulkhead's opening goes up with it, the lining's lintel and ledge make room, and a landing with a flight of steps to each side stands before it. Everything removed, moved or added is written down in the text block RECORD; remove() undoes it from that. """ import json import math import os import sys import bpy from mathutils import Matrix, Vector sys.dont_write_bytecode = True HERE = os.path.dirname(os.path.abspath(__file__)) if HERE not in sys.path: sys.path.insert(0, HERE) import quarters # noqa: E402 import reactor_room as rr # noqa: E402 import shipfit # noqa: E402 TAG = "fore_deck" ZONE = "10.4 | Fore main deck" BAY_ZONE = "10.1 | Sub bay" LINING_COLL = "10.1 | Sub bay | straight lining" RECORD = "FORE DECK | what it was" MADE_FOR = "M-Ship v2 fore main deck, raised (build/fore_deck.py)" PRE = "SM_FD_" NAME = "Fore deck | " Z2 = -3.36 # the main deck's sill level (the sub bay's deck) BAY = 3.60 DOOR_GAP = 0.35 # a car ends this far from the door plane it faces CAR_HALF = 2.60 # a car's outer half width; the side rows' axes lie 2 * CAR_HALF from the passage's OFF = 0.002 N_BOW = 2 # bays of the three cars forward of the passage's door BEAM_D = 0.35 # the beam under the ceiling where two cars' walls stood PIER_D = 0.45 STRIP = (2.505, 2.695) # between the two rows of sill plates over the gutters (ship y, port side) # the landing before the raised door in the sub bay LAND_D = 1.80 # its depth from the lining's face LAND_W = 1.80 # half its width (the door's wall piece is 3.6 m wide) RISERS = 8 TREAD = 0.28 RAIL_H = 0.90 NICHE = 0.36 # the landing reaches this far into the door's niche in the lining (to the door's trim) SILL_T = 0.10 # the door's sill plate is this thick: the bulkhead's opening begins that far below the sill RAIL = "EN_SM_Rail_A" SILL_PLATE = "EN_SM_GlassFloor_Door_B" _kind, _bb, _mid = rr._kind, rr._bb, rr._mid def _zone(): return [o for o in bpy.data.objects if str(o.get("zone", "")).startswith(ZONE)] def exists(): return RECORD in bpy.data.texts def record(): return json.loads(bpy.data.texts[RECORD].as_string()) if exists() else None class Deck: """The zone as it stands: the passage's bays, and which objects are what.""" def __init__(self): self.objs = _zone() tiles = sorted([o for o in self.objs if _kind(o) == "EN_SM_GlassFloor_A" and abs(_mid(o).y) < 0.2], key=lambda o: _mid(o).x) if len(tiles) < 3: raise RuntimeError("no fore passage in this file (zone %r)" % ZONE) self.bays = [(_bb(o)[0].x, _bb(o)[1].x) for o in tiles] self.coll = bpy.data.collections[ZONE] self.lights = bpy.data.collections.get(ZONE + " | lights") or self.coll self.decals = bpy.data.collections.get(ZONE + " | lettering") or self.coll self.root = bpy.data.objects.get(quarters.ROOT) def split(self): """(objects of the passage's last-but-one bay, objects of its last bay and the door behind it, the cube).""" xa, xb = self.bays[-2][0], self.bays[-1][0] cube_x = self.bays[-1][1] + 2 * DOOR_GAP + 0.30 gone, shift, cube = [], [], [] for o in self.objs: c = _mid(o) if _kind(o).startswith("EN_SM_Intersection_") or c.x > cube_x: cube.append(o) elif c.x >= xb - 0.05: shift.append(o) elif c.x >= xa - 0.05 and abs(c.y) < CAR_HALF - 0.01: gone.append(o) return gone, shift, cube def markers(self): lo = Vector((min(_bb(o)[0].x for o in self.objs), min(_bb(o)[0].y for o in self.objs), 0.0)) hi = Vector((max(_bb(o)[1].x for o in self.objs), max(_bb(o)[1].y for o in self.objs), 0.0)) out = [] for o in bpy.data.collections[quarters.MARKERS].objects: p = o.matrix_world.translation if lo.x - 0.5 < p.x < hi.x + 0.5 and lo.y - 0.5 < p.y < hi.y + 0.5 and p.z < Z2 + 1.2: out.append(o) return out def headroom(self, objs): """How far `objs` can rise before they meet the command deck: (distance, what they meet, its underside).""" top = max(_bb(o)[1].z for o in objs if o.type == "MESH") lo = Vector((min(_bb(o)[0].x for o in objs), min(_bb(o)[0].y for o in objs))) hi = Vector((max(_bb(o)[1].x for o in objs), max(_bb(o)[1].y for o in objs))) best = (1e9, None) for o in bpy.data.objects: if o.type != "MESH" or not (str(o.get("zone", "")).startswith("10.5") or o.name.startswith("Deck | ")) or _kind(o).startswith("DECAL_"): continue a, b = _bb(o) if b.x < lo.x or a.x > hi.x or b.y < lo.y or a.y > hi.y or a.z < top - 0.01: continue if a.z < best[0]: best = (a.z, o.name) return best[0] - top, best[1], best[0] # ---- small own parts ----------------------------------------------------------------------------------------------- def _box(own, lo, hi, material, faces="xXyYzZ", uv=None): (x0, y0, z0), (x1, y1, z1) = lo, hi uv = uv or rr._rect(rr.PLAIN, (0.0, 0.0), (1.0, 1.0)) quads = {"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 f in faces: pts, n = quads[f] own.face(pts, material, uv, n) def mesh_beam(length, height): """The pier, beam, pier where the walls between the two cars of the sensor room stood. Local frame: x along the room from its aft end, y across (centred on the old wall line), z from the floor.""" own = rr.Own([rr.DARK]) hw_p, hw_b = 0.40 - OFF, 0.20 zb = height - BEAM_D for xa, xb in ((0.0, PIER_D), (length - PIER_D, length)): _box(own, (xa, -hw_p, OFF), (xb, hw_p, zb), rr.DARK, faces="xXyY") _box(own, (0.0, -hw_b, zb), (length, hw_b, height + 0.30), rr.DARK, faces="yYz") # the piers' heads under the beam, where they are wider than it for xa, xb in ((0.0, PIER_D), (length - PIER_D, length)): for ya, yb in ((-hw_p, -hw_b), (hw_b, hw_p)): own.face([(xa, ya, zb), (xb, ya, zb), (xb, yb, zb), (xa, yb, zb)], rr.DARK, rr._rect(rr.PLAIN, (0, 0), (1, 1)), (0, 0, 1)) return own.finish(PRE + "Beam", "pier, beam, pier of the sensor room: where the walls between the passage's car and the port car stood") def mesh_strip(length): own = rr.Own([rr.DARK]) w = STRIP[1] - STRIP[0] n = max(1, int(round(length / BAY))) for i in range(n): own.face([(i * length / n, 0.0, 0.0), ((i + 1) * length / n, 0.0, 0.0), ((i + 1) * length / n, w, 0.0), (i * length / n, w, 0.0)], rr.DARK, rr._rect(rr.PLAIN, (0, 0), (1, 1)), (0, 0, 1)) return own.finish(PRE + "Floor_Strip", "floor strip between the two rows of sill plates of the sensor room, under its beam") def _bar(own, a, b, w, material): """A square bar from a to b (points in the y-z plane at one x; the bar may slope), w thick.""" (x, ya, za), (_, yb, zb) = a, b d = Vector((0.0, yb - ya, zb - za)) n = Vector((0.0, -d.z, d.y)).normalized() * (w / 2.0) # across the bar, in the y-z plane uv = rr._rect(rr.PLAIN, (0.0, 0.0), (1.0, 1.0)) ring = [] for p in (Vector(a), Vector(b)): ring.append([p + Vector((-w / 2.0, 0, 0)) - n, p + Vector((w / 2.0, 0, 0)) - n, p + Vector((w / 2.0, 0, 0)) + n, p + Vector((-w / 2.0, 0, 0)) + n]) mid = (Vector(a) + Vector(b)) * 0.5 for i in range(4): j = (i + 1) % 4 q = [ring[0][i], ring[0][j], ring[1][j], ring[1][i]] c = (q[0] + q[1] + q[2] + q[3]) * 0.25 own.face([tuple(v) for v in q], material, uv, tuple(c - mid)) for k, sgn in ((0, -1.0), (1, 1.0)): own.face([tuple(v) for v in ring[k]], material, uv, tuple(d * sgn)) def mesh_podium(height): """The landing before the raised door and a flight of steps to each side. Local frame: origin on the bay's deck under the door's middle, at the lining's face; -x into the bay, +x into the door's niche; z up.""" own = rr.Own([rr.RIM, rr.DARK, rr.SHINY]) H, r = height, height / RISERS top = rr._rect(rr.RIM_UV, (0.0, 0.0), (1.0, 1.0)) plain = rr._rect(rr.PLAIN, (0.0, 0.0), (1.0, 1.0)) B = 0.008 # (its faces begin on the bay's deck, which lies 5 mm above the sill level) own.face([(-LAND_D, -LAND_W, H), (0.0, -LAND_W, H), (0.0, LAND_W, H), (-LAND_D, LAND_W, H)], rr.RIM, top, (0, 0, 1)) own.face([(0.0, -LAND_W + 0.03, H), (NICHE, -LAND_W + 0.03, H), (NICHE, LAND_W - 0.03, H), (0.0, LAND_W - 0.03, H)], rr.RIM, top, (0, 0, 1)) # in the niche own.face([(-LAND_D, -LAND_W, B), (-LAND_D, LAND_W, B), (-LAND_D, LAND_W, H), (-LAND_D, -LAND_W, H)], rr.DARK, plain, (-1, 0, 0)) for s in (1.0, -1.0): for k in range(1, RISERS + 1): ya, yb = s * (LAND_W + (k - 1) * TREAD), s * (LAND_W + k * TREAD) z1, z0 = H - (k - 1) * r, max(B, H - k * r) own.face([(-LAND_D, ya, z0), (0.0, ya, z0), (0.0, ya, z1), (-LAND_D, ya, z1)], rr.DARK, plain, (0, s, 0)) # riser if k < RISERS: own.face([(-LAND_D, ya, z0), (0.0, ya, z0), (0.0, yb, z0), (-LAND_D, yb, z0)], rr.RIM, top, (0, 0, 1)) # tread own.face([(-LAND_D, ya, B), (-LAND_D, yb, B), (-LAND_D, yb, z0), (-LAND_D, ya, z0)], rr.DARK, plain, (-1, 0, 0)) # the flight's open side # a handrail on each flight's open side: two posts, a top rail and a knee rail parallel to the nosings xr = -LAND_D + 0.05 for s in (1.0, -1.0): y0, y1 = s * (LAND_W + 0.04), s * (LAND_W + (RISERS - 1) * TREAD - 0.04) z0, z1 = H, r # the landing's level; the lowest tread's for y, z in ((y0, z0), (y1, z1)): _bar(own, (xr, y, z + OFF), (xr, y, z + RAIL_H), 0.04, rr.SHINY) for h in (RAIL_H, RAIL_H * 0.5): _bar(own, (xr, y0, z0 + h), (xr, y1, z1 + h), 0.04, rr.SHINY) return own.finish(PRE + "Door_Podium", "landing before the fore passage's raised door in the sub bay, with a flight of %d risers to each side" % RISERS) # ---- the record ---------------------------------------------------------------------------------------------------- def _take(o, rec): rec["removed"].append(rr._snap(o)) if o.data is not None: o.data.use_fake_user = True bpy.data.objects.remove(o, do_unlink=True) def _move(o, d, rec): rec["moved"].append([o.name, list(d)]) o.matrix_world = Matrix.Translation(d) @ o.matrix_world def _added(before, rec, tag=True): new = [o for o in bpy.data.objects if o not in before] for o in new: if tag: o["made_by"] = TAG rec["added"].append(o.name) return new # ---- building ------------------------------------------------------------------------------------------------------ def apply(kit=None, eng=None, stages=("spine", "raise", "bow", "door")): if exists(): raise RuntimeError("the fore deck is already raised (text block %r); remove() undoes it" % RECORD) deck = Deck() rec = {"version": 1, "removed": [], "moved": [], "added": [], "meshes": [], "swapped": [], "bays_before": len(deck.bays)} coll, root = deck.coll, deck.root out = {} marks = deck.markers() # (found before the cube goes: the sensor module's is in it) # 1. the passage ends one bay earlier if "spine" in stages: gone, shift, cube = deck.split() out["spine"] = {"bay_taken_out": len(gone), "moved_one_bay_aft": len(shift), "cube": len(cube)} for o in gone + cube: _take(o, rec) for o in shift: _move(o, (-BAY, 0.0, 0.0), rec) bpy.context.view_layer.update() deck = Deck() x_door = deck.bays[-1][1] + DOOR_GAP # the door plane at the passage's bow end # 2. everything goes up to the command deck dz = 0.0 if "raise" in stages: body = [o for o in deck.objs] gap, what, under = deck.headroom(body) dz = round(gap - OFF, 4) for o in body + marks: _move(o, (0.0, 0.0, dz), rec) out["raise"] = {"by_m": dz, "meets": what, "its_underside_z": round(under, 3), "objects": len(body), "markers": [m.name[9:] for m in marks]} bpy.context.view_layer.update() rec["raised_by"] = dz rec["x_door"] = x_door zs = Z2 + dz # the raised deck's sill level # 3. the sensor room and the lift room, forward of the door if "bow" in stages: import bl_kit import cars import units kit = kit or bl_kit.Kit(log=lambda *a: None) eng = eng or cars.Engineer(units.Demo(cars.WHITE_MAP)) before = set(bpy.data.objects) stats = {} def P(unit, x, y, yaw=0.0): return units.place(kit, unit.items, coll, units.frame(x, y, zs, yaw), zone=ZONE, lights=deck.lights, decals=deck.decals, parent=root, stats=stats) def merged(unit, s): """A car without what belongs to the wall on side s (local y): the gutter's bottom, the wall ends' trims.""" return unit.filtered(lambda it: not (it.kind == "mesh" and it.name in ("SM_GlassFloor_Side_A", "SM_Wall_HB_Edge") and it.center.y * s > 0.5)) both = [(k, 1) for k in range(N_BOW)] P(merged(eng.car("blue", N_BOW, door_a=True, door_b=False, side_doors=both + [(0, -1)], props="tidy"), 1), x_door, 0.0) P(merged(eng.car("blue", N_BOW, door_a=False, door_b=False, side_doors=[(k, -1) for k in range(N_BOW)], props="tidy"), -1), x_door, 2 * CAR_HALF) P(eng.car("blue", N_BOW, door_a=False, door_b=False, side_doors=[(0, 1)], props="tidy"), x_door, -2 * CAR_HALF) P(eng.conn, x_door + DOOR_GAP + 0.5 * BAY, -CAR_HALF, -90.0) new = _added(before, rec) out["bow"] = {"pack_pieces": dict(stats)} # where the two cars of the sensor room meet: sill plates over the two gutters, a strip between them, # a beam on two piers under the ceiling xa, xb = x_door + DOOR_GAP, x_door + DOOR_GAP + N_BOW * BAY before = set(bpy.data.objects) src = next((o for o in deck.objs if _kind(o) == SILL_PLATE), None) if src is not None: rot = Matrix.Rotation(math.radians(90.0), 4, "Z") @ src.matrix_world.to_3x3().to_4x4() cs = [rot @ v.co for v in src.data.vertices] lo = Vector((min(c.x for c in cs), min(c.y for c in cs), max(c.z for c in cs))) for i in range(N_BOW): for ya in (BAY / 2.0 + OFF, STRIP[1]): m = Matrix.Translation(Vector((xa + i * BAY, ya, zs)) - lo) @ rot o = quarters.place_mesh(src.data, NAME + "sill plate over the gutters %d" % (len(rec["added"]) + 1), coll, root, m, ZONE) o["ue_mesh"] = src.data["ue_mesh"] strip = mesh_strip(xb - xa) beam = mesh_beam(xb - xa, 3.90) rec["meshes"] += [strip.name, beam.name] quarters.place_mesh(strip, NAME + "floor strip under the beam", coll, root, Matrix.Translation((xa, STRIP[0], zs - OFF)), ZONE) quarters.place_mesh(beam, NAME + "sensor room beam", coll, root, Matrix.Translation((xa, CAR_HALF, zs)), ZONE) out["bow"]["objects"] = len(new) + len(_added(before, rec)) # 4. the door from the sub bay if "door" in stages and dz > 0.0: out["door"] = _door(rec, deck, dz) txt = bpy.data.texts.new(RECORD) txt.write(json.dumps(rec)) bpy.context.view_layer.update() out["objects"] = {"removed": len(rec["removed"]), "moved": len(rec["moved"]), "added": len(rec["added"])} return out def _door(rec, deck, dz): """The fore bulkhead's opening raised, the lining's lintel and ledge make room, a landing with two flights.""" root = deck.root bay = bpy.data.collections[BAY_ZONE] lin = bpy.data.collections.get(LINING_COLL) or bay zs = Z2 + dz res = {} bulk = bpy.data.objects.get("Bulkhead | sub bay fore") lintel = bpy.data.objects.get("Pier | fore door lintel") ledge = bpy.data.objects.get("Ledge | fore end wall") if bulk is None: return {"skipped": "no fore bulkhead in this file"} a, b = _bb(bulk) x_room, x_fore = a.x, b.x # its sub bay face, its fore face (the door plane) # the bulkhead again, with the lower opening where the door is now fit = shipfit.Fit(["M_SHIP_Hull", "Moonpool | hull lining"]) before = set(bpy.data.objects) mat = bulk.data.materials[0] if bulk.data.materials else None shipfit.bulkhead_x("Bulkhead | sub bay fore (new)", bay, root, fit, ("Moonpool | hull lining",), x_fore - 0.15, x_fore, x_room, Z2, 7.2, openings=[(-1.8 - OFF, 1.8 + OFF, zs - SILL_T - OFF, zs + 3.9 + OFF), (-1.8, 1.8, 3.14, 3.14 + 3.8)], material=mat) tmp = [o for o in bpy.data.objects if o not in before] if len(tmp) == 1 and tmp[0].type == "MESH": new_me = tmp[0].data new_me.name = "Bulkhead | sub bay fore (door raised)" rec["swapped"].append([bulk.name, bulk.data.name, new_me.name]) bulk.data.use_fake_user = True bulk.data = new_me bpy.data.objects.remove(tmp[0], do_unlink=True) res["bulkhead_opening_z"] = [round(zs - SILL_T, 3), round(zs + 3.9, 3)] # the lining at the door: its lintel goes, its ledge is parted, jambs and a lintel above the raised door before = set(bpy.data.objects) face = x_room - 0.61 # the lining's face lmat = None if lintel is not None: la, lb = _bb(lintel) face = la.x lmat = lintel.data.materials[0] if lintel.data.materials else None _take(lintel, rec) head = zs + 3.9 + 0.02 if ledge is not None: ea, eb = _bb(ledge) lmat = lmat or (ledge.data.materials[0] if ledge.data.materials else None) _take(ledge, rec) for s, side in ((1.0, "port"), (-1.0, "starboard")): ya, yb = sorted((s * 2.61, s * max(abs(ea.y), abs(eb.y)))) shipfit.box("Ledge | fore end wall, " + side, lin, root, ea.x, eb.x, ya, yb, ea.z, eb.z, material=lmat) cap = ea.z else: cap = Z2 + 4.5 for s, side in ((1.0, "port"), (-1.0, "starboard")): ya, yb = sorted((s * 1.79, s * 2.61)) shipfit.box("Pier | fore door jamb above the lining, " + side, lin, root, face, x_room - 0.10 - OFF, ya, yb, cap, head, material=lmat) shipfit.box("Pier | fore door lintel (raised door)", lin, root, face, x_room, -2.61, 2.61, head, head + 0.60, material=lmat) # landing and steps pod = mesh_podium(dz) rec["meshes"].append(pod.name) quarters.place_mesh(pod, "Sub bay | landing and steps to the fore passage", bay, root, Matrix.Translation((face, 0.0, Z2)), BAY_ZONE) for y in (-0.85, 0.85): quarters.put(RAIL, bay, root, BAY_ZONE, (face - LAND_D + 0.05, y, zs), yaw=90.0, anchor="00-", name="Sub bay | rail on the fore passage's landing") _added(before, rec) res["landing"] = {"top_z": round(zs, 3), "risers": RISERS, "rise_m": round(dz / RISERS, 4), "tread_m": TREAD, "flights_reach_y": round(LAND_W + (RISERS - 1) * TREAD, 2)} return res def remove(): """Back to the fore main deck as it was, from the record.""" rec = record() if rec is None: return {"present": False} for n in rec["added"]: o = bpy.data.objects.get(n) if o is not None: d = o.data bpy.data.objects.remove(o, do_unlink=True) if isinstance(d, bpy.types.Light) and d.users == 0: bpy.data.lights.remove(d) for name, old, new in rec["swapped"]: o = bpy.data.objects.get(name) if o is not None and old in bpy.data.meshes: o.data = bpy.data.meshes[old] me = bpy.data.meshes.get(new) if me is not None and me.users == 0: bpy.data.meshes.remove(me) for name, d in reversed(rec["moved"]): o = bpy.data.objects.get(name) if o is not None: o.matrix_world = Matrix.Translation((-d[0], -d[1], -d[2])) @ o.matrix_world back = 0 for d in rec["removed"]: if d["name"] in bpy.data.objects: continue data = None if d["type"] == "MESH": data = bpy.data.meshes.get(d["data"]) elif d["type"] == "LIGHT": data = bpy.data.lights.get(d["data"]) if data is None and d["type"] != "EMPTY": continue o = bpy.data.objects.new(d["name"], data) for c in d["collections"]: if c in bpy.data.collections: bpy.data.collections[c].objects.link(o) if d["parent"] and d["parent"] in bpy.data.objects: o.parent = bpy.data.objects[d["parent"]] o.matrix_world = Matrix(d["matrix"]) for k, v in d["props"].items(): o[k] = v for slot, (link, mat) in zip(o.material_slots, d["slots"]): if link == "OBJECT": slot.link = "OBJECT" slot.material = bpy.data.materials.get(mat) if mat else None back += 1 for n in rec["meshes"]: me = bpy.data.meshes.get(n) if me is not None and me.users == 0: rr._forget(n) bpy.data.texts.remove(bpy.data.texts[RECORD]) bpy.context.view_layer.update() return {"present": True, "taken_out": len(rec["added"]), "moved_back": len(rec["moved"]), "put_back": back} def forget(): """Drop the record without touching any object (for a rebuild, which throws the old layout away).""" rec = record() if rec is None: return False bpy.data.texts.remove(bpy.data.texts[RECORD]) return True # ---- checking ------------------------------------------------------------------------------------------------------ def check(others=True): out = {"present": exists()} if not out["present"]: return out rec = record() dz = rec["raised_by"] zone = _zone() meshes = [o for o in zone if o.type == "MESH" and not _kind(o).startswith("DECAL_")] out["raised_by_m"] = dz out["objects"] = {"zone": len(zone), "removed": len(rec["removed"]), "moved": len(rec["moved"]), "added": len(rec["added"]), "missing": [n for n in rec["added"] if n not in bpy.data.objects][:10]} top = max(_bb(o)[1].z for o in meshes) low = min(_bb(o)[0].z for o in meshes) out["zone_z"] = [round(low, 3), round(top, 3)] deck = Deck() out["passage_bays"] = len([b for b in deck.bays if b[1] <= rec["x_door"] + 0.01]) gap, what, under = deck.headroom([o for o in meshes if _bb(o)[1].x < rec["x_door"] + 0.5]) out["under_the_command_deck"] = {"gap_m": round(gap, 4), "to": what} env = shipfit.Envelope() worst, outside, n = (9.0, None, None), {}, 0 added = set(rec["added"]) for o in meshes: a, b = _bb(o) if not (o.name in added or max(abs(a.y), abs(b.y)) > 6.6 or b.x > 40.5): continue n += 1 r = shipfit.check_inside(o, env, spacing=0.15, clear_min=0.0) if r["outside"]: outside[o.name] = [_kind(o), r["outside"], r["furthest_outside_m"], r["outside_sample"][:1]] if r["least_clearance_m"] < worst[0]: worst = (r["least_clearance_m"], o.name, r["least_clearance_at"]) out["inside_the_ship"] = {"objects_sampled": n, "outside": outside, "least_clearance_m": worst[0], "at": worst[1], "point": worst[2]} if others: mine = [bpy.data.objects[nm] for nm in rec["added"] if nm in bpy.data.objects and bpy.data.objects[nm].type == "MESH"] scene = (meshes[0].users_scene or [bpy.context.scene])[0] names = {o.name for o in mine} lo = Vector((min(_bb(o)[0].x for o in mine) - 1.0, min(_bb(o)[0].y for o in mine) - 1.0, min(_bb(o)[0].z for o in mine) - 1.0)) hi = Vector((max(_bb(o)[1].x for o in mine) + 1.0, max(_bb(o)[1].y for o in mine) + 1.0, max(_bb(o)[1].z for o in mine) + 1.0)) near = [] for o in scene.objects: if o.type != "MESH" or o.name in names or o.name.startswith("KIT | "): continue a, b = _bb(o) if any(a[i] > hi[i] or lo[i] > b[i] for i in range(3)): continue near.append(o) got = rr.crossings(mine, near) bad = {k: v for k, v in got.items() if any(p.startswith(("[ship]", PRE)) for p in k.split(" x "))} out["crossings"] = {"against": len(near), "with_the_ship_or_parts_made_here": bad, "pack_pieces": len(got) - len(bad), "pack_pairs": {k: v for k, v in sorted(got.items(), key=lambda kv: -kv[1])[:40] if k not in bad}} # the raised section against what is above and beside it: the command deck, the decks, the hull body = [o for o in meshes if o.name not in names] above = [o for o in scene.objects if o.type == "MESH" and (str(o.get("zone", "")).startswith("10.5") or o.name.startswith(("Deck | ", "Bulkhead | "))) and not _kind(o).startswith("DECAL_")] cache = {} hits = {} for a_ in above: ga = rr._geo(a_, cache) for b_ in body: if _bb(b_)[1].z < ga[3].z - 0.05: continue k = rr._crossing(ga, rr._geo(b_, cache)) if k: key = "%s x %s" % (rr._label(a_), rr._label(b_)) hits[key] = hits.get(key, 0) + k out["raised_section_against_the_command_deck"] = hits return out