refs: survey the sibling project and recalibrate the hero facade
Tools/sibling_asset_audit.py reads the sibling security-game project's prop library straight from the GLB headers: 20 street props, 11.8k triangles, every one with a Blender source. The 50 .blend sources are copied to Art/props/blender_src/ with a manifest; nothing is imported into Unreal yet. Its building meshes are deliberately not taken - they are Godot GLBs built around a 29.6 m facade that this project measured at 25.75 m. The facade proportions were wrong against the photograph: 5.7 m of blank wall above the capitals. Entablature is now 3.4 m, parapet 2.2 m, with architrave and cornice bands and square pedestals, so the elevation reads as layered stone.
This commit is contained in:
1 parent
52a736bc9f
commit
5e5e5ae9b4
67 files changed
+591
-27
No files matched your search
@@ -0,0 +1,107 @@
|
||||
# Audit the sibling Gig Line project for assets this project can reuse.
|
||||
#
|
||||
# python3 Tools/sibling_asset_audit.py # summary
|
||||
# python3 Tools/sibling_asset_audit.py --manifest # write Art/props/reuse-manifest.json
|
||||
#
|
||||
# The sibling project (…/06-games/security-game) is a Godot 4 security game by
|
||||
# the same author that researched this block first. Its street props are small,
|
||||
# generic and far cheaper to reuse than to remodel; its building meshes are not
|
||||
# reusable because they are Godot GLBs built to a different contract.
|
||||
#
|
||||
# What this tool does NOT do: copy anything, decide licence, or assume a prop
|
||||
# matches this project's proportions. It reports name, size, format and whether
|
||||
# a Blender source exists, so the copy list can be chosen deliberately.
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
|
||||
SIBLING = "/run/media/system/DATA/Documents/06-games/security-game"
|
||||
PROPS_GLB = os.path.join(SIBLING, "assets/models/blender/props")
|
||||
SRC_BLEND = os.path.join(SIBLING, "assets/models/blender_src")
|
||||
OUT = "Art/props/reuse-manifest.json"
|
||||
|
||||
|
||||
def glb_stats(path):
|
||||
"""Triangle count and bounds from a GLB, without any dependency."""
|
||||
with open(path, "rb") as f:
|
||||
data = f.read()
|
||||
if data[:4] != b"glTF":
|
||||
return None
|
||||
json_len = struct.unpack("<I", data[12:16])[0]
|
||||
doc = json.loads(data[20 : 20 + json_len].decode("utf-8"))
|
||||
tris = 0
|
||||
for mesh in doc.get("meshes", []):
|
||||
for prim in mesh.get("primitives", []):
|
||||
idx = prim.get("indices")
|
||||
if idx is not None:
|
||||
tris += doc["accessors"][idx]["count"] // 3
|
||||
# bounds across all positions
|
||||
mins = [1e9] * 3
|
||||
maxs = [-1e9] * 3
|
||||
for mesh in doc.get("meshes", []):
|
||||
for prim in mesh.get("primitives", []):
|
||||
acc = prim.get("attributes", {}).get("POSITION")
|
||||
if acc is None:
|
||||
continue
|
||||
a = doc["accessors"][acc]
|
||||
if "min" in a and "max" in a:
|
||||
mins = [min(m, v) for m, v in zip(mins, a["min"])]
|
||||
maxs = [max(m, v) for m, v in zip(maxs, a["max"])]
|
||||
size = [round(maxs[i] - mins[i], 3) for i in range(3)] if mins[0] < 1e8 else None
|
||||
return {"triangles": tris, "size_gltf": size, "materials": len(doc.get("materials", []))}
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("--manifest", action="store_true")
|
||||
args = ap.parse_args()
|
||||
if not os.path.isdir(PROPS_GLB):
|
||||
sys.exit(f"missing {PROPS_GLB}")
|
||||
|
||||
rows = []
|
||||
for name in sorted(os.listdir(PROPS_GLB)):
|
||||
if not name.endswith(".glb"):
|
||||
continue
|
||||
stem = name[:-4]
|
||||
path = os.path.join(PROPS_GLB, name)
|
||||
stats = glb_stats(path) or {}
|
||||
source = os.path.join(SRC_BLEND, stem + ".blend")
|
||||
rows.append({
|
||||
"prop": stem,
|
||||
"glb": name,
|
||||
"bytes": os.path.getsize(path),
|
||||
"blend_source": os.path.basename(source) if os.path.exists(source) else None,
|
||||
"blend_bytes": os.path.getsize(source) if os.path.exists(source) else None,
|
||||
**stats,
|
||||
})
|
||||
|
||||
for r in rows:
|
||||
src = "blend" if r["blend_source"] else "glb-only"
|
||||
size = r.get("size_gltf")
|
||||
dims = "x".join(f"{v:.2f}" for v in size) if size else "?"
|
||||
print(f"{r['prop']:18} {r['triangles']:5} tris {dims:>18} glTF m "
|
||||
f"mats {r.get('materials', 0)} {src}")
|
||||
|
||||
print(f"\n{len(rows)} props, {sum(r['bytes'] for r in rows)//1024} KB of GLB, "
|
||||
f"{sum(r['triangles'] for r in rows)} triangles total")
|
||||
print("glTF is Y-up and Blender is Z-up: the exporter swaps Y/Z, so `size_gltf` "
|
||||
"lists X, height, depth — not X, Y, Z as authored.")
|
||||
|
||||
if args.manifest:
|
||||
os.makedirs(os.path.dirname(OUT), exist_ok=True)
|
||||
with open(OUT, "w") as f:
|
||||
json.dump({
|
||||
"source_project": SIBLING,
|
||||
"scanned": "assets/models/blender/props + assets/models/blender_src",
|
||||
"licence": "project-owned (same author); confirm before shipping",
|
||||
"note": "audit only — nothing has been copied",
|
||||
"props": rows,
|
||||
}, f, indent=2)
|
||||
print(f"wrote {OUT}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user