"""Spearfish: a Tender ("sub tub") built after the user's orthographic blueprint (reference/user_blueprint.webp), 2026-10-20: "Create this blueprint as subtub tender 3d model in blender using MCP precisely accordingly take your time check as the referance image". "Spearfish" is a working name after the long pointed nose; the user has not named it. All measurements and the choices where the blueprint's views disagree are in sf_measure.py. Axes as the Kazah and the Manta: 1 unit = 1 m, nose to -Y, port +X, Z up, origin 9 cm behind the docking clamp's axis. No booleans. The hull is one quad grid whose columns are pinned to the canopy, side window and hatch outlines, so each opening is a rectangle of the grid with its frame and sleeve in the same topology (sf_hull.py); fins, rails and fittings are lofted or revolved quads with quad caps (sf_topo.py), pruned where they sink into the hull. Runs inside the open Blender (exec through the MCP) or headless; it rebuilds only its own collections. """ import bpy, bmesh, math, os, sys, json, time from mathutils import Vector, Matrix HERE = r"D:\HADAL - The Descent\models\tender-spearfish\build" exec(open(os.path.join(HERE, "lib2.py")).read()) exec(open(os.path.join(HERE, "tender_lib.py")).read()) exec(open(os.path.join(HERE, "sf_measure.py")).read()) OUT = os.path.dirname(HERE) t_start = time.time() REPORT = {} PFX = "SPEARFISH | " for cname in (PFX + "Tender (exported)", PFX + "Collision (UCX)", PFX + "Sockets"): c = bpy.data.collections.get(cname) if c: for o in list(c.all_objects): bpy.data.objects.remove(o) bpy.data.collections.remove(c) for me in list(bpy.data.meshes): if me.users == 0: bpy.data.meshes.remove(me) # ================================================================== materials (look only; the surfaces pass dresses them) PAINT = MAT(PFX + "Hull paint", (0.66, 0.70, 0.74), rough=0.28, coat=1.0) SEAM = MAT(PFX + "Panel seam", (0.06, 0.07, 0.08), rough=0.55) GRAPH = MAT(PFX + "Graphite", (0.030, 0.034, 0.040), rough=0.42) SENSOR = MAT(PFX + "Sensor glass", (0.006, 0.008, 0.012), rough=0.05, coat=1.0) STEEL = MAT(PFX + "Steel", (0.60, 0.61, 0.63), metal=1.0, rough=0.28) CHROME = MAT(PFX + "Chrome", (0.86, 0.87, 0.89), metal=1.0, rough=0.10) RUBBER = MAT(PFX + "Rubber", (0.017, 0.017, 0.018), rough=0.80) YELLOW = MAT(PFX + "Yellow marking", (0.85, 0.55, 0.04), rough=0.40, coat=0.5) GLASS = MAT(PFX + "Glass", (0.20, 0.27, 0.34), rough=0.03) LENS = MAT(PFX + "Lamp lens", (0.95, 0.97, 1.0), rough=0.03) LAMP = MAT(PFX + "Lamp light", (0.9, 0.9, 0.9), rough=0.3, emit=(1.0, 0.90, 0.75), emit_str=25.0) CAB = MAT(PFX + "Cabin panel", (0.10, 0.11, 0.12), rough=0.6) FLOOR = MAT(PFX + "Cabin floor", (0.05, 0.05, 0.055), rough=0.65) SEAT = MAT(PFX + "Seat", (0.05, 0.035, 0.026), rough=0.55) SCREEN = MAT(PFX + "Screen", (0.01, 0.02, 0.025), rough=0.15, emit=(0.35, 0.75, 1.0), emit_str=3.0) CABGLOW = MAT(PFX + "Cabin light", (0.05, 0.06, 0.07), rough=0.4, emit=(0.70, 0.85, 1.0), emit_str=12.0) for m in (GLASS, LENS): b = m.node_tree.nodes["Principled BSDF"] b.inputs["Transmission Weight"].default_value = 1.0 b.inputs["IOR"].default_value = 1.49 C_ROOT = get_coll(PFX + "Tender (exported)") C_COL = get_coll(PFX + "Collision (UCX)") C_SOCK = get_coll(PFX + "Sockets") root = bpy.data.objects.new("Spearfish_Root", None) C_ROOT.objects.link(root) root.empty_display_type = 'ARROWS' # ================================================================== 1. the hull's shape (sf_shape.py) exec(open(os.path.join(HERE, "sf_shape.py")).read()) # ================================================================== 2-3. hull, openings, glass, lining, cabin exec(open(os.path.join(HERE, "sf_hull.py")).read()) for f in ("sf_fittings.py", "sf_finish.py"): fp = os.path.join(HERE, f) if os.path.exists(fp): exec(open(fp).read())