# vr_decals.py - stencils, slot cut-outs, stickers and latch plates (textures/T_VR_Decals.png, made by # vr_decal_atlas.py). Each decal is a quad cast onto its plate by a ray, 0.8 mm off the surface, # skinned to the plate's bone. Right-side decals are cast separately (mirrored ray), so no text reads # mirrored. Placement follows the references (3.jpg chest and arms, 1.jpg and 2.jpg legs); the maker # name and logos there are replaced by generic codes. # Run after vr_build.build(): dc = {}; exec(..., dc); dc["place_all"](ns) (ns = vr_build's namespace) import bpy, bmesh, json from mathutils import Vector as V ROOT = r"D:\HADAL - The Descent\models\veiled-robot" ATLAS = ROOT + r"\textures\T_VR_Decals.png" def decal_mat(): m = bpy.data.materials.get("M_VR_Decal") or bpy.data.materials.new("M_VR_Decal") m.use_nodes = True; nt = m.node_tree; N = nt.nodes; L = nt.links.new for n in list(N): N.remove(n) out = N.new("ShaderNodeOutputMaterial"); bs = N.new("ShaderNodeBsdfPrincipled") tx = N.new("ShaderNodeTexImage"); tx.image = bpy.data.images.load(ATLAS, check_existing=True) tx.image.reload(); tx.image.colorspace_settings.name = "sRGB"; tx.interpolation = 'Cubic' tc = N.new("ShaderNodeTexCoord") noi = N.new("ShaderNodeTexNoise"); noi.inputs["Scale"].default_value = 160; noi.inputs["Detail"].default_value = 10 L(tc.outputs["Object"], noi.inputs["Vector"]) wear = N.new("ShaderNodeMapRange") # paint rubbed off in small flecks wear.inputs["From Min"].default_value = 0.30; wear.inputs["From Max"].default_value = 0.40 L(noi.outputs["Fac"], wear.inputs["Value"]) a = N.new("ShaderNodeMath"); a.operation = 'MULTIPLY' L(tx.outputs["Alpha"], a.inputs[0]); L(wear.outputs["Result"], a.inputs[1]) tint = N.new("ShaderNodeMix"); tint.data_type = 'RGBA'; tint.inputs["Factor"].default_value = 1.0 tint.blend_type = 'MULTIPLY'; tint.inputs["B"].default_value = (0.75, 0.76, 0.73, 1) L(tx.outputs["Color"], tint.inputs["A"]); L(tint.outputs["Result"], bs.inputs["Base Color"]) bs.inputs["Roughness"].default_value = 0.55 L(a.outputs["Value"], bs.inputs["Alpha"]) L(bs.outputs["BSDF"], out.inputs["Surface"]) m.diffuse_color = (0.8, 0.8, 0.8, 1) return m def cast(part, origin, direction, up, region, width, name, coll): ob = bpy.data.objects.get(part) if ob is None: return None ok, loc, nrm, _ = ob.ray_cast(V(origin), V(direction).normalized()) if not ok: return None n = nrm.normalized(); u = V(up); u = (u - n * u.dot(n)).normalized(); r = u.cross(n) meta = json.load(open(ROOT + r"\textures\T_VR_Decals.json")) S = meta["size"]; x0, y0, x1, y1 = meta["regions"][region] h = width * (y1 - y0) / (x1 - x0) c = loc + n * 0.0008 pts = [c - r * width / 2 - u * h / 2, c + r * width / 2 - u * h / 2, c + r * width / 2 + u * h / 2, c - r * width / 2 + u * h / 2] uvs = [(x0 / S, 1 - y1 / S), (x1 / S, 1 - y1 / S), (x1 / S, 1 - y0 / S), (x0 / S, 1 - y0 / S)] old = bpy.data.objects.get(name) if old: me_o = old.data; bpy.data.objects.remove(old); bpy.data.meshes.remove(me_o) me = bpy.data.meshes.new(name); bm = bmesh.new() f = bm.faces.new([bm.verts.new(p) for p in pts]) uvl = bm.loops.layers.uv.new("UVMap") for lp, uv in zip(f.loops, uvs): lp[uvl].uv = uv bm.to_mesh(me); bm.free() me.materials.append(bpy.data.materials["M_VR_Decal"]) o = bpy.data.objects.new(name, me); coll.objects.link(o) bone = ob["vr_bone"] vg = o.vertex_groups.new(name=bone); vg.add(range(4), 1.0, 'REPLACE') rig = bpy.data.objects["VR_Rig"]; o.parent = rig md = o.modifiers.new("Armature", 'ARMATURE'); md.object = rig o.visible_shadow = False o["vr_decal"] = bone return o def place_all(ns): decal_mat() top = bpy.data.collections["VeiledRobot"] coll = bpy.data.collections.get("VR | Decals") or bpy.data.collections.new("VR | Decals") if coll.name not in [c.name for c in top.children]: top.children.link(coll) for o in list(coll.objects): bpy.data.objects.remove(o) F = {k: ns[k] for k in ("F_UA", "F_LA", "F_TH", "F_CA")} Z = (0, 0, 1) jobs = [ # part, origin, direction, up, region, width (centre line, world) ("VR_ChestPlate", (0, -0.5, 1.50), (0, 1, 0), Z, "unit", 0.11), # 5.jpg: text 1.50, badges 1.435 ("VR_ChestPlate", (0.087, -0.5, 1.435), (0, 1, 0), Z, "badge", 0.026), ("VR_ChestPlate", (-0.087, -0.5, 1.435), (0, 1, 0), Z, "badge", 0.026), ("VR_ChestPlate", (-0.027, -0.5, 1.413), (0, 1, 0), Z, "slotpair", 0.036), # 3.jpg: slots in two pairs ("VR_ChestPlate", (0.027, -0.5, 1.413), (0, 1, 0), Z, "slotpair", 0.036), ("VR_ChestPlate", (0.14, -0.5, 1.52), (0, 1, 0), Z, "slot", 0.018), ("VR_ChestPlate", (-0.14, -0.5, 1.52), (0, 1, 0), Z, "slot", 0.018), ("VR_ChestPlate", (0.06, -0.5, 1.375), (0, 1, 0), Z, "slot", 0.016), ("VR_ChestPlate", (-0.06, -0.5, 1.375), (0, 1, 0), Z, "slot", 0.016), ("VR_Abdomen", (0, -0.5, 1.262), (0, 1, 0), Z, "emblem", 0.042), ("VR_Abdomen", (0, -0.5, 1.222), (0, 1, 0), Z, "code", 0.058), ("VR_BackUnit", (0, 0.5, 1.49), (0, -1, 0), Z, "frame07", 0.075), ("VR_BackUnit", (0, 0.5, 1.41), (0, -1, 0), Z, "slotpair", 0.06), ("VR_PelvisPlate", (0, -0.5, 0.99), (0, 1, 0), Z, "code", 0.05), ("VR_Head", (-0.5, 0.05, 1.80), (1, 0, 0), Z, "code", 0.05), ("VR_Head", (0, 0.5, 1.80), (0, -1, 0), Z, "slotpair", 0.04), ("VR_Abdomen", (0.065, -0.5, 1.295), (0, 1, 0), Z, "bolt", 0.008), ("VR_Abdomen", (-0.065, -0.5, 1.295), (0, 1, 0), Z, "bolt", 0.008), ("VR_Abdomen", (0.065, -0.5, 1.195), (0, 1, 0), Z, "bolt", 0.008), ("VR_Abdomen", (-0.065, -0.5, 1.195), (0, 1, 0), Z, "bolt", 0.008), ("VR_ChestPlate", (0, -0.5, 1.537), (0, 1, 0), Z, "seamshort", 0.16), ("VR_PelvisPlate", (0, -0.5, 1.035), (0, 1, 0), Z, "slotpair", 0.04), ] L = lambda f, p: tuple(F[f] @ V(p)) D = lambda f, d: tuple(F[f].to_3x3() @ V(d)) side = [ # left side; mirrored below ("VR_Trapezius_l", (0.167, -0.5, 1.605), (0, 1, 0), Z, "slot", 0.02), ("VR_ShoulderCap_l", L("F_UA", (0.025, 0.5, 0.01)), D("F_UA", (0, -1, 0)), D("F_UA", (0, 0, -1)), "sticker", 0.04), ("VR_ShoulderCap_l", L("F_UA", (0.5, 0.0, 0.03)), D("F_UA", (-1, 0, 0)), D("F_UA", (0, 1, 0)), "slot", 0.022), ("VR_UpperArm_l", L("F_UA", (0.5, 0.0, 0.19)), D("F_UA", (-1, 0, 0)), D("F_UA", (0, 1, 0)), "unit", 0.11), ("VR_UpperArm_l", L("F_UA", (0.0, 0.5, 0.14)), D("F_UA", (0, -1, 0)), D("F_UA", (0, 0, -1)), "slotpair", 0.045), ("VR_UpperArm_l", L("F_UA", (0.0, -0.5, 0.18)), D("F_UA", (0, 1, 0)), D("F_UA", (0, 0, -1)), "slotpair", 0.045), ("VR_Forearm_l", L("F_LA", (0.5, 0.0, 0.22)), D("F_LA", (-1, 0, 0)), D("F_LA", (0, 1, 0)), "code", 0.065), ("VR_Forearm_l", L("F_LA", (0.0, 0.5, 0.215)), D("F_LA", (0, -1, 0)), D("F_LA", (0, 0, -1)), "slotpair", 0.045), ("VR_Forearm_l", L("F_LA", (-0.5, 0.0, 0.18)), D("F_LA", (1, 0, 0)), D("F_LA", (0, 1, 0)), "latch", 0.03), ("VR_Thigh_l", L("F_TH", (0.0, 0.5, 0.17)), D("F_TH", (0, -1, 0)), D("F_TH", (0, 0, -1)), "slotpair", 0.05), ("VR_Thigh_l", L("F_TH", (0.5, 0.0, 0.02)), D("F_TH", (-1, 0, 0)), D("F_TH", (0, 0, -1)), "frame07", 0.05), ("VR_Thigh_l", L("F_TH", (0.5, 0.0, 0.22)), D("F_TH", (-1, 0, 0)), D("F_TH", (0, 0, -1)), "slot", 0.02), ("VR_Shin_l", L("F_CA", (0.0, 0.5, 0.26)), D("F_CA", (0, -1, 0)), D("F_CA", (0, 0, -1)), "slot", 0.02), ("VR_Shin_l", L("F_CA", (0.5, 0.0, 0.17)), D("F_CA", (-1, 0, 0)), D("F_CA", (0, 0, -1)), "code", 0.055), ("VR_Shin_l", L("F_CA", (0.0, 0.5, 0.02)), D("F_CA", (0, -1, 0)), D("F_CA", (0, 0, -1)), "slotpair", 0.04), ("VR_Foot_l", (0.8, 0.02, 0.05), (-1, 0, 0), Z, "slot", 0.02), # second pass after the close-up comparison (3.jpg, 1.jpg, 2.jpg): cut-outs, bolts, seams ("VR_Forearm_l", L("F_LA", (0.5, 0.0, 0.10)), D("F_LA", (-1, 0, 0)), D("F_LA", (0, 1, 0)), "tris", 0.06), ("VR_Forearm_l", L("F_LA", (0.0, 0.5, 0.12)), D("F_LA", (0, -1, 0)), D("F_LA", (0, 0, -1)), "latch", 0.028), ("VR_Forearm_l", L("F_LA", (0.0, -0.5, 0.2)), D("F_LA", (0, 1, 0)), D("F_LA", (0, 0, -1)), "slot", 0.02), ("VR_UpperArm_l", L("F_UA", (0.0, 0.5, 0.235)), D("F_UA", (0, -1, 0)), D("F_UA", (0, 0, -1)), "latch", 0.03), ("VR_UpperArm_l", L("F_UA", (0.5, 0.03, 0.27)), D("F_UA", (-1, 0, 0)), D("F_UA", (0, 1, 0)), "slot", 0.02), ("VR_ShoulderCap_l", L("F_UA", (0.03, -0.5, 0.0)), D("F_UA", (0, 1, 0)), D("F_UA", (0, 0, -1)), "slotpair", 0.035), ("VR_ShoulderCap_l", L("F_UA", (0.065, 0.5, 0.04)), D("F_UA", (0, -1, 0)), D("F_UA", (0, 0, -1)), "bolt", 0.009), ("VR_Thigh_l", L("F_TH", (0.0, 0.5, 0.26)), D("F_TH", (0, -1, 0)), D("F_TH", (0, 0, -1)), "seamshort", 0.07), ("VR_Thigh_l", L("F_TH", (0.035, 0.5, -0.03)), D("F_TH", (0, -1, 0)), D("F_TH", (0, 0, -1)), "bolt", 0.01), ("VR_Thigh_l", L("F_TH", (-0.035, 0.5, -0.03)), D("F_TH", (0, -1, 0)), D("F_TH", (0, 0, -1)), "bolt", 0.01), ("VR_Thigh_l", L("F_TH", (0.0, -0.5, 0.1)), D("F_TH", (0, 1, 0)), D("F_TH", (0, 0, -1)), "slotpair", 0.05), ("VR_Shin_l", L("F_CA", (0.5, 0.0, 0.29)), D("F_CA", (-1, 0, 0)), D("F_CA", (0, 1, 0)), "seamshort", 0.08), ("VR_Shin_l", L("F_CA", (0.0, 0.5, 0.33)), D("F_CA", (0, -1, 0)), D("F_CA", (0, 0, -1)), "bolt", 0.009), ("VR_Shin_l", L("F_CA", (0.0, -0.5, 0.16)), D("F_CA", (0, 1, 0)), D("F_CA", (0, 0, -1)), "slotpair", 0.045), ("VR_Trapezius_l", (0.167, 0.5, 1.6), (0, -1, 0), Z, "slot", 0.018), ("VR_Foot_l", (0.25, -0.5, 0.06), (0, 1, 0), Z, "bolt", 0.009), ] mir = lambda v: V((-v[0], v[1], v[2])) for p, o, d, u, reg, w in side: jobs.append((p, o, d, u, reg, w)) jobs.append((p[:-2] + "_r", mir(o), mir(d), mir(u), reg, w)) made, missed = 0, [] for i, (p, o, d, u, reg, w) in enumerate(jobs): ob = cast(p, o, d, u, reg, w, f"VR_Decal_{i:02d}_{p[3:]}", coll) if ob: made += 1 else: missed.append((p, reg)) return {"decals": made, "missed": missed}