Repository: mikedh/trimesh

Issue:
Getting error trying to run projection function
Hello,
I am using version 3.9.36.
When I try to execute projected function on my mesh I get following error:

```
Traceback (most recent call last):
  File "/home/parsa/Documents/test/python_workshop/cts/trimesh_center_in_pyvista.py", line 8, in <module>
    projection = mesh.projected(normal=[0,0,1])
  File "/home/parsa/Documents/test/python_workshop/venv/lib/python3.9/site-packages/trimesh/base.py", line 2539, in projected
    return load_path(projected(mesh=self, normal=normal, **kwargs))
  File "/home/parsa/Documents/test/python_workshop/venv/lib/python3.9/site-packages/trimesh/path/exchange/load.py", line 62, in load_path
    kwargs.update(misc.lines_to_path(file_obj))
  File "/home/parsa/Documents/test/python_workshop/venv/lib/python3.9/site-packages/trimesh/path/exchange/misc.py", line 56, in lines_to_path
    lines = np.asanyarray(lines, dtype=np.float64)
TypeError: float() argument must be a string or a number, not 'Polygon'
```

my code is:

```
import trimesh
import numpy as np


mesh = trimesh.load_mesh('/home/parsa/Documents/test/python_workshop/cts/703-fixed.ply')


projection = mesh.projected(normal=[0,0,1])

```

Thanks!

Discussion:

Patch:
diff --git a/trimesh/path/exchange/load.py b/trimesh/path/exchange/load.py
index 054efae6..fbcae664 100644
--- a/trimesh/path/exchange/load.py
+++ b/trimesh/path/exchange/load.py
@@ -47,7 +47,7 @@ def load_path(file_obj, file_type=None, **kwargs):
             file_type = os.path.splitext(file_obj)[-1][1:].lower()
             # call the loader
             kwargs.update(path_loaders[file_type](f, file_type=file_type))
-    elif util.is_instance_named(file_obj, 'Polygon'):
+    elif util.is_instance_named(file_obj, ['Polygon', 'MultiPolygon']):
         # convert from shapely polygons to Path2D
         kwargs.update(misc.polygon_to_path(file_obj))
     elif util.is_instance_named(file_obj, 'MultiLineString'):
diff --git a/trimesh/path/exchange/misc.py b/trimesh/path/exchange/misc.py
index 6520564f..4d05c986 100644
--- a/trimesh/path/exchange/misc.py
+++ b/trimesh/path/exchange/misc.py
@@ -6,8 +6,6 @@ from ... import grouping
 
 from ..entities import Line, Arc
 
-from collections import deque
-
 
 def dict_to_path(as_dict):
     """
@@ -93,20 +91,24 @@ def polygon_to_path(polygon):
       Keyword arguments for Path2D constructor
     """
     # start with a single polyline for the exterior
-    entities = deque([Line(points=np.arange(
-        len(polygon.exterior.coords)))])
+    entities = []
     # start vertices
-    vertices = np.array(polygon.exterior.coords).tolist()
+    vertices = []
+
+    if hasattr(polygon.boundary, 'geoms'):
+        boundaries = polygon.boundary.geoms
+    else:
+        boundaries = [polygon.boundary]
 
     # append interiors as single Line objects
-    for boundary in polygon.interiors:
+    for boundary in boundaries:
         entities.append(Line(np.arange(len(boundary.coords)) +
                              len(vertices)))
         # append the new vertex array
         vertices.extend(boundary.coords)
 
     # make sure result arrays are numpy
-    kwargs = {'entities': np.array(entities),
+    kwargs = {'entities': entities,
               'vertices': np.array(vertices)}
 
     return kwargs
diff --git a/trimesh/path/polygons.py b/trimesh/path/polygons.py
index b0ba438c..fe634b53 100644
--- a/trimesh/path/polygons.py
+++ b/trimesh/path/polygons.py
@@ -607,7 +607,7 @@ def repair_invalid(polygon, scale=None, rtol=.5):
     basic = polygon.buffer(tol.zero)
     # if it returned multiple polygons check the largest
     if util.is_sequence(basic):
-        basic = basic[np.argmax([i.area for i in basic])]
+        basic = basic.geoms[np.argmax([i.area for i in basic.geoms])]
 
     # check perimeter of result against original perimeter
     if basic.is_valid and np.isclose(basic.length,
@@ -654,7 +654,7 @@ def repair_invalid(polygon, scale=None, rtol=.5):
     buffered = polygon.buffer(distance).buffer(-distance)
     # if it returned multiple polygons check the largest
     if util.is_sequence(buffered):
-        buffered = buffered[np.argmax([i.area for i in buffered])]
+        buffered = buffered.geoms[np.argmax([i.area for i in buffered.geoms])]
     # check perimeter of result against original perimeter
     if buffered.is_valid and np.isclose(buffered.length,
                                         polygon.length,
diff --git a/trimesh/util.py b/trimesh/util.py
index 781d6fbd..6c7120cd 100644
--- a/trimesh/util.py
+++ b/trimesh/util.py
@@ -1414,7 +1414,10 @@ def is_instance_named(obj, name):
       Whether the object is a member of the named class
     """
     try:
-        type_named(obj, name)
+        if isinstance(name, list):
+            return any(is_instance_named(obj, i) for i in name)
+        else:
+            type_named(obj, name)
         return True
     except ValueError:
         return False
diff --git a/trimesh/version.py b/trimesh/version.py
index 71aa675a..59f22dae 100644
--- a/trimesh/version.py
+++ b/trimesh/version.py
@@ -1,1 +1,1 @@
-__version__ = '3.9.38'
+__version__ = '3.9.39'


Test patch:
diff --git a/tests/test_polygons.py b/tests/test_polygons.py
index 83a5e84d..ebe26ea2 100644
--- a/tests/test_polygons.py
+++ b/tests/test_polygons.py
@@ -97,6 +97,15 @@ class PolygonTests(g.unittest.TestCase):
         assert g.np.allclose(
             [i.area for i in p], g.np.pi, atol=0.05)
 
+    def test_project_multi(self):
+        mesh = (g.trimesh.creation.box() +
+                g.trimesh.creation.box().apply_translation([3, 0, 0]))
+        proj = mesh.projected(normal=[0, 0, 1])
+
+        assert mesh.body_count == 2
+        assert len(proj.root) == 2
+        assert g.np.isclose(proj.area, 2.0)
+
 
 if __name__ == '__main__':
     g.trimesh.util.attach_to_log()