From 4a71ceda59f7ee120ab10c097cf7cb5aa47d4101 Mon Sep 17 00:00:00 2001 From: "mathmathou@gmail.com" Date: Fri, 10 Jul 2026 11:04:51 +1100 Subject: [PATCH 1/2] =?UTF-8?q?Add=20numbered=20markers=20(S1,=20S2?= =?UTF-8?q?=E2=80=A6)=20for=20cross-section=20positions=20on=20orthographi?= =?UTF-8?q?c=20views?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app.py | 4 ++++ compositor.py | 19 +++++++++++++++---- templates/index.html | 21 +++++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/app.py b/app.py index 5ab7d43..ef4f462 100644 --- a/app.py +++ b/app.py @@ -122,6 +122,7 @@ def _selection_path(filename): "front_flip": False, "output_format": "png", "label_parts": False, + "label_sections": False, "ao_mode": "ssao", "keep_uploads": False, } @@ -301,6 +302,7 @@ def stream(): front_flip = bool(data.get("front_flip", False)) rotation_deg = float(data.get("rotation_deg", 0) or 0) label_parts = bool(data.get("label_parts", False)) + label_sections = bool(data.get("label_sections", True)) keep_uploads = bool(data.get("keep_uploads", False)) # "vertex" (fast: per-vertex baked AO, can look blocky on large # flat panels) or "ssao" (default: screen-space AO computed @@ -324,6 +326,7 @@ def stream(): "ao": ao_enabled, "ao_darkness": ao_darkness, "up_axis": up_axis, "front_flip": front_flip, "rotation_deg": rotation_deg, "output_format": output_format, "label_parts": label_parts, + "label_sections": label_sections, "ao_mode": ao_mode, "keep_uploads": keep_uploads, }) @@ -425,6 +428,7 @@ def stream(): bg_color=bg_color, line_color=line_color, scale_pct=scale_pct, model_name=model_display_name, part_numbers=(part_numbers if label_parts else None), + label_sections=label_sections, ) yield _event("Done.", 1.0, done=True, image_url=f"/outputs/{out_name}") diff --git a/compositor.py b/compositor.py index db5d0af..616b246 100644 --- a/compositor.py +++ b/compositor.py @@ -146,14 +146,20 @@ def _draw_view(ax, result, bbox, line_color, bg_color="#FFFFFF", # pixel regardless of where along the axis it actually is), so a # cut position has no meaningful line to draw there at all — # callers simply don't pass markers for those views. - for frac in (rib_marker_x_fracs or []): + for frac, num in (rib_marker_x_fracs or []): x = xmin + frac * (xmax - xmin) ax.plot([x, x], [ymin, ymax], linestyle=(0, (2, 4)), linewidth=0.5, color=line_color, alpha=0.35, zorder=1, solid_capstyle="butt") - for frac in (rib_marker_y_fracs or []): + if num is not None: + ax.text(x, ymin + (ymax - ymin) * 0.02, f"S{num}", ha="center", va="top", + fontsize=18 * cs, family="DejaVu Sans", color=line_color, alpha=0.85, zorder=1) + for frac, num in (rib_marker_y_fracs or []): y = ymin + frac * (ymax - ymin) ax.plot([xmin, xmax], [y, y], linestyle=(0, (2, 4)), linewidth=0.5, color=line_color, alpha=0.35, zorder=1, solid_capstyle="butt") + if num is not None: + ax.text(xmin + (xmax - xmin) * 0.01, y, f"S{num}", ha="left", va="center", + fontsize=18 * cs, family="DejaVu Sans", color=line_color, alpha=0.85, zorder=1) outer_segs = [np.column_stack([c[:, 1], c[:, 0]]) for c in result["outer_contours"]] if outer_segs: @@ -258,7 +264,8 @@ def _draw_rib(ax, rib_segments, ppm, bbox, line_color, lw=1.0): def compose_image(view_results, rib_sections, rib_ppm, output_path, bg_color="#FFFFFF", line_color="#000000", scale_pct=100, dpi_base=250, - model_name=None, show_chrome=True, part_numbers=None): + model_name=None, show_chrome=True, part_numbers=None, + label_sections=True): """view_results: dict of {view_name: render_view() result}, only for views the user actually requested. rib_sections: list of rib cuts, each a list of (p0,p1) segment pairs @@ -424,7 +431,11 @@ def add_axes_px(x0, y0_top, wpx, hpx): w_v, h_v = sizes[name] ax = add_axes_px(x_cursor, y_cursor, w_v, view_row_h) axis_kind, mirrored = _RIB_MARKER_AXIS.get(name, (None, False)) - fracs_for_view = [(1 - f if mirrored else f) for f in rib_fracs] if axis_kind else [] + fracs_for_view = ( + [(1 - f if mirrored else f, (i + 1) if (show_chrome and label_sections) else None) + for i, f in enumerate(rib_fracs)] + if axis_kind else [] + ) _draw_view( ax, view_results[name], bboxes[name], line_color, bg_color, label=(name.upper() if show_chrome else None), diff --git a/templates/index.html b/templates/index.html index 3adc216..6b2424d 100644 --- a/templates/index.html +++ b/templates/index.html @@ -112,6 +112,19 @@

Orthographic Template Generator

} document.addEventListener('DOMContentLoaded', updateThemeToggleLabel); + + function updateLabelSectionsState() { + var ribChecked = document.getElementById('rib_cb').checked; + var cb = document.getElementById('label_sections_cb'); + if (!ribChecked) { + cb.checked = false; + cb.disabled = true; + } else { + cb.disabled = false; + } + } + + document.addEventListener('DOMContentLoaded', updateLabelSectionsState);
@@ -161,6 +174,9 @@

3. Views

1 (1 = single cut at the midpoint. Higher = more evenly-spaced cuts)
+
+ +

3b. Orientation (only touch if the model loads facing the wrong way)

@@ -285,6 +301,8 @@

5. Detail & Output

document.getElementById('rotation_deg_input').value = prefs.rotation_deg || 0; document.getElementById('output_format_select').value = prefs.output_format || 'png'; document.getElementById('label_parts_cb').checked = !!prefs.label_parts; + document.getElementById('label_sections_cb').checked = !!prefs.label_sections; + updateLabelSectionsState(); document.getElementById('keep_uploads_cb').checked = !!prefs.keep_uploads; } @@ -293,6 +311,8 @@

5. Detail & Output

.then(prefs => applyGlobalPrefs(prefs)) .catch(() => {}); // non-fatal if this fails; controls just keep their defaults +document.getElementById('rib_cb').addEventListener('change', updateLabelSectionsState); + dropZone.addEventListener('click', () => fileInput.click()); dropZone.addEventListener('dragover', e => { e.preventDefault(); dropZone.classList.add('dragover'); }); dropZone.addEventListener('dragleave', () => dropZone.classList.remove('dragover')); @@ -422,6 +442,7 @@

5. Detail & Output

rotation_deg: parseFloat(document.getElementById('rotation_deg_input').value) || 0, output_format: document.getElementById('output_format_select').value, label_parts: document.getElementById('label_parts_cb').checked, + label_sections: document.getElementById('label_sections_cb').checked, keep_uploads: document.getElementById('keep_uploads_cb').checked, }; From 73b1612a11006209ad47781b84015681ad3b7773 Mon Sep 17 00:00:00 2001 From: Dirlligafu Date: Fri, 10 Jul 2026 21:19:13 +1100 Subject: [PATCH 2/2] Fix label_sections default mismatch between request parsing and global prefs --- app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app.py b/app.py index ef4f462..ef32b37 100644 --- a/app.py +++ b/app.py @@ -302,7 +302,7 @@ def stream(): front_flip = bool(data.get("front_flip", False)) rotation_deg = float(data.get("rotation_deg", 0) or 0) label_parts = bool(data.get("label_parts", False)) - label_sections = bool(data.get("label_sections", True)) + label_sections = bool(data.get("label_sections", False)) keep_uploads = bool(data.get("keep_uploads", False)) # "vertex" (fast: per-vertex baked AO, can look blocky on large # flat panels) or "ssao" (default: screen-space AO computed