Repository: mikedh/trimesh
Pull request #1575 (2022-04-29 00:43:21)

Add base_frame parameter to append_scenes method
When using `append_scenes` the base frame of the scenes are ignored and the resulting scene always has the standard `world` base frame.  To enable a meaningful merge in a situation like this:
```
scene_0 = g.trimesh.Scene(base_frame='not_world')
scene_1 = g.trimesh.Scene(base_frame='not_world')

scene_sum = g.trimesh.scene.scene.append_scenes((scene_0, scene_1), common=['not_world'])
scene_sum.show() # will break
```
this MR adds a `base_frame` argument to the `append_scenes` method.

Alternatively, the first element of `common` could be used, but this would be ill-defined in case the variable is empty and might lead to less intuitive behavior.  Let me know which solution is preferred.


Patch:
diff --git a/trimesh/path/simplify.py b/trimesh/path/simplify.py
index a6a58567..329be371 100644
--- a/trimesh/path/simplify.py
+++ b/trimesh/path/simplify.py
@@ -202,7 +202,6 @@ def merge_colinear(points, scale):
     # and direction vectors A-B, B-C, etc
     # these will be perpendicular to the vectors A-C, B-D, etc
     perp = (points[2:] - points[:-2]).T[::-1].T
-    perp[:, 0] *= -1
     perp_norm = util.row_norm(perp)
     perp_nonzero = perp_norm > tol.merge
     perp[perp_nonzero] /= perp_norm[perp_nonzero].reshape((-1, 1))
diff --git a/trimesh/points.py b/trimesh/points.py
index 629736dc..3df29840 100644
--- a/trimesh/points.py
+++ b/trimesh/points.py
@@ -646,15 +646,4 @@ class PointCloud(Geometry3D):
                            **kwargs)
 
     def __add__(self, other):
-        if len(other.colors) == len(self.colors) == 0:
-            colors = None
-        else:
-            # preserve colors
-            # if one point cloud has no color property use black
-            other_colors = [[0, 0, 0, 255]] * \
-                len(other.vertices) if len(other.colors) == 0 else other.colors
-            self_colors = [[0, 0, 0, 255]] * \
-                len(self.vertices) if len(self.colors) == 0 else self.colors
-            colors = np.vstack((self_colors, other_colors))
-        return PointCloud(vertices=np.vstack(
-            (self.vertices, other.vertices)), colors=colors)
+        return PointCloud(vertices=np.vstack((self.vertices, other.vertices)))
diff --git a/trimesh/ray/ray_pyembree.py b/trimesh/ray/ray_pyembree.py
index 5b9603c5..470cc337 100644
--- a/trimesh/ray/ray_pyembree.py
+++ b/trimesh/ray/ray_pyembree.py
@@ -144,13 +144,11 @@ class RayMeshIntersector(object):
           Intersection points, only returned if return_locations
         """
         # make sure input is _dtype for embree
-        ray_origins = np.array(
+        ray_origins = np.asanyarray(
             deepcopy(ray_origins),
             dtype=np.float64)
         ray_directions = np.asanyarray(ray_directions,
                                        dtype=np.float64)
-        if ray_origins.shape != ray_directions.shape:
-            raise ValueError('Ray origin and direction don\'t match!')
         ray_directions = util.unitize(ray_directions)
 
         # since we are constructing all hits, save them to a deque then
@@ -180,10 +178,10 @@ class RayMeshIntersector(object):
             # run the pyembree query
             # if you set output=1 it will calculate distance along
             # ray, which is bizzarely slower than our calculation
-
             query = self._scene.run(
                 ray_origins[current],
                 ray_directions[current])
+
             # basically we need to reduce the rays to the ones that hit
             # something
             hit = query != -1
diff --git a/trimesh/scene/scene.py b/trimesh/scene/scene.py
index 071e6971..ac5a3b30 100644
--- a/trimesh/scene/scene.py
+++ b/trimesh/scene/scene.py
@@ -1174,7 +1174,7 @@ def split_scene(geometry, **kwargs):
     return scene
 
 
-def append_scenes(iterable, common=['world']):
+def append_scenes(iterable, common=['world'], base_frame='world'):
     """
     Concatenate multiple scene objects into one scene.
 
@@ -1184,6 +1184,8 @@ def append_scenes(iterable, common=['world']):
        Geometries that should be appended
     common : (n,) str
        Nodes that shouldn't be remapped
+    base_frame : str
+       Base frame of the resulting scene
 
     Returns
     ------------
@@ -1278,7 +1280,7 @@ def append_scenes(iterable, common=['world']):
         consumed.update(current)
 
     # add all data to a new scene
-    result = Scene()
+    result = Scene(base_frame=base_frame)
     result.graph.from_edgelist(edges)
     result.geometry.update(geometry)
 


Test patch:
diff --git a/tests/test_points.py b/tests/test_points.py
index a3a6333f..5009c9d5 100644
--- a/tests/test_points.py
+++ b/tests/test_points.py
@@ -225,35 +225,6 @@ class PointsTest(g.unittest.TestCase):
         assert culled.shape == (100, 3)
         assert mask.shape == (200,)
 
-    def test_add_operator(self):
-        points_1 = g.np.random.random((10, 3))
-        points_2 = g.np.random.random((20, 3))
-        colors_1 = [[123, 123, 123, 255]] * len(points_1)
-        colors_2 = [[255, 0, 123, 255]] * len(points_2)
-
-        # Test: Both cloud's colors are preserved
-        cloud_1 = g.trimesh.points.PointCloud(points_1, colors=colors_1)
-        cloud_2 = g.trimesh.points.PointCloud(points_2, colors=colors_2)
-
-        cloud_sum = cloud_1 + cloud_2
-        assert g.np.allclose(
-            cloud_sum.colors, g.np.vstack(
-                (cloud_1.colors, cloud_2.colors)))
-
-        # Next test: Only second cloud has colors
-        cloud_1 = g.trimesh.points.PointCloud(points_1)
-        cloud_2 = g.trimesh.points.PointCloud(points_2, colors=colors_2)
-
-        cloud_sum = cloud_1 + cloud_2
-        assert g.np.allclose(cloud_sum.colors[len(cloud_1.vertices):], cloud_2.colors)
-
-        # Next test: Only first cloud has colors
-        cloud_1 = g.trimesh.points.PointCloud(points_1, colors=colors_1)
-        cloud_2 = g.trimesh.points.PointCloud(points_2)
-
-        cloud_sum = cloud_1 + cloud_2
-        assert g.np.allclose(cloud_sum.colors[:len(cloud_1.vertices)], cloud_1.colors)
-
 
 if __name__ == '__main__':
     g.trimesh.util.attach_to_log()
diff --git a/tests/test_scene.py b/tests/test_scene.py
index 01fabd3b..72d8d0d2 100644
--- a/tests/test_scene.py
+++ b/tests/test_scene.py
@@ -390,6 +390,15 @@ class SceneTests(g.unittest.TestCase):
         # nothing should have changed
         assert original.identifier_md5 == original_hash
 
+    def test_append_scenes(self):
+        scene_0 = g.trimesh.Scene(base_frame='not_world')
+        scene_1 = g.trimesh.Scene(base_frame='not_world')
+
+        scene_sum = g.trimesh.scene.scene.append_scenes(
+            (scene_0, scene_1), common=['not_world'], base_frame='not_world')
+
+        assert scene_sum.graph.base_frame == 'not_world'
+
 
 if __name__ == '__main__':
     g.trimesh.util.attach_to_log()
diff --git a/tests/test_simplify.py b/tests/test_simplify.py
index 8880b904..01eed496 100644
--- a/tests/test_simplify.py
+++ b/tests/test_simplify.py
@@ -24,7 +24,7 @@ class SimplifyTest(g.unittest.TestCase):
             # the simplified version shouldn't have lost area
             assert g.np.allclose(path.area,
                                  simplified.area,
-                                 rtol=1e-2)
+                                 rtol=1e-3)
 
             # see if we fit as many arcs as existed in the original drawing
             new_count = sum(int(type(i).__name__ == 'Arc')
@@ -87,17 +87,6 @@ class SimplifyTest(g.unittest.TestCase):
         simple = path_2D.simplify_spline(smooth=0.01)
         assert g.np.isclose(path_2D.area, simple.area, rtol=.01)
 
-    def test_merge_colinear(self):
-        num = 100
-        dists = g.np.linspace(0, 1000, num=num)
-        direction = g.trimesh.unitize([1, g.np.random.rand()])
-        points = direction * dists.reshape((-1, 1))
-        merged = g.trimesh.path.simplify.merge_colinear(points, scale=1000)
-        print('direction:', direction)
-        print('points:', points.shape)
-        print('merged:', merged.shape)
-        assert merged.shape[0] == 2
-
 
 if __name__ == '__main__':
     g.trimesh.util.attach_to_log()