trenchfoot 0.2.7__py3-none-any.whl → 0.3.0__py3-none-any.whl

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.

Potentially problematic release.


This version of trenchfoot might be problematic. Click here for more details.

@@ -425,6 +425,7 @@ class SurfaceMeshFiles:
425
425
  obj_path: Path
426
426
  metrics_path: Path
427
427
  preview_paths: Tuple[Path, ...]
428
+ sdf_metadata_path: Optional[Path] = None
428
429
 
429
430
 
430
431
  @dataclass
@@ -435,7 +436,73 @@ class SurfaceMeshResult:
435
436
  metrics: Dict[str, Any]
436
437
  previews: Dict[str, bytes]
437
438
 
438
- def persist(self, out_dir: str | Path, *, include_previews: bool = False) -> SurfaceMeshFiles:
439
+ def _build_sdf_metadata(self) -> Dict[str, Any]:
440
+ """Build SDF metadata for downstream consumers.
441
+
442
+ This metadata enables generic mesh-to-SDF pipelines to correctly
443
+ interpret the mesh geometry without trenchfoot-specific heuristics.
444
+ """
445
+ # Extract trench opening polygon from the trench cap geometry
446
+ trench_opening_vertices = None
447
+ if "trench_cap_for_volume" in self.groups:
448
+ V_cap, F_cap = self.groups["trench_cap_for_volume"]
449
+ if V_cap.size > 0:
450
+ # Get unique vertices at z ≈ ground level (the top cap boundary)
451
+ # These form the trench opening polygon
452
+ z_level = float(np.median(V_cap[:, 2]))
453
+ xy_coords = V_cap[:, :2]
454
+ # Use convex hull to get ordered boundary vertices
455
+ try:
456
+ from scipy.spatial import ConvexHull
457
+ hull = ConvexHull(xy_coords)
458
+ boundary_indices = hull.vertices
459
+ trench_opening_vertices = xy_coords[boundary_indices].tolist()
460
+ except ImportError:
461
+ # Fallback: just use unique xy coords (unordered)
462
+ trench_opening_vertices = xy_coords.tolist()
463
+
464
+ # Determine geometry type
465
+ is_closed = _is_path_closed(self.spec.path_xy)
466
+ geometry_type = "closed_well" if is_closed else "open_trench"
467
+
468
+ # Build surface group info
469
+ surface_groups = {}
470
+ for name in self.groups:
471
+ if name in _INTERNAL_GROUPS:
472
+ continue
473
+ if "bottom" in name:
474
+ surface_groups[name] = {"normal_direction": "up", "surface_type": "floor"}
475
+ elif "wall" in name:
476
+ surface_groups[name] = {"normal_direction": "inward", "surface_type": "wall"}
477
+ elif "ground" in name:
478
+ surface_groups[name] = {"normal_direction": "up", "surface_type": "ground"}
479
+ elif "pipe" in name:
480
+ surface_groups[name] = {"normal_direction": "outward", "surface_type": "embedded_object"}
481
+ elif "box" in name or "sphere" in name:
482
+ surface_groups[name] = {"normal_direction": "outward", "surface_type": "embedded_object"}
483
+ else:
484
+ surface_groups[name] = {"normal_direction": "unknown", "surface_type": "other"}
485
+
486
+ return {
487
+ "sdf_metadata": {
488
+ "version": "1.0",
489
+ "normal_convention": "into_void",
490
+ "geometry_type": geometry_type,
491
+ "trench_opening": {
492
+ "type": "polygon",
493
+ "vertices_xy": trench_opening_vertices,
494
+ "z_level": self.spec.ground.z0 if self.spec.ground else 0.0,
495
+ },
496
+ "surface_groups": surface_groups,
497
+ "embedded_objects": {
498
+ "pipes": self.object_counts.get("pipes", 0),
499
+ "boxes": self.object_counts.get("boxes", 0),
500
+ "spheres": self.object_counts.get("spheres", 0),
501
+ },
502
+ }
503
+ }
504
+
505
+ def persist(self, out_dir: str | Path, *, include_previews: bool = False, include_sdf_metadata: bool = True) -> SurfaceMeshFiles:
439
506
  out_path = Path(out_dir)
440
507
  out_path.mkdir(parents=True, exist_ok=True)
441
508
  obj_path = out_path / "trench_scene.obj"
@@ -445,13 +512,27 @@ class SurfaceMeshResult:
445
512
  metrics_path = out_path / "metrics.json"
446
513
  with metrics_path.open("w") as fh:
447
514
  json.dump(self.metrics, fh, indent=2)
515
+
516
+ # Export SDF metadata
517
+ sdf_metadata_path = None
518
+ if include_sdf_metadata:
519
+ sdf_metadata = self._build_sdf_metadata()
520
+ sdf_metadata_path = out_path / "sdf_metadata.json"
521
+ with sdf_metadata_path.open("w") as fh:
522
+ json.dump(sdf_metadata, fh, indent=2)
523
+
448
524
  preview_paths: List[Path] = []
449
525
  if include_previews and self.previews:
450
526
  for name, data in self.previews.items():
451
527
  target = out_path / f"preview_{name}.png"
452
528
  target.write_bytes(data)
453
529
  preview_paths.append(target)
454
- return SurfaceMeshFiles(obj_path=obj_path, metrics_path=metrics_path, preview_paths=tuple(preview_paths))
530
+ return SurfaceMeshFiles(
531
+ obj_path=obj_path,
532
+ metrics_path=metrics_path,
533
+ preview_paths=tuple(preview_paths),
534
+ sdf_metadata_path=sdf_metadata_path,
535
+ )
455
536
 
456
537
  def _ground_fn(g: GroundSpec):
457
538
  sx, sy = g.slope
@@ -620,7 +701,8 @@ def make_trench_from_path_sloped(path_xy: List[Tuple[float,float]], width_top: f
620
701
 
621
702
  bot_verts, bot_faces = _triangulate_annulus(outer_bot, inner_bot[::-1])
622
703
  V_bottom = np.column_stack([bot_verts, np.concatenate([z_outer_bot, z_inner_bot[::-1]])])
623
- F_bottom = bot_faces[:, ::-1] # flip for outward normals
704
+ # Floor normals point UP (+z) into the trench void for correct SDF sign
705
+ F_bottom = _ensure_upward_normals(V_bottom, bot_faces)
624
706
 
625
707
  # Outer wall: connects outer_top to outer_bot (facing outward from trench)
626
708
  n_outer = len(outer_top)
@@ -680,7 +762,8 @@ def make_trench_from_path_sloped(path_xy: List[Tuple[float,float]], width_top: f
680
762
  V_cap = np.column_stack([poly_top, z_top])
681
763
  V_bottom = np.column_stack([poly_bot, z_bot])
682
764
  F_cap = tris_top
683
- F_bottom = tris_bot[:, ::-1] # outward
765
+ # Floor normals point UP (+z) into the trench void for correct SDF sign
766
+ F_bottom = _ensure_upward_normals(V_bottom, tris_bot)
684
767
 
685
768
  # Walls: connect corresponding indices
686
769
  N = len(poly_top)
@@ -715,6 +798,38 @@ def make_trench_from_path_sloped(path_xy: List[Tuple[float,float]], width_top: f
715
798
 
716
799
  return groups, poly_top, poly_bot, extra
717
800
 
801
+ def _ensure_upward_normals(V: np.ndarray, F: np.ndarray) -> np.ndarray:
802
+ """Ensure all faces have upward-pointing normals (+z).
803
+
804
+ For horizontal surfaces like trench floors, normals should point UP
805
+ into the void for correct SDF computation. This function flips any
806
+ faces with downward-pointing normals.
807
+
808
+ Parameters
809
+ ----------
810
+ V : np.ndarray
811
+ Vertices (n, 3)
812
+ F : np.ndarray
813
+ Faces (m, 3) - indices into V
814
+
815
+ Returns
816
+ -------
817
+ np.ndarray
818
+ Faces with consistent upward normals (may have winding flipped)
819
+ """
820
+ F_out = F.copy()
821
+ p0 = V[F[:, 0]]
822
+ p1 = V[F[:, 1]]
823
+ p2 = V[F[:, 2]]
824
+ normals = np.cross(p1 - p0, p2 - p0)
825
+
826
+ # Flip faces with negative z-component normals
827
+ down_mask = normals[:, 2] < 0
828
+ F_out[down_mask] = F_out[down_mask, ::-1]
829
+
830
+ return F_out
831
+
832
+
718
833
  def _triangulate_annulus(outer: np.ndarray, inner: np.ndarray) -> Tuple[np.ndarray, np.ndarray]:
719
834
  """Triangulate the annular region between outer and inner polygons.
720
835
 
@@ -822,6 +937,9 @@ def make_ground_surface_plane(path_xy: List[Tuple[float,float]], width_top: floa
822
937
  combined_xy, tris = _triangulate_annulus(ground_outer, trench_outer)
823
938
  Vg = np.array([[x, y, gfun(x, y)] for (x, y) in combined_xy], float)
824
939
 
940
+ # Ground normals should point UP (+z) into the air
941
+ tris = _ensure_upward_normals(Vg, tris)
942
+
825
943
  return {"ground_surface": (Vg, tris)}
826
944
  else:
827
945
  # Open paths: ground forms annulus with extensions past trench endpoints.
@@ -844,6 +962,9 @@ def make_ground_surface_plane(path_xy: List[Tuple[float,float]], width_top: floa
844
962
  # Apply ground elevation to get 3D vertices
845
963
  Vg = np.array([[x, y, gfun(x, y)] for (x, y) in combined_xy], float)
846
964
 
965
+ # Ground normals should point UP (+z) into the air
966
+ tris = _ensure_upward_normals(Vg, tris)
967
+
847
968
  return {"ground_surface": (Vg, tris)}
848
969
 
849
970
  def _half_width_at_depth(half_top: float, slope: float, top_z: float, z: float) -> float:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trenchfoot
3
- Version: 0.2.7
3
+ Version: 0.3.0
4
4
  Summary: Synthetic trench scenario generator bundle (surfaces + volumetrics).
5
5
  Author: Liam Moore
6
6
  License-File: LICENSE
@@ -6,7 +6,7 @@ trenchfoot/gmsh_sloped_trench_mesher.py,sha256=D7EL6V0wkE6tvDARmm006yZE6KEzCl25O
6
6
  trenchfoot/plot_mesh.py,sha256=26dOlVfaM1WsUfr_sXVqA7axtY9qjY3WCNM7cUBTS7Q,3810
7
7
  trenchfoot/render_colors.py,sha256=EfldvoshgaZ6iZFk-UDL67-tvwGYaiZFS2NQZBMwhDM,1596
8
8
  trenchfoot/scene_spec_example.json,sha256=UcV25ku422UO0ZZPDrJwrT1zwmjoOIpnBdLuEdh-AZA,1028
9
- trenchfoot/trench_scene_generator_v3.py,sha256=3PLkvvwSNmJP2fwAkqkZRTlWqj4yNx5r1rETxrx5QPQ,44430
9
+ trenchfoot/trench_scene_generator_v3.py,sha256=9d93JK1rl61iq4fPxjiENEiFdX0Zhw306UQISeWnnvg,49277
10
10
  trenchfoot/scenarios/SUMMARY.json,sha256=uylEzgzIqk5pGBfWVchVFnwwIDGBjNTDY_E23L_iakI,9372
11
11
  trenchfoot/scenarios/S01_straight_vwalls/metrics.json,sha256=7VDscjZdxNPgNZaPHzRHYBJ1a5amNgJ7XYKCezVJJKQ,691
12
12
  trenchfoot/scenarios/S01_straight_vwalls/preview.png,sha256=adsx3-6oMu9WaixggXRhuXi_KM-q5s2RNSBLR_stq80,199624
@@ -71,8 +71,8 @@ trenchfoot/scenarios/S07_circular_well/preview_top.png,sha256=X1P0g8kblJLsxOpYiZ
71
71
  trenchfoot/scenarios/S07_circular_well/scene.json,sha256=bvror2YX6aNbsEc25-N7JO3ysH2dTLGyEE6zGzZysXQ,3146
72
72
  trenchfoot/scenarios/S07_circular_well/trench_scene.obj,sha256=leTTT0i5xE-fvFSzHLNf_JBsU0AN3YqadDx4HmNmFhU,1618101
73
73
  trenchfoot/scenarios/S07_circular_well/volumetric/trench_volume.msh,sha256=dqhtd3SFKj5RLT_BcWIIvVGCbAqvOx7RX25-K7NKX10,615212
74
- trenchfoot-0.2.7.dist-info/METADATA,sha256=_z3RUhu1UZm0sLNyZO3AbzL4nmEhZjevNObCwu-T_R4,5292
75
- trenchfoot-0.2.7.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
76
- trenchfoot-0.2.7.dist-info/entry_points.txt,sha256=5TejAGmc4GnNYLn7MhhLtSCNz9240RvzcNaetF4IHfg,119
77
- trenchfoot-0.2.7.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
78
- trenchfoot-0.2.7.dist-info/RECORD,,
74
+ trenchfoot-0.3.0.dist-info/METADATA,sha256=_N5N5_BZvcb3JmbEKrtaosI3BEe1DIh0fOzIunUNF88,5292
75
+ trenchfoot-0.3.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
76
+ trenchfoot-0.3.0.dist-info/entry_points.txt,sha256=5TejAGmc4GnNYLn7MhhLtSCNz9240RvzcNaetF4IHfg,119
77
+ trenchfoot-0.3.0.dist-info/licenses/LICENSE,sha256=hIahDEOTzuHCU5J2nd07LWwkLW7Hko4UFO__ffsvB-8,34523
78
+ trenchfoot-0.3.0.dist-info/RECORD,,