From 60e3dacbd3fe403e9b72b917fa33758575fa9ba0 Mon Sep 17 00:00:00 2001 From: Eric Vin Date: Thu, 14 Mar 2024 20:53:23 -0700 Subject: [PATCH] Voxel to Mesh Workaround --- src/scenic/core/pruning.py | 45 +++++++++++++++++++++++++-------- src/scenic/core/regions.py | 14 +++++----- src/scenic/core/scenarios.py | 27 ++++++++++---------- src/scenic/syntax/translator.py | 3 +++ tests/core/test_regions.py | 11 -------- 5 files changed, 58 insertions(+), 42 deletions(-) diff --git a/src/scenic/core/pruning.py b/src/scenic/core/pruning.py index d6b345f1a..6ccba1c7a 100644 --- a/src/scenic/core/pruning.py +++ b/src/scenic/core/pruning.py @@ -240,14 +240,26 @@ def pruneContainment(scenario, verbosity): # We can do an exact erosion container = container.buffer(-maxErosion) elif isinstance(container, MeshVolumeRegion): - # We can attempt to erode a voxel approximation of the MeshVolumeRegion. - eroded_container = container._erodeOverapproximate( - maxErosion, PRUNING_PITCH - ) + current_pitch = PRUNING_PITCH + eroded_container = None + + while eroded_container is None: + # We can attempt to erode a voxel approximation of the MeshVolumeRegion. + eroded_container = container._erodeOverapproximate( + maxErosion, PRUNING_PITCH + ) - # Now check if this erosion is useful, i.e. do we have less volume to sample from. - # If so, replace the original container. - if eroded_container.size < container.size: + if isinstance(eroded_container, VoxelRegion): + eroded_container = eroded_container.mesh + + current_pitch = min(2 * current_pitch, 1) + + # Now check if this erosion is valid and useful, i.e. do we have less volume + # to sample from. If so, replace the original container. + if ( + eroded_container is not None + and eroded_container.size < container.size + ): container = eroded_container # Restrict the base region to the possibly eroded container, unless @@ -416,9 +428,22 @@ def bufferHelper(viewRegion): if needsSampling(viewRegion): return viewRegion._bufferOverapproximate(buffer_quantity, 1) else: - return viewRegion._bufferOverapproximate( - buffer_quantity, PRUNING_PITCH - ) + current_pitch = PRUNING_PITCH + buffered_container = None + + while buffered_container is None: + buffered_container = viewRegion._bufferOverapproximate( + buffer_quantity, current_pitch + ) + + if isinstance(buffered_container, VoxelRegion): + buffered_container = buffered_container.mesh + + current_pitch = min(2 * current_pitch, 1) + + assert buffered_container is not None + + return buffered_container else: return viewRegion diff --git a/src/scenic/core/regions.py b/src/scenic/core/regions.py index 61908c87e..ccf4557f2 100644 --- a/src/scenic/core/regions.py +++ b/src/scenic/core/regions.py @@ -2227,6 +2227,7 @@ def dilation(self, iterations, structure=None): ) return VoxelRegion(voxelGrid=new_voxel_grid) + @cached_property def mesh(self): # Extract values for original voxel grid and the surface of the voxel grid. dense_encoding = self.voxelGrid.encoding.dense @@ -2262,10 +2263,6 @@ def actual_face(index): [base_index[0] + 1, base_index[1], base_index[2]] ) if actual_face(target_index): - # # Check for an existing interior/exterior face. If an interior is found, - # # wipe it. If an exterior is found, don't set this one. - # conflicting_points = [(p, fm) for p, fm in point_face_mask_list - # if p[1] == base_index[1] and p[2] == base_index[2]] face_mask[0] = True # Left @@ -2411,9 +2408,12 @@ def actual_face(index): ) out_mesh = trimesh.Trimesh(**trimesh.triangles.to_kwargs(triangles)) - out_mesh.show() - assert out_mesh.is_volume - return out_mesh + + # TODO: Ensure the mesh is a proper volume + if not out_mesh.is_volume: + return None + else: + return MeshVolumeRegion(out_mesh, centerMesh=False) @property def AABB(self): diff --git a/src/scenic/core/scenarios.py b/src/scenic/core/scenarios.py index 1d49c4760..6e339b6ec 100644 --- a/src/scenic/core/scenarios.py +++ b/src/scenic/core/scenarios.py @@ -316,8 +316,6 @@ def __init__( self._instances + paramDeps + tuple(requirementDeps) + tuple(behaviorDeps) ) - self.validate() - # Setup the default checker self.defaultRequirements = self.generateDefaultRequirements() self.setSampleChecker(WeightedAcceptanceChecker(bufferSize=100)) @@ -347,6 +345,19 @@ def validate(self): # Trivial case where container is empty if isinstance(container, EmptyRegion): raise InvalidScenarioError(f"Container region of {oi} is empty") + # Ensure we are not sampling position from AllRegion + if isinstance( + oi.position._conditioned, PointInRegionDistribution + ) and isinstance(oi.position._conditioned.region, AllRegion): + if oi.position.tag == "visible": + raise InvalidScenarioError( + f"Object {oi} uses the visible specifier to specify position, but it lacks enough information to do so." + f" The simplest solution to this is to define a workspace or specify position in some other fashion." + ) + else: + raise InvalidScenarioError( + f"Object {oi} has position sampled from everywhere." + ) # skip objects with unknown positions or bounding boxes if not staticBounds[i]: continue @@ -371,18 +382,6 @@ def validate(self): raise InvalidScenarioError( f"{oi} at {oi.position} intersects" f" {oj} at {oj.position}" ) - if isinstance(oi.position, PointInRegionDistribution) and isinstance( - oi.position.region, AllRegion - ): - if oi.position.tag == "visible": - raise InvalidScenarioError( - f"Object {oi} uses the visible specifier to specify position, but it lacks enough information to do so." - f" The simplest solution to this is to define a workspace or specify position in some other fashion." - ) - else: - raise InvalidScenarioError( - f"Object {oi} has position sampled from everywhere." - ) def generate(self, maxIterations=2000, verbosity=0, feedback=None): """Sample a `Scene` from this scenario. diff --git a/src/scenic/syntax/translator.py b/src/scenic/syntax/translator.py index 701b283a7..994e65b8b 100644 --- a/src/scenic/syntax/translator.py +++ b/src/scenic/syntax/translator.py @@ -687,4 +687,7 @@ def isModularScenario(thing): if usePruning: pruning.prune(scenario, verbosity=errors.verbosityLevel) + # Validate scenario + scenario.validate() + return scenario diff --git a/tests/core/test_regions.py b/tests/core/test_regions.py index b6fb6d6ed..c990dd02c 100644 --- a/tests/core/test_regions.py +++ b/tests/core/test_regions.py @@ -606,17 +606,6 @@ def test_mesh_voxelization(getAssetPath): assert vr.containsPoint(sampled_pt) -def test_voxel_to_mesh(getAssetPath): - plane_region = MeshVolumeRegion.fromFile(getAssetPath("meshes/classic_plane.obj.bz2")) - vr = plane_region.voxelized(max(plane_region.mesh.extents) / 100) - mr = vr.mesh - - points = [vr.uniformPointInner() for _ in range(100)] - - for pt in points: - assert vr.containsPoint(pt) == mr.containsPoint(pt) - - def test_empty_erosion(): box_region = BoxRegion(position=(0, 0, 0), dimensions=(1, 1, 1)) vr = box_region.voxelized(pitch=0.1)