"""In-place update of a scene that holds an APPENDED copy of the unrigged carousel (for example the vessel interior): swaps in the four part meshes that gained hose brackets and adds the ore hose with its rig to each arm. Nothing else in the scene is touched; nothing is rebuilt, reloaded or saved here. In the open Blender (Python): exec(open(r"D:\\SubnauticaCLONE\\models\\docking-arm\\build\\scene_add_hose.py").read()); result = add_ore_hose() """ import bpy, os, json, re, math from mathutils import Matrix, Vector _DA =r"D:\SubnauticaCLONE\models\docking-arm" exec(open(os.path.join(_DA, "build", "hose_rig_setup.py")).read()) _SWAP = ["SM_DockArm_Turret", "SM_DockArm_UpperLink", "SM_DockArm_LowerLink", "SM_DockArm_Head"] def _base(name): return re.sub(r"\.\d{3}$", "", name) def remove_ore_hose(): """Takes the hose objects, their mesh and rig data out again (the part meshes stay as they are).""" gone = [] for o in [o for o in bpy.data.objects if re.fullmatch(r"Arm[123]_(OreHose|HoseRig)", o.name)]: gone.append(o.name) bpy.data.objects.remove(o) for coll, n in ((bpy.data.meshes, "SM_DockArm_OreHose"), (bpy.data.armatures, "DockArm_HoseRig"), (bpy.data.materials, "M_DockArm_OreHose"), (bpy.data.images, "T_DockArm_HoseWrinkle_Normal"), (bpy.data.images, "T_DockArm_HoseFold_Mask")): d = coll.get(n) if d is not None and d.users == 0: coll.remove(d) return gone def add_ore_hose(src=os.path.join(_DA, "DockArm_Carousel.blend"), replace=False): if "SM_DockArm_OreHose" in bpy.data.meshes: if not replace: return {"error": "this scene already has the ore hose (pass replace=True to put the current one in)"} remove_ore_hose() need = ["DockArm_Root"] + ["Arm%d%s" % (k, s) for k in (1, 2, 3) for s in ("", "_UpperLink", "_LowerLink", "_Head")] missing = [n for n in need if n not in bpy.data.objects] if missing: return {"error": "carousel objects not found", "missing": missing} root = bpy.data.objects["DockArm_Root"] coll = bpy.data.objects["Arm1"].users_collection[0] old = {n: bpy.data.meshes[n] for n in _SWAP} mats_before = set(bpy.data.materials) imgs_before = set(bpy.data.images) by_name_m = {m.name: m for m in bpy.data.materials} by_name_i = {i.name: i for i in bpy.data.images} with bpy.data.libraries.load(src, link=False) as (f, t): t.meshes = _SWAP + ["SM_DockArm_OreHose"] t.armatures = ["DockArm_HoseRig"] new = dict(zip(_SWAP + ["SM_DockArm_OreHose"], t.meshes)) rigdata = t.armatures[0] log = {"materials_reused": [], "materials_added": [], "images_reused": [], "images_added": []} # the scene keeps its own copies of what it already had (no .001 twins) for m in [m for m in bpy.data.materials if m not in mats_before]: twin = by_name_m.get(_base(m.name)) if twin is not None and twin is not m: m.user_remap(twin) log["materials_reused"].append(twin.name) bpy.data.materials.remove(m) else: log["materials_added"].append(m.name) for i in [i for i in bpy.data.images if i not in imgs_before]: twin = by_name_i.get(_base(i.name)) if twin is not None and twin is not i: i.user_remap(twin) log["images_reused"].append(twin.name) bpy.data.images.remove(i) else: log["images_added"].append(i.name) # the four parts that gained brackets: same pivots, so only the mesh data changes log["meshes_swapped"] = {} for n in _SWAP: users = old[n].users old[n].user_remap(new[n]) old[n].name = n + "__before_hose" new[n].name = n log["meshes_swapped"][n] = users bpy.data.meshes.remove(old[n]) new["SM_DockArm_OreHose"].name = "SM_DockArm_OreHose" rigdata.name = "DockArm_HoseRig" made = [] for k in (1, 2, 3): o = bpy.data.objects rig, hose = setup_hose_rig(o["Arm%d" % k], o["Arm%d_UpperLink" % k], o["Arm%d_LowerLink" % k], o["Arm%d_Head" % k], rigdata, new["SM_DockArm_OreHose"], coll, "Arm%d" % k) made += [rig.name, hose.name] log["objects_added"] = made log["collection"] = coll.name bpy.context.view_layer.update() return log def check_ore_hose(): """Compares arm 1's hose with the positions the model file gave for the same pose (when the pose is stowed or BOARD), and the two other arms with arm 1's stowed reference turned 120 and 240 degrees.""" ref = json.load(open(os.path.join(_DA, "build", "hose_ref.json"))) root = bpy.data.objects["DockArm_Root"] turret = bpy.data.objects["SM_DockArm_Turret"] for k in (1, 2, 3): for n in ("Arm%d", "Arm%d_UpperLink", "Arm%d_LowerLink", "Arm%d_Head", "Arm%d_HoseRig", "Arm%d_OreHose"): bpy.data.objects[n % k].update_tag() bpy.context.view_layer.update() dg = bpy.context.evaluated_depsgraph_get() dg.update() out = {} for k in (1, 2, 3): low = float(bpy.data.objects["Arm%d" % k]["lower_m"]) key = "%.1f" % low if key not in ref: out["Arm%d" % k] = "pose %.2f m has no reference" % low continue e = bpy.data.objects["Arm%d_OreHose" % k].evaluated_get(dg) M = e.matrix_world.copy() # reference is arm 1 with the turret at 0, in the root frame R = root.matrix_world @ turret.matrix_basis @ Matrix.Rotation(math.radians(-120.0 * (k - 1)), 4, 'Z') worst = 0.0 for i, p in enumerate(ref[key]): got = M @ e.data.vertices[i * 12].co worst = max(worst, (got - (R @ Vector(p))).length) out["Arm%d" % k] = {"pose_m": low, "rings": len(ref[key]), "max_deviation_mm": round(worst * 1000, 3)} return out