diff options
| -rw-r--r-- | generat3d/evaluate.py | 11 | ||||
| -rw-r--r-- | generat3d/scene.py | 11 | ||||
| -rw-r--r-- | tests/test_evaluate.py | 17 | ||||
| -rw-r--r-- | tests/test_scene.py | 12 |
4 files changed, 45 insertions, 6 deletions
diff --git a/generat3d/evaluate.py b/generat3d/evaluate.py index c1a9f1b..173a78f 100644 --- a/generat3d/evaluate.py +++ b/generat3d/evaluate.py @@ -15,6 +15,11 @@ # with this program; if not, write to the Free Software Foundation, Inc., # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +import math + +import numpy as np + +from . import Error from .scene import AXES, REST_CHANNELS, key @@ -26,8 +31,12 @@ def evaluate(scene): for k, (idx, deltas) in fig.morphs.items(): w = values.get(k, 0.0) if w: - verts[idx] += w * deltas # indices are unique within one morph + with np.errstate(all="ignore"): + verts[idx] += w * deltas # indices are unique within one morph rest = {n.id: {ch: [values[key(n.id, n.id, f"{ch}/{a}")] for a in AXES] for ch in REST_CHANNELS} for n in fig.nodes} + if (not np.isfinite(verts).all() + or not all(math.isfinite(v) for r in rest.values() for c in r.values() for v in c)): + raise Error("evaluation produced non-finite values") return verts, rest diff --git a/generat3d/scene.py b/generat3d/scene.py index cccfcff..722057f 100644 --- a/generat3d/scene.py +++ b/generat3d/scene.py @@ -63,8 +63,11 @@ class Scene: def _finite(v): - return (not isinstance(v, bool) and isinstance(v, (int, float)) - and math.isfinite(v)) + try: + return (not isinstance(v, bool) and isinstance(v, (int, float)) + and math.isfinite(v)) + except OverflowError: # huge int + return False def _channel(node, name): @@ -86,8 +89,8 @@ def _lookup(library, doc, url, section): def _number(url, v): - if isinstance(v, bool) or not isinstance(v, (int, float)): - raise Error(f"{url}: dial value is not a number") + if not _finite(v): + raise Error(f"{url}: dial value is not a finite number") return v diff --git a/tests/test_evaluate.py b/tests/test_evaluate.py index 6668b0e..da5b840 100644 --- a/tests/test_evaluate.py +++ b/tests/test_evaluate.py @@ -17,9 +17,12 @@ import contextlib import io +import json +import tempfile import unittest from pathlib import Path +from generat3d import Error from generat3d.evaluate import evaluate from generat3d.library import Library from generat3d.scene import load @@ -37,3 +40,17 @@ class EvaluateTest(unittest.TestCase): self.assertEqual(rest["hip"]["end_point"], [0, 8, 0]) self.assertEqual(rest["Fig"]["end_point"], [0, 10, 0]) self.assertEqual(scene.figure.verts.tolist()[0], [0, 0, 0]) # base untouched + + def test_overflow_is_error(self): + with tempfile.TemporaryDirectory() as d: + root = Path(d) / "extra" + p = root / "data/Test/Fig/Morphs/Test/Big.dsf" + p.parent.mkdir(parents=True) + p.write_text(json.dumps({"modifier_library": [{ + "id": "Big", "parent": "/data/Test/Fig/Fig.dsf#Fig", + "channel": {"id": "value", "value": 1e300}, + "morph": {"vertex_count": 3, "deltas": {"values": [[0, 1e300, 0, 0]]}}}]})) + with contextlib.redirect_stderr(io.StringIO()): + scene = load(DATA / "scene.duf", Library([DATA / "lib", root])) + with self.assertRaisesRegex(Error, "non-finite"): + evaluate(scene) diff --git a/tests/test_scene.py b/tests/test_scene.py index 0d2e3c9..fe6c34d 100644 --- a/tests/test_scene.py +++ b/tests/test_scene.py @@ -170,7 +170,7 @@ class SceneTest(unittest.TestCase): load_quiet(self.duf(scene)) def test_dial_not_a_number(self): - with self.assertRaisesRegex(Error, "dial value is not a number"): + with self.assertRaisesRegex(Error, "dial value is not a finite number"): load_quiet(self.duf({"nodes": self.base_nodes(), "modifiers": [ {"id": "Tall", "url": "/data/Test/Fig/Morphs/Test/Tall.dsf#Tall", "parent": "#Fig", "channel": {"id": "value", "current_value": "a"}}]})) @@ -271,3 +271,13 @@ class SceneTest(unittest.TestCase): "output": "Hip:/data/Test/Fig2/Fig2.dsf#hip?center_point/y", "operations": [{"op": "push", "val": 1}]}]}) self.assertEqual(len(scene.graph.formulas["hip:hip?center_point/y"]), 1) + + def test_dial_not_finite(self): + for v in ("1e400", "10**400"): + text = json.dumps({"scene": {"nodes": self.base_nodes(), "animations": [ + {"url": "Fig:#Tall?value", "keys": [[0, 0]]}]}}).replace( + '"keys": [[0, 0]]', '"keys": [[0, %s]]' % ("1e400" if v == "1e400" else "1" + "0" * 400)) + p = self.dir / "s.duf" + p.write_text(text) + with self.assertRaisesRegex(Error, "dial value is not a finite number"): + load_quiet(p) |
