""" The control room corridor's floor, brought up to its door sills. The user, 2026-10-13, on a marked-up picture of the corridor (crosses on the three stairs in view, a line along the sill level from wall to wall, arrows on the white floor pointing up): "corridor needs to be level, delete stairs and level the floor" The corridor is the Engineer pack's sunken car: its white floor lay 0.9 m under the door sills, with a five-step stair at each of its four doors. Now: * the four stairs and the four risers under the sills are gone; * the floor itself is the same floor, 0.9 m higher: the white tiles, the two grated gutters with the pipes in them and the black strips along the walls (28 pieces), wall to wall at sill level (z 3.14); * the two side doors get the pack's sill plate (the two end doors had theirs), so the floor runs on through the doorways into the rooms beside the corridor; * what stood on the wall's foot below the new floor is taken out with it (it would be under the floor, or cut by it): the short pipes that came out of the floor, the plugs and sockets, the two rows of junction boxes and the labels that belonged to them. The tall pipe with the valve stays: it runs from the old floor to the ceiling and now simply passes through the floor; * the two arrows painted on the floor come up with it, and so does the marker "Sonar table". Everything is done on the objects of the open file, in place (apply()). Nothing here is a rebuild. (The pack's own level cars carry a glass floor between two open trenches along the walls; this corridor keeps its white floor instead, as the user's arrows ask, and the black strips close the floor to the walls.) """ import re import bpy from mathutils import Matrix, Vector COLL = "10.5 | Command deck" LETTERING = "10.5 | Command deck | lettering" MARKERS = "10.8 | Markers (game hooks)" ROOT = "M_SHIP_INTERIOR_S2" X0, X1 = 15.5, 30.7 # the corridor between its two end door planes HALF = 2.5 # its inside lies within this distance of the centre line SILL = 3.14 # door sill level = the command deck RISE = 0.9 FLOOR = ("EN_SM_Floor_A1", "EN_SM_Floor_A2", "EN_SM_Floor_A_Pipes", "EN_SM_GlassFloor_Side_A") STAIRS = ("EN_SM_Stairs_A", "EN_SM_Floor_A_End") # on the wall's foot, below the sills (taken out where they end up under the floor or cut by it) FOOT_PROPS = ("EN_SM_Wall_Pipe_A", "EP_SM_CoatedPipe_A", "EP_SM_IndustrialPlug_A", "EP_SM_WallPipes_Small_A", "EN_SM_ElectricDistribBox", "EP_SM_Socket_A") SILL_PLATE = "EN_SM_GlassFloor_Door_B" SIDE_DOOR_X = 24.86 # the two side doors' axis CAR_HALF = 2.6 # their door planes STAYS = ("EN_SM_Wall_HB", "EN_SM_Doorway", "EN_SM_Door_", "EN_SM_PipeValve_A", "EN_SM_GlassFloor_Door", "SI_SM_Wall_Puffy") def kind(ob): """Which piece of the pack a mesh object shows: its mesh's name without a copy suffix. (The file holds meshes the user pasted, 'EN_SM_Stairs_A.001' among them, with the same Unreal path as the pack's own; a rebuild's kit adopts whichever it meets last.)""" return re.sub(r"\.\d{3}$", "", ob.data.name) def bounds(ob): mw = ob.matrix_world cs = [mw @ Vector(c) for c in ob.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 in_corridor(ob): lo, hi = bounds(ob) c = (lo + hi) / 2 return X0 < c.x < X1 and abs(c.y) < HALF def corridor_meshes(): out = [] for name in (COLL, LETTERING): col = bpy.data.collections.get(name) if col is None: continue out += [ob for ob in col.objects if ob.type == "MESH" and in_corridor(ob)] return out def state(): """What the corridor's floor is like now (for the check before a change and the report after it).""" obs = corridor_meshes() floor = [ob for ob in obs if kind(ob) in FLOOR] tops = sorted({round(bounds(ob)[1].z, 3) for ob in floor if kind(ob) == "EN_SM_Floor_A1"}) plates = [ob for ob in bpy.data.collections[COLL].objects if ob.type == "MESH" and kind(ob) == SILL_PLATE] return {"floor_pieces": len(floor), "white_floor_top_z": tops, "stairs": sum(1 for ob in obs if kind(ob) == "EN_SM_Stairs_A"), "risers": sum(1 for ob in obs if kind(ob) == "EN_SM_Floor_A_End"), "sill_plates": len(plates), "level": bool(tops) and all(abs(t - SILL) < 0.002 for t in tops)} def _move_up(ob, dz): m = ob.matrix_world.copy() m.translation.z += dz ob.matrix_world = m def apply(): """Level the corridor. Returns a report; does nothing if the floor is already at sill level.""" before = state() if before["level"]: return {"done": False, "why": "the floor is already at sill level", "state": before} if before["white_floor_top_z"] != [round(SILL - RISE, 3)]: raise RuntimeError("the corridor's floor is not where it was built (%s): look before changing it" % before["white_floor_top_z"]) obs = corridor_meshes() rep = {"done": True, "before": before, "raised": {}, "removed": {}, "decals_raised": [], "decals_removed": []} # 1. the stairs and their risers for ob in [o for o in obs if kind(o) in STAIRS]: rep["removed"][kind(ob)] = rep["removed"].get(kind(ob), 0) + 1 bpy.data.objects.remove(ob) # 2. what stood on the wall's foot and would now be under the floor or cut by it for ob in [o for o in corridor_meshes() if kind(o) in FOOT_PROPS]: if bounds(ob)[0].z < SILL - 0.005: rep["removed"][kind(ob)] = rep["removed"].get(kind(ob), 0) + 1 bpy.data.objects.remove(ob) # 3. the floor itself for ob in [o for o in corridor_meshes() if kind(o) in FLOOR]: _move_up(ob, RISE) rep["raised"][kind(ob)] = rep["raised"].get(kind(ob), 0) + 1 # 4. lettering: what lies on the floor comes up with it; labels on the wall's foot go with their equipment for ob in [o for o in corridor_meshes() if kind(o).startswith("DECAL")]: lo, hi = bounds(ob) if lo.z >= SILL - 0.005 + 0.08: continue mat = ob.material_slots[0].material.name if ob.material_slots and ob.material_slots[0].material else "" if hi.z - lo.z < 0.01 and hi.z < SILL - RISE + 0.1: _move_up(ob, RISE) rep["decals_raised"].append(mat) elif lo.z < SILL + 0.075 and abs((lo.y + hi.y) / 2) > 1.9 and X0 + 0.5 < (lo.x + hi.x) / 2 < X1 - 0.5: rep["decals_removed"].append(mat) bpy.data.objects.remove(ob) # 5. sill plates in the two side doors (copies of an end door's plate, turned to the side door's plane) col = bpy.data.collections[COLL] root = bpy.data.objects.get(ROOT) src = next(ob for ob in col.objects if ob.type == "MESH" and kind(ob) == SILL_PLATE) slo, shi = bounds(src) src_plane = Vector(((slo.x + shi.x) / 2, 0.0, 0.0)) made = [] for s, tag in ((1.0, "port"), (-1.0, "starboard")): m = (Matrix.Translation((SIDE_DOOR_X, s * CAR_HALF, 0.0)) @ Matrix.Rotation(s * 1.5707963267948966, 4, "Z") @ Matrix.Translation(-src_plane) @ src.matrix_world) ob = bpy.data.objects.new("Sill | side door, %s" % tag, src.data) col.objects.link(ob) if root is not None: ob.parent = root ob.matrix_world = m for k in ("ue_mesh", "zone"): if k in src: ob[k] = src[k] for i, sl in enumerate(src.material_slots): if sl.link == "OBJECT": ob.material_slots[i].link = "OBJECT" ob.material_slots[i].material = sl.material made.append(ob.name) rep["sill_plates_added"] = made # 6. the marker that stood on the old floor mk = bpy.data.objects.get("MARKER | Sonar table") if mk is not None and abs(mk.matrix_world.translation.z - (SILL - RISE)) < 0.05: _move_up(mk, RISE) rep["marker_raised"] = mk.name bpy.context.view_layer.update() rep["after"] = state() rep["still_below_the_floor"] = below_the_floor() return rep def below_the_floor(): """Mesh objects in the corridor that reach under the floor's top and are not meant to (should be empty).""" out = [] for ob in corridor_meshes(): n = kind(ob) if n in FLOOR or n.startswith(STAYS) or n.startswith("Bulkhead"): continue lo, hi = bounds(ob) if lo.z < SILL - 0.006: out.append([ob.name, n, round(lo.z, 3), round(hi.z, 3)]) return out