holder: cap total cells at 1000 to prevent runaway renders
User-triggered 120x120 = 14400 cells, which produces huge STL/long renders. Total cells is the right metric (CSG cost scales with count, not max axis), so cap by N = rows*cols (or rows*(rows+1)/2 for tria style). 1000 covers any realistic pack (e.g. 20x50) while blocking accidental misuse. Backend: - holder.py: MAX_CELLS env-tunable (default 1000); expected_cell_count and _check_cell_limit raise ValueError on exceed; both compute_cells() and render_stl() call it up-front. - app.py: /api/holder/render now returns 400 on ValueError (not 500) so the frontend can distinguish bad input from server failure. /api/holder/params now publishes max_cells alongside the schema. Frontend: - holder-app.js: reads max_cells from the params endpoint; status shows "N cells / over limit (1000)" in red and disables the Render and "Design busbars" buttons when exceeded. - holder.css: .topbar-status.over-limit style (red, bold).
This commit is contained in:
@@ -31,6 +31,9 @@ OPENSCAD_BIN = os.environ.get("OPENSCAD_BIN", "openscad")
|
||||
# Empty string disables; default Manifold gives ~10-50x speedup on OpenSCAD 2024+
|
||||
OPENSCAD_BACKEND = os.environ.get("OPENSCAD_BACKEND", "Manifold")
|
||||
RENDER_TIMEOUT = int(os.environ.get("OPENSCAD_TIMEOUT", "300"))
|
||||
# Cap total cells to keep renders fast and STL sizes sane. 1000 covers
|
||||
# real packs (e.g. 20×50 = 1000) while blocking accidental 120×120 = 14400.
|
||||
MAX_CELLS = int(os.environ.get("HOLDER_MAX_CELLS", "1000"))
|
||||
|
||||
|
||||
def openscad_available() -> bool:
|
||||
@@ -126,7 +129,26 @@ def schema_dict() -> list[dict]:
|
||||
_COS30 = math.cos(math.radians(30))
|
||||
|
||||
|
||||
def expected_cell_count(params: dict) -> int:
|
||||
"""Cell count for a pack without computing coordinates."""
|
||||
rows = int(params.get("num_rows", 6))
|
||||
cols = int(params.get("num_cols", 12))
|
||||
style = str(params.get("pack_style", "rect"))
|
||||
if style == "tria":
|
||||
return rows * (rows + 1) // 2
|
||||
return rows * cols
|
||||
|
||||
|
||||
def _check_cell_limit(params: dict) -> None:
|
||||
n = expected_cell_count(params)
|
||||
if n > MAX_CELLS:
|
||||
raise ValueError(
|
||||
f"Too many cells ({n} > {MAX_CELLS}). Reduce rows × cols."
|
||||
)
|
||||
|
||||
|
||||
def compute_cells(params: dict) -> list[dict]:
|
||||
_check_cell_limit(params)
|
||||
cell_dia = float(params.get("cell_dia", 21.2))
|
||||
wall = float(params.get("wall", 0.8))
|
||||
rows = int(params.get("num_rows", 6))
|
||||
@@ -200,6 +222,7 @@ def _filter_params(params: dict) -> dict:
|
||||
|
||||
def render_stl(params: dict) -> bytes:
|
||||
"""Render the .scad with given parameter overrides; return STL bytes."""
|
||||
_check_cell_limit(params)
|
||||
if not openscad_available():
|
||||
raise RuntimeError(
|
||||
f"`{OPENSCAD_BIN}` not found on PATH. Install OpenSCAD "
|
||||
|
||||
Reference in New Issue
Block a user