From 31aa69961fb49168af54a4a9970d734767185917 Mon Sep 17 00:00:00 2001 From: Dirlligafu Date: Sat, 11 Jul 2026 11:58:17 +1100 Subject: [PATCH] Scale hidden line tolerance to model size and add optional full wireframe toggle --- app.py | 4 ++++ renderer.py | 29 ++++++++++++++++++++++++++--- templates/index.html | 5 +++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/app.py b/app.py index 5ab7d43..84ff183 100644 --- a/app.py +++ b/app.py @@ -122,6 +122,7 @@ def _selection_path(filename): "front_flip": False, "output_format": "png", "label_parts": False, + "show_hidden_lines": 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)) + show_hidden_lines = bool(data.get("show_hidden_lines", 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 @@ -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, + "show_hidden_lines": show_hidden_lines, "ao_mode": ao_mode, "keep_uploads": keep_uploads, }) @@ -396,6 +399,7 @@ def stream(): resolution=render_res, ao_mesh=ao_mesh, ao_mode=(ao_mode if ao_enabled else None), ao_max_darkness=ao_darkness / 100.0, + show_hidden_lines=show_hidden_lines, ) if part_centroids: r = view_results[v] diff --git a/renderer.py b/renderer.py index 7905cfb..481dd94 100644 --- a/renderer.py +++ b/renderer.py @@ -300,12 +300,12 @@ def _smooth_closed_contour(coords, iterations=2): def render_view(mesh, view_name, center, half_span, dist, axis_cfg, - resolution=1800, n_samples=30, depth_eps=0.018, + resolution=1800, n_samples=30, depth_eps=None, crease_angle_deg=25.0, ao_mesh=None, ao_levels=9, min_contour_area=60, ao_blur_sigma_px=1.4, ao_mode=None, ao_max_darkness=0.5, smooth_contours=True, smooth_iterations=1, - contour_supersample=4): + contour_supersample=4, show_hidden_lines=False): """Renders one orthographic view of mesh, returning a dict with: outer_contours: list of Nx2 pixel-space silhouette contours ao_raster: (gray uint8 array, alpha bool mask) for AO shading as a @@ -323,8 +323,24 @@ def render_view(mesh, view_name, center, half_span, dist, axis_cfg, ao_mode: None (no AO), "vertex" (use ao_mesh's baked vertex colors), or "ssao" (screen-space AO computed directly from THIS view's own depth buffer — no ao_mesh needed, genuinely per-view, recomputed every call). + + depth_eps: tolerance (world units) used by compute_visible_edges() to + decide whether an edge sample is at the same depth as the nearest + surface there (visible) or meaningfully behind it (hidden). Defaults to + None, which derives it from half_span instead of using a fixed number -- + a fixed absolute default breaks down for models exported at a very + different unit scale than the "normal-size car" it was tuned for (e.g. + a whole model just a few centimeters across): the tolerance can end up + larger than the model itself, so the visibility test never hides + anything and interior geometry bleeds through as visible lines. + + show_hidden_lines: if True, skips the occlusion test entirely and keeps + every feature/boundary edge regardless of what's in front of it (full + wireframe). The silhouette is still computed normally either way. """ znear, zfar = 0.01, dist * 2.2 + if depth_eps is None: + depth_eps = half_span * 0.0075 eye, up = _view_geometry(view_name, center, dist, axis_cfg) # Feature edges (dihedral angle) + true mesh boundaries. On fragmented @@ -343,7 +359,14 @@ def render_view(mesh, view_name, center, half_span, dist, axis_cfg, except Exception: edges_v = np.empty((0, 2), dtype=int) - if len(edges_v) > 0: + if show_hidden_lines: + # Still need a depth render for the silhouette, just not for edge + # visibility -- every feature/boundary edge is kept as-is. + depth_raw, pose, cam = render_depth(mesh_welded, eye, center, up, half_span, half_span, + resolution=resolution, znear=znear, zfar=zfar) + depth_true = pyrender_depth_to_true_distance(depth_raw, znear, zfar) + segs = list(zip(mesh_welded.vertices[edges_v[:, 0]], mesh_welded.vertices[edges_v[:, 1]])) if len(edges_v) > 0 else [] + elif len(edges_v) > 0: segs, depth_true, pose, cam = compute_visible_edges( mesh_welded, edges_v, eye, center, up, half_span, half_span, resolution=resolution, n_samples=n_samples, znear=znear, zfar=zfar, depth_eps=depth_eps diff --git a/templates/index.html b/templates/index.html index 3adc216..1ae8879 100644 --- a/templates/index.html +++ b/templates/index.html @@ -237,6 +237,9 @@

5. Detail & Output

Labels are placed at each part's center point in every view. Parts hidden behind other geometry still get a label there.

+
+ +