toolwake 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. {toolwake-0.2.1 → toolwake-0.2.2}/CHANGELOG.md +16 -0
  2. {toolwake-0.2.1/src/toolwake.egg-info → toolwake-0.2.2}/PKG-INFO +1 -1
  3. {toolwake-0.2.1 → toolwake-0.2.2}/pyproject.toml +1 -1
  4. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake/__init__.py +1 -1
  5. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake/viewer.py +21 -3
  6. {toolwake-0.2.1 → toolwake-0.2.2/src/toolwake.egg-info}/PKG-INFO +1 -1
  7. {toolwake-0.2.1 → toolwake-0.2.2}/tests/test_toolwake.py +35 -0
  8. {toolwake-0.2.1 → toolwake-0.2.2}/LICENSE +0 -0
  9. {toolwake-0.2.1 → toolwake-0.2.2}/MANIFEST.in +0 -0
  10. {toolwake-0.2.1 → toolwake-0.2.2}/README.md +0 -0
  11. {toolwake-0.2.1 → toolwake-0.2.2}/examples/demo.py +0 -0
  12. {toolwake-0.2.1 → toolwake-0.2.2}/setup.cfg +0 -0
  13. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake/cli.py +0 -0
  14. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake/deposit.py +0 -0
  15. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake/geometry.py +0 -0
  16. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake/profile.py +0 -0
  17. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake/render.py +0 -0
  18. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake/serve.py +0 -0
  19. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake/simulate.py +0 -0
  20. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake/tool.py +0 -0
  21. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake/toolpath.py +0 -0
  22. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake.egg-info/SOURCES.txt +0 -0
  23. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake.egg-info/dependency_links.txt +0 -0
  24. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake.egg-info/entry_points.txt +0 -0
  25. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake.egg-info/requires.txt +0 -0
  26. {toolwake-0.2.1 → toolwake-0.2.2}/src/toolwake.egg-info/top_level.txt +0 -0
@@ -4,6 +4,22 @@ All notable changes to this project are documented here. Format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
5
5
  [Semantic Versioning](https://semver.org/).
6
6
 
7
+ ## [0.2.2] — 2026-09-18
8
+
9
+ Bounds the size of the viewer page. No reported number changes.
10
+
11
+ ### Fixed
12
+ - **The viewer inlined every deposited bead, so the page grew without limit.**
13
+ Frames were capped at 400; beads were not capped at all. A 2 674-row path
14
+ already produces a 1.2 MB page, and a 100 000-row one would produce tens of
15
+ megabytes — which is a problem the moment anyone raises their row budget and
16
+ tries to embed the result in an iframe.
17
+
18
+ `to_html_str` now takes `max_beads` (default 20 000) and decimates the drawn
19
+ wake on the same rule as frames: sample evenly, and never drop a bead
20
+ something collided with. Purely cosmetic — the report is computed from the
21
+ full deposit, and nothing on the page claims a bead count.
22
+
7
23
  ## [0.2.1] — 2026-09-18
8
24
 
9
25
  Finishes 0.2.0. That release fixed the tool's geometry; this one fixes the
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: toolwake
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Simulate a depositing tool travelling a toolpath, accumulate the material it leaves in its wake, and check the tool against it.
5
5
  Author-email: Zane Bates <zanetbates1@gmail.com>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "toolwake"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "Simulate a depositing tool travelling a toolpath, accumulate the material it leaves in its wake, and check the tool against it."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -19,7 +19,7 @@ from .profile import Section, ToolProfile, blunt_cannula, luer_taper_tip
19
19
  from .tool import Needle, ToolPose
20
20
  from .toolpath import PRINT, TRAVEL, Toolpath
21
21
 
22
- __version__ = "0.2.1"
22
+ __version__ = "0.2.2"
23
23
 
24
24
  __all__ = [
25
25
  "Toolpath", "PRINT", "TRAVEL",
@@ -310,7 +310,7 @@ def to_html(result, out="wake.html", **kw):
310
310
  return out
311
311
 
312
312
 
313
- def to_html_str(result, *, max_frames=400, fps=20, title=None,
313
+ def to_html_str(result, *, max_frames=400, max_beads=20000, fps=20, title=None,
314
314
  show_housing=True, live_poll=None):
315
315
  """Write a standalone interactive viewer for `result`.
316
316
 
@@ -318,6 +318,10 @@ def to_html_str(result, *, max_frames=400, fps=20, title=None,
318
318
  "jump to worst" goes straight to the deepest penetration — which is usually
319
319
  the only frame anyone actually wants to see.
320
320
 
321
+ `max_frames` and `max_beads` bound the size of the page. Both sample evenly
322
+ and both keep every collision regardless: the page exists to show what went
323
+ wrong, so that is the one thing decimation must never discard.
324
+
321
325
  The current frame is mirrored into the URL hash, so `file.html#f=506` or
322
326
  `file.html#worst` opens directly on a given row. That turns "look at the
323
327
  collision around row 506" into something sendable.
@@ -353,8 +357,22 @@ def to_html_str(result, *, max_frames=400, fps=20, title=None,
353
357
  if c >= 0 and d < result.threshold}
354
358
  segs = dep.segments()
355
359
  t_of = np.asarray(dep._t) if len(dep) else np.zeros(0, dtype=int)
356
- beads = [[*map(float, s[0]), *map(float, s[1]), int(t_of[j]), 1 if j in hot else 0]
357
- for j, s in enumerate(segs)]
360
+
361
+ # Decimate the drawn wake, on the same rule as frames: sample evenly, and
362
+ # never drop a bead something collided with. Every segment is inlined into
363
+ # the page, so an unbounded wake is an unbounded page — a 2 674-row path
364
+ # gives 1.2 MB, and a 100 000-row one would give tens of megabytes that no
365
+ # iframe is going to enjoy. Purely cosmetic: the report is computed from
366
+ # the full deposit, and nothing on the page claims a bead count.
367
+ n_segs = len(segs)
368
+ if max_beads and n_segs > max_beads:
369
+ stride = int(np.ceil(n_segs / max_beads))
370
+ shown = sorted(set(range(0, n_segs, stride)) | hot)
371
+ else:
372
+ shown = range(n_segs)
373
+ beads = [[*map(float, segs[j][0]), *map(float, segs[j][1]),
374
+ int(t_of[j]), 1 if j in hot else 0]
375
+ for j in shown]
358
376
 
359
377
  frames = []
360
378
  for r in rows:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: toolwake
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Simulate a depositing tool travelling a toolpath, accumulate the material it leaves in its wake, and check the tool against it.
5
5
  Author-email: Zane Bates <zanetbates1@gmail.com>
6
6
  License: MIT
@@ -885,3 +885,38 @@ def test_bead_sits_below_the_nozzle_face_not_on_the_path():
885
885
  assert dropped.n_hits == 0, (
886
886
  f"bead dropped to its layer still collides: {dropped.n_hits} rows, "
887
887
  f"worst {dropped.worst * 1e3:.4f} mm")
888
+
889
+
890
+ def test_viewer_bounds_the_wake_but_keeps_every_collision():
891
+ """The page inlines every bead, so an unbounded wake is an unbounded page.
892
+
893
+ A 2 674-row path already produces 1.2 MB; at the 100 000-row budget the
894
+ backend now allows, an uncapped wake would be tens of megabytes and no
895
+ iframe would enjoy it. Decimation samples evenly and keeps every bead
896
+ something collided with — the page exists to show what went wrong, so that
897
+ is the one thing it must never drop.
898
+ """
899
+ import json
900
+ import re
901
+
902
+ from toolwake.viewer import to_html_str
903
+
904
+ # A tight spiral so plenty of beads land near the tool and some collide.
905
+ path = Toolpath.helix(radius=0.004, pitch=2e-4, turns=12.0, per_turn=120)
906
+ res = simulate(path, Needle(inner_d=90e-6), lag=4, threshold=0.0)
907
+
908
+ def beads_of(html):
909
+ m = re.search(r'"beads":\s*(\[.*?\])\s*,\s*"', html, re.S)
910
+ assert m, "bead array not found in the page"
911
+ return json.loads(m.group(1))
912
+
913
+ full = beads_of(to_html_str(res, max_beads=0))
914
+ small = beads_of(to_html_str(res, max_beads=60))
915
+ assert len(full) > 200, "fixture is too small to exercise decimation"
916
+
917
+ n_hot = sum(1 for b in full if b[7])
918
+ assert len(small) <= 60 + n_hot, (
919
+ f"{len(small)} beads kept against a cap of 60 (+{n_hot} collisions)")
920
+ assert len(small) < len(full)
921
+ assert sum(1 for b in small if b[7]) == n_hot, (
922
+ "decimation dropped a bead that something collided with")
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes