aboutsummaryrefslogtreecommitdiffstats
path: root/generat3d/scene.py
diff options
context:
space:
mode:
Diffstat (limited to 'generat3d/scene.py')
-rw-r--r--generat3d/scene.py45
1 files changed, 36 insertions, 9 deletions
diff --git a/generat3d/scene.py b/generat3d/scene.py
index 49cc4c9..08ad4ee 100644
--- a/generat3d/scene.py
+++ b/generat3d/scene.py
@@ -77,12 +77,20 @@ def _lookup(library, doc, url, section):
raise Error(f"{url}: no {section} entry {ident!r}")
+def _number(url, v):
+ if isinstance(v, bool) or not isinstance(v, (int, float)):
+ raise Error(f"{url}: dial value is not a number")
+ return v
+
+
class _Loader:
def __init__(self, path, library):
self.path = Path(path)
self.library = library
self.doc = read_dson(self.path)
self.graph = Graph()
+ self.loaded = set()
+ self.hd = 0
def run(self):
nodes = self.doc.get("scene", {}).get("nodes", [])
@@ -97,9 +105,9 @@ class _Loader:
posed = posed or any(v for ch in ("rotation", "translation")
for v in _channel(n, ch))
elif kind != "figure":
- warn(f"skipped scene node {n.get('name', n['id'])!r} ({kind or 'no type'})")
+ warn(f"skipped scene node {n.get('name') or n.get('id')!r} ({kind or 'no type'})")
elif "conform_target" in n:
- warn(f"skipped follower {n.get('name', n['id'])!r} (not handled in v1)")
+ warn(f"skipped follower {n.get('name') or n.get('id')!r} (not handled in v1)")
else:
figs.append((n, fpath, asset))
if not figs:
@@ -112,6 +120,8 @@ class _Loader:
posed = self._dials() or posed
if posed:
warn("pose in scene ignored: the .dbz holds rest data")
+ if self.hd:
+ warn(f"{self.hd} HD morphs: detail ignored (v1)")
return Scene(self.path, self.figure, self.graph)
def _figure(self, inst, fpath, asset):
@@ -171,25 +181,39 @@ class _Loader:
morph = mod.get("morph")
if morph:
if "hd_url" in morph:
- warn(f"{path}#{mod['id']}: HD morph detail ignored")
+ self.hd += 1
d = morph.get("deltas", {}).get("values", [])
if morph.get("vertex_count", nverts) != nverts:
warn(f"{path}#{mod['id']}: vertex count mismatch, morph skipped")
elif d:
arr = np.array(d, dtype=float)
+ if (arr.ndim != 2 or arr.shape[1] != 4
+ or (arr[:, 0] != np.floor(arr[:, 0])).any()
+ or (arr[:, 0] < 0).any() or (arr[:, 0] >= nverts).any()):
+ raise Error(f"{path}#{mod['id']}: bad morph deltas")
self.figure.morphs[k] = (arr[:, 0].astype(int), arr[:, 1:4])
for f in mod.get("formulas", []):
self._formula(f, f"{path}#{mod['id']}")
+ def _load_file(self, path):
+ if path.lower() in self.loaded:
+ return
+ self.loaded.add(path.lower())
+ doc = self.library.load(path)
+ try:
+ self._modifiers(path, doc)
+ except (KeyError, TypeError, ValueError, IndexError, AttributeError) as e:
+ raise Error(f"{path}: malformed morph data: {e!r}") from None
+
def _morphs(self, fpath):
morph_dir = posixpath.join(posixpath.dirname(fpath), "Morphs")
for path in self.library.walk(morph_dir):
try:
- doc = self.library.load(path)
+ self.library.load(path)
except Error as e:
warn(f"skipped unreadable morph file: {e}")
continue
- self._modifiers(path, doc)
+ self._load_file(path)
def _dials(self):
"""Apply .duf dial values. Returns True if the scene animates a pose channel."""
@@ -199,18 +223,18 @@ class _Loader:
if "url" not in m:
continue
k = key(self.figure.id, parse_url(m["url"])[2], "value")
- v = m.get("channel", {}).get("current_value", 0.0)
+ v = _number(m["url"], m.get("channel", {}).get("current_value", 0.0))
if k not in self.graph.props:
path = parse_url(m["url"])[1]
if path and self.library.find(path):
- self._modifiers(path, self.library.load(path))
+ self._load_file(path)
if k in self.graph.props:
values[k] = v
elif v:
missing.append(m["url"])
for a in scene.get("animations", []):
channel = parse_url(a["url"])[3]
- v = a.get("keys", [[0, 0.0]])[0][1]
+ v = _number(a["url"], a.get("keys", [[0, 0.0]])[0][1])
if channel.split("/")[0] in POSE_REST:
posed = posed or bool(v)
elif channel == "value":
@@ -228,4 +252,7 @@ class _Loader:
def load(path, library):
"""Load a .duf scene and everything its figure needs."""
- return _Loader(path, library).run()
+ try:
+ return _Loader(path, library).run()
+ except (KeyError, TypeError, IndexError, ValueError, AttributeError) as e:
+ raise Error(f"{path}: malformed scene data: {e!r}") from None