From 53f505f01ec8d470356429e62b620660a65ca9eb Mon Sep 17 00:00:00 2001 From: Michael Dawson-Haggerty Date: Thu, 17 Aug 2023 16:42:16 -0400 Subject: [PATCH 01/14] remove some poorly tested dependencies --- Dockerfile | 6 +- setup.py | 3 - trimesh/integrate.py | 129 ------------------------------------------- 3 files changed, 5 insertions(+), 133 deletions(-) delete mode 100644 trimesh/integrate.py diff --git a/Dockerfile b/Dockerfile index 4a47ab7a6..68e51c531 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,11 @@ COPY --chown=user:user setup.py /home/user/ USER user # install trimesh into .local -RUN pip install /home/user[all] +# then delete any included tests +# also remove Cython after all the building is complete +RUN pip install /home/user[all] && \ + find /home/user/.local -type d -name tests -prune -exec rm -rf {} \; && \ + pip uninstall -y cython #################################### ### Build output image most things should run on diff --git a/setup.py b/setup.py index 68f72d90d..b8ad17956 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,6 @@ 'shapely', # handle 2D polygons robustly 'rtree', # create N-dimension trees for broad-phase queries 'svg.path', # handle SVG format path strings - 'sympy', # do analytical math 'pillow', # load images 'embreex', # Intel's Embree ray check engine with wheels 'requests', # do network requests @@ -56,8 +55,6 @@ requirements_all = requirements_easy.union([ 'python-fcl', # do fast 3D collision queries 'psutil', # figure out how much memory we have - 'glooey', # make GUI applications with 3D stuff - 'meshio', # load a number of additional mesh formats; Python 3.5+ 'scikit-image', # marching cubes and other nice stuff 'xatlas', # texture unwrapping 'pyglet<2', # render preview windows nicely : note pyglet 2.0 is basically a re-write diff --git a/trimesh/integrate.py b/trimesh/integrate.py deleted file mode 100644 index 65cd7d112..000000000 --- a/trimesh/integrate.py +++ /dev/null @@ -1,129 +0,0 @@ -""" -integrate.py --------------- - -Utilities for integrating functions over meshes surfaces. -""" - -import sympy as sp -from sympy.parsing.sympy_parser import parse_expr as sympy_parse - -from .constants import log -from . import util - -import warnings -warnings.warn( - '`trimesh.integrate` is deprecated and will ' + - 'be removed in March 2024, you might take a ' + - 'look at `quadpy` which is far more complete', - category=DeprecationWarning, - stacklevel=2) - - -def symbolic_barycentric(function): - """ - Symbolically integrate a function(x,y,z) across a triangle or mesh. - - Parameters - ---------- - function: string or sympy expression - x, y, z will be replaced with a barycentric representation - and the the function is integrated across the triangle. - - Returns - ---------- - evaluator: numpy lambda function of result which takes a mesh - expr: sympy expression of result - - Examples - ----------- - - In [1]: function = '1' - - In [2]: integrator, expr = integrate_barycentric(function) - - In [3]: integrator - Out[3]: <__main__.evaluator instance at 0x7f66cd2a6200> - - In [4]: expr - Out[4]: 1/2 - - In [5]: result = integrator(mesh) - - In [6]: mesh.area - Out[6]: 34.641016151377542 - - In [7]: result.sum() - Out[7]: 34.641016151377542 - """ - - class evaluator: - - def __init__(self, expr, expr_args): - self.lambdified = sp.lambdify(args=expr_args, - expr=expr, - modules=['numpy', 'sympy']) - - def __call__(self, mesh, *args): - """ - Quickly evaluate the surface integral across a mesh - - Parameters - ---------- - mesh: Trimesh object - - Returns - ---------- - integrated: (len(faces),) float, integral evaluated for each face - """ - integrated = self.lambdified(*mesh.triangles.reshape((-1, 9)).T) - integrated *= 2 * mesh.area_faces - return integrated - - function, symbols = substitute_barycentric(function) - - b1, b2, x1, x2, x3, y1, y2, y3, z1, z2, z3 = symbols - # do the first integral for b1 - integrated_1 = sp.integrate(function, b1) - integrated_1 = (integrated_1.subs({b1: 1 - b2}) - - integrated_1.subs({b1: 0})) - - integrated_2 = sp.integrate(integrated_1, b2) - integrated_2 = (integrated_2.subs({b2: 1}) - - integrated_2.subs({b2: 0})) - - lambdified = evaluator(expr=integrated_2, - expr_args=[x1, y1, z1, x2, y2, z2, x3, y3, z3]) - - return lambdified, integrated_2 - - -def substitute_barycentric(function): - if util.is_string(function): - function = sympy_parse(function) - # barycentric coordinates - b1, b2 = sp.symbols('b1 b2', - real=True, - positive=True) - # vertices of the triangles - x1, x2, x3, y1, y2, y3, z1, z2, z3 = sp.symbols( - 'x1,x2,x3,y1,y2,y3,z1,z2,z3', real=True) - - # generate the substitution dictionary to convert from cartesian to barycentric - # since the input could have been a sympy expression or a string - # that we parsed substitute based on name to avoid id(x) issues - substitutions = {} - for symbol in function.free_symbols: - if symbol.name == 'x': - substitutions[symbol] = b1 * x1 + b2 * x2 + (1 - b1 - b2) * x3 - elif symbol.name == 'y': - substitutions[symbol] = b1 * y1 + b2 * y2 + (1 - b1 - b2) * y3 - elif symbol.name == 'z': - substitutions[symbol] = b1 * z1 + b2 * z2 + (1 - b1 - b2) * z3 - # apply the conversion to barycentric - function = function.subs(substitutions) - log.debug('converted function to barycentric: %s', str(function)) - - symbols = (b1, b2, x1, x2, x3, y1, y2, y3, z1, z2, z3) - - return function, symbols From 63c60951c20df46d722cc150dc35b66d157e3866 Mon Sep 17 00:00:00 2001 From: Ibrar Malik <9162284+ibrarmalik@users.noreply.github.com> Date: Fri, 18 Aug 2023 16:28:25 +0200 Subject: [PATCH 02/14] Preserve texture on slice_plane --- trimesh/intersections.py | 48 +++++++++++++++++++++++++++++++++++----- 1 file changed, 42 insertions(+), 6 deletions(-) diff --git a/trimesh/intersections.py b/trimesh/intersections.py index 8dd2f7715..340106563 100644 --- a/trimesh/intersections.py +++ b/trimesh/intersections.py @@ -13,6 +13,7 @@ from . import transformations as tf from .constants import tol +from .triangles import points_to_barycentric def mesh_plane(mesh, @@ -430,6 +431,7 @@ def slice_faces_plane(vertices, faces, plane_normal, plane_origin, + uv=None, face_index=None, cached_dots=None): """ @@ -447,6 +449,8 @@ def slice_faces_plane(vertices, Normal vector of plane to intersect with mesh plane_origin : (3,) float Point on plane to intersect with mesh + uv : (n, 2) float, optional + UV coordinates of source mesh to slice face_index : ((m,) int) Indexes of faces to slice. When no mask is provided, the default is to slice all faces. @@ -460,10 +464,14 @@ def slice_faces_plane(vertices, Vertices of sliced mesh new_faces : (n, 3) int Faces of sliced mesh + new_uv : (n, 3) int or None + UV coordinates of sliced mesh """ if len(vertices) == 0: - return vertices, faces + return vertices, faces, uv + + have_uv = uv is not None # Construct a mask for the faces to slice. if face_index is not None: @@ -539,7 +547,8 @@ def slice_faces_plane(vertices, if len(new_faces) == 0: # if no new faces at all return empty arrays empty = (np.zeros((0, 3), dtype=np.float64), - np.zeros((0, 3), dtype=np.int64)) + np.zeros((0, 3), dtype=np.int64), + np.zeros((0, 2), dtype=np.float64) if have_uv else None) return empty # find the unique indices in the new faces @@ -551,8 +560,9 @@ def slice_faces_plane(vertices, # use the unique indices for our final vertices and faces final_vert = vertices[unique] final_face = inverse.reshape((-1, 3)) + final_uv = uv[unique] if have_uv else None - return final_vert, final_face + return final_vert, final_face, final_uv # Extract the intersections of each triangle's edges with the plane o = cut_triangles # origins @@ -641,7 +651,23 @@ def slice_faces_plane(vertices, final_vert = new_vertices[unique] final_face = inverse.reshape((-1, 3)) - return final_vert, final_face + final_uv = None + if have_uv: + # Generate barycentric coordinates for intersection vertices + quad_barycentrics = points_to_barycentric( + np.repeat(vertices[cut_faces_quad], 2, axis=0), + new_quad_vertices) + tri_barycentrics = points_to_barycentric( + np.repeat(vertices[cut_faces_tri], 2, axis=0), + new_tri_vertices) + all_barycentrics = np.concatenate([quad_barycentrics, tri_barycentrics]) + + # Interpolate UVs + cut_uv = np.concatenate([uv[cut_faces_quad], uv[cut_faces_tri]]) + new_uv = np.einsum('ijk,ij->ik', np.repeat(cut_uv, 2, axis=0), all_barycentrics) + final_uv = np.concatenate([uv, new_uv])[unique] + + return final_vert, final_face, final_uv def slice_mesh_plane(mesh, @@ -689,6 +715,7 @@ def slice_mesh_plane(mesh, # avoid circular import from .base import Trimesh + from .visual import TextureVisuals from .path import polygons from scipy.spatial import cKDTree from .creation import triangulate_polygon @@ -712,6 +739,11 @@ def slice_mesh_plane(mesh, vertices = mesh.vertices.copy() faces = mesh.faces.copy() + # We copy the UV coordinates if available + has_uv = (hasattr(mesh.visual, 'uv') and np.shape( + mesh.visual.uv) == (len(mesh.vertices), 2)) + uv = mesh.visual.uv.copy() if has_uv else None + if 'process' not in kwargs: kwargs['process'] = False @@ -719,9 +751,10 @@ def slice_mesh_plane(mesh, for origin, normal in zip(plane_origin.reshape((-1, 3)), plane_normal.reshape((-1, 3))): # save the new vertices and faces - vertices, faces = slice_faces_plane( + vertices, faces, uv = slice_faces_plane( vertices=vertices, faces=faces, + uv=uv, plane_normal=normal, plane_origin=origin, face_index=face_index) @@ -774,5 +807,8 @@ def slice_mesh_plane(mesh, faces.append(vid[fn]) faces = np.vstack(faces) + visual = TextureVisuals( + uv=uv, material=mesh.visual.material.copy()) if has_uv else None + # return the sliced mesh - return Trimesh(vertices=vertices, faces=faces, **kwargs) + return Trimesh(vertices=vertices, faces=faces, visual=visual, **kwargs) From 96ed39f4ed1bbb103362928eb87e3803af2b025c Mon Sep 17 00:00:00 2001 From: Ibrar Malik <9162284+ibrarmalik@users.noreply.github.com> Date: Fri, 18 Aug 2023 16:50:16 +0200 Subject: [PATCH 03/14] Don't compute UVs when cap == True. The UV interpolation is not implemented correctly for when cap is True. --- trimesh/intersections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trimesh/intersections.py b/trimesh/intersections.py index 340106563..9cb15151b 100644 --- a/trimesh/intersections.py +++ b/trimesh/intersections.py @@ -741,7 +741,7 @@ def slice_mesh_plane(mesh, # We copy the UV coordinates if available has_uv = (hasattr(mesh.visual, 'uv') and np.shape( - mesh.visual.uv) == (len(mesh.vertices), 2)) + mesh.visual.uv) == (len(mesh.vertices), 2)) and not cap uv = mesh.visual.uv.copy() if has_uv else None if 'process' not in kwargs: From af1f9efb4acdfad86d0b134d31c6d30e3ff8b709 Mon Sep 17 00:00:00 2001 From: Ibrar Malik <9162284+ibrarmalik@users.noreply.github.com> Date: Fri, 18 Aug 2023 16:56:11 +0200 Subject: [PATCH 04/14] Fix wrong array shape on docstring --- trimesh/intersections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trimesh/intersections.py b/trimesh/intersections.py index 9cb15151b..160c87751 100644 --- a/trimesh/intersections.py +++ b/trimesh/intersections.py @@ -464,7 +464,7 @@ def slice_faces_plane(vertices, Vertices of sliced mesh new_faces : (n, 3) int Faces of sliced mesh - new_uv : (n, 3) int or None + new_uv : (n, 2) int or None UV coordinates of sliced mesh """ From 2f6403bd6dfacfee9f4135d89679e39e835725fd Mon Sep 17 00:00:00 2001 From: Ibrar Malik <9162284+ibrarmalik@users.noreply.github.com> Date: Sun, 20 Aug 2023 21:35:27 +0200 Subject: [PATCH 05/14] Fix when UV exist but plane does not intersect --- trimesh/intersections.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/trimesh/intersections.py b/trimesh/intersections.py index 160c87751..2784e50c0 100644 --- a/trimesh/intersections.py +++ b/trimesh/intersections.py @@ -576,6 +576,8 @@ def slice_faces_plane(vertices, # Initialize the array of new vertices with the current vertices new_vertices = vertices + new_quad_vertices = np.zeros((0, 3)) + new_tri_vertices = np.zeros((0, 3)) # Handle the case where a new quad is formed by the intersection # First, extract the intersection points belonging to a new quad From 4b5e0e8c9048ff7f09e6b45480ee43b5d76373ed Mon Sep 17 00:00:00 2001 From: Ibrar Malik <9162284+ibrarmalik@users.noreply.github.com> Date: Sun, 20 Aug 2023 21:36:58 +0200 Subject: [PATCH 06/14] Add tests for slice mesh with texture --- tests/test_section.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/test_section.py b/tests/test_section.py index 2f8044978..a75ea9508 100644 --- a/tests/test_section.py +++ b/tests/test_section.py @@ -291,6 +291,32 @@ def test_slice_submesh(self): assert len(sliced.faces) > 0 + def test_textured_mesh(self): + # Create a plane mesh with UV == xy + plane = g.trimesh.Trimesh( + vertices=g.np.array([[0., 0., 0.], [1., 0., 0.], [1., 1., 0.], [0., 1., 0.]]), + faces=g.np.array([[0, 1, 2], [2, 3, 0]])) + plane.visual = g.trimesh.visual.TextureVisuals( + uv=plane.vertices[:, :2], material=g.trimesh.visual.material.empty_material()) + + # We cut the plane and check that the new UV match the new vertices + origin = g.np.array([0.5, 0.5, 0.]) + normal = g.trimesh.unitize([2, 1, 2]) + sliced = plane.slice_plane(plane_origin=origin, plane_normal=normal) + assert g.np.isclose(sliced.vertices[:, :2], sliced.visual.uv).all() + + # Test scenario when plane does not cut + origin = g.np.array([-1., -1., 0.]) + normal = g.trimesh.unitize([1, 1, 2]) + sliced = plane.slice_plane(plane_origin=origin, plane_normal=normal) + assert g.np.isclose(sliced.vertices[:, :2], sliced.visual.uv).all() + + # Test cut with no new vertices + origin = g.np.array([0.5, 0.5, 0.]) + normal = g.trimesh.unitize([2, -2, 1]) + sliced = plane.slice_plane(plane_origin=origin, plane_normal=normal) + assert g.np.isclose(sliced.vertices[:, :2], sliced.visual.uv).all() + def test_cap_coplanar(self): # check to see if we handle capping with # existing coplanar faces correctly From d9a389d1f379e4e3aa0a8fe50e464da4914dea09 Mon Sep 17 00:00:00 2001 From: Michael Dawson-Haggerty Date: Mon, 21 Aug 2023 22:14:50 -0400 Subject: [PATCH 07/14] assign a system uid to docker user --- Dockerfile | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 68e51c531..3f1e259ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -5,7 +5,10 @@ LABEL maintainer="mikedh@kerfed.com" COPY --chmod=755 docker/trimesh-setup /usr/local/bin/ # Create a local non-root user. -RUN useradd -m -s /bin/bash user +RUN useradd -m \ # -m creates a home directory + -u 499 \ # set UID to that of a system user 100-499 + -s /bin/bash \ # set default shell to bash + user # make the username `user` # Required for Python to be able to find libembree. ENV LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" From d186c61ec044956adf2ca7ddc0c22b78d1506076 Mon Sep 17 00:00:00 2001 From: Michael Dawson-Haggerty Date: Mon, 21 Aug 2023 22:23:18 -0400 Subject: [PATCH 08/14] remove comments --- Dockerfile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Dockerfile b/Dockerfile index 3f1e259ba..5e1d0a3b7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,11 +4,8 @@ LABEL maintainer="mikedh@kerfed.com" # Install helper script to PATH. COPY --chmod=755 docker/trimesh-setup /usr/local/bin/ -# Create a local non-root user. -RUN useradd -m \ # -m creates a home directory - -u 499 \ # set UID to that of a system user 100-499 - -s /bin/bash \ # set default shell to bash - user # make the username `user` +# Create a non-root user with `uid=499`. +RUN useradd -m -u 499 -s /bin/bash user # Required for Python to be able to find libembree. ENV LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" From 29673a42ad5e49addb85c70c6abffff2a859d58a Mon Sep 17 00:00:00 2001 From: Michael Dawson-Haggerty Date: Wed, 23 Aug 2023 16:19:46 -0400 Subject: [PATCH 09/14] soft require sympy --- Dockerfile | 6 +- setup.py | 283 +++++++++++++++++++++++++++-------------------- tests/generic.py | 2 - 3 files changed, 163 insertions(+), 128 deletions(-) diff --git a/Dockerfile b/Dockerfile index 5e1d0a3b7..61c89fe84 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,8 +27,8 @@ COPY --chown=user:user setup.py /home/user/ USER user # install trimesh into .local -# then delete any included tests -# also remove Cython after all the building is complete +# then delete any included test directories +# and remove Cython after all the building is complete RUN pip install /home/user[all] && \ find /home/user/.local -type d -name tests -prune -exec rm -rf {} \; && \ pip uninstall -y cython @@ -67,7 +67,7 @@ RUN trimesh-setup --install=test,gltf_validator,llvmpipe,binvox USER user # install things like pytest -RUN pip install `python setup.py --list-test` +RUN pip install .[all,easy,recommends,tests] # run pytest wrapped with xvfb for simple viewer tests RUN xvfb-run pytest --cov=trimesh \ diff --git a/setup.py b/setup.py index b8ad17956..617ca81c5 100644 --- a/setup.py +++ b/setup.py @@ -5,85 +5,100 @@ from setuptools import setup # load __version__ without importing anything -_version_file = os.path.join( - os.path.dirname(__file__), - 'trimesh', 'version.py') +_version_file = os.path.join(os.path.dirname(__file__), "trimesh", "version.py") if os.path.isfile(_version_file): - with open(_version_file, 'r') as f: + with open(_version_file, "r") as f: _version_raw = f.read() # use eval to get a clean string of version from file - __version__ = eval(next( - line.strip().split('=')[-1] - for line in str.splitlines(_version_raw) - if '_version_' in line)) + __version__ = eval( + next( + line.strip().split("=")[-1] + for line in str.splitlines(_version_raw) + if "_version_" in line + ) + ) else: __version__ = None # load README.md as long_description -long_description = '' -if os.path.exists('README.md'): - with open('README.md', 'r') as f: +long_description = "" +if os.path.exists("README.md"): + with open("README.md", "r") as f: long_description = f.read() # minimal requirements for installing trimesh -requirements_default = set(['numpy']) +requirements_default = set(["numpy"]) # "easy" requirements should install without compiling # anything on Windows, Linux, and Mac, for Python >= 3.6 -requirements_easy = set([ - 'scipy', # provide convex hulls, fast graph ops, etc - 'networkx', # provide slow graph ops with a nice API - 'lxml', # handle XML better and faster than built- in XML - 'shapely', # handle 2D polygons robustly - 'rtree', # create N-dimension trees for broad-phase queries - 'svg.path', # handle SVG format path strings - 'pillow', # load images - 'embreex', # Intel's Embree ray check engine with wheels - 'requests', # do network requests - 'xxhash', # hash ndarrays faster than built-in MD5/CRC - 'setuptools', # do setuptools stuff - 'jsonschema', # validate JSON schemas like GLTF - 'pycollada', # parse collada/dae/zae files - 'chardet', # figure out if someone used UTF-16 - 'mapbox-earcut', # fast 2D triangulations of polygons - 'colorlog']) # log in pretty colors +requirements_easy = set( + [ + "scipy", # provide convex hulls, fast graph ops, etc + "networkx", # provide slow graph ops with a nice API + "lxml", # handle XML better and faster than built- in XML + "shapely", # handle 2D polygons robustly + "rtree", # create N-dimension trees for broad-phase queries + "svg.path", # handle SVG format path strings + "pillow", # load images + "embreex", # Intel's Embree ray check engine with wheels + "requests", # do network requests + "xxhash", # hash ndarrays faster than built-in MD5/CRC + "setuptools", # do setuptools stuff + "jsonschema", # validate JSON schemas like GLTF + "pycollada", # parse collada/dae/zae files + "chardet", # figure out if someone used UTF-16 + "mapbox-earcut", # fast 2D triangulations of polygons + "colorlog", + ] +) # log in pretty colors # "all" requirements only need to be installable # through some mechanism on Linux with Python 3.5+ # and are allowed to compile code -requirements_all = requirements_easy.union([ - 'python-fcl', # do fast 3D collision queries - 'psutil', # figure out how much memory we have - 'scikit-image', # marching cubes and other nice stuff - 'xatlas', # texture unwrapping - 'pyglet<2', # render preview windows nicely : note pyglet 2.0 is basically a re-write -]) +requirements_all = requirements_easy.union( + [ + "python-fcl", # do fast 3D collision queries + "psutil", # figure out how much memory we have + "scikit-image", # marching cubes and other nice stuff + "xatlas", # texture unwrapping + "pyglet<2", # render preview windows nicely : note pyglet 2.0 is basically a re-write + ] +) # requirements for running unit tests -requirements_test = set(['pytest', # run all unit tests - 'pytest-cov', # coverage plugin - 'pyinstrument', # profile code - 'coveralls', # report coverage stats - 'autopep8<2', # check and autoformat - 'ruff', # static code analysis - 'pymeshlab', # used as a validator for exports - 'ezdxf']) # used as a validator for exports +requirements_test = set( + [ + "pytest", # run all unit tests + "pytest-cov", # coverage plugin + "pyinstrument", # profile code + "coveralls", # report coverage stats + "autopep8<2", # check and autoformat + "ruff", # static code analysis + "pymeshlab", # used as a validator for exports + "ezdxf", + ] +) # used as a validator for exports + +# things that are used implicitly +requirements_recommends = set(["meshio", "sympy", "glooey"]) # Python 2.7 and 3.4 support has been dropped from packages # version lock those packages here so install succeeds current = (sys.version_info.major, sys.version_info.minor) # packages that no longer support old Python -lock = [((3, 4), 'lxml', '4.3.5'), - ((3, 4), 'shapely', '1.6.4'), - ((3, 4), 'pyglet', '1.4.10'), - ((3, 5), 'sympy', None), - ((3, 6), 'pyglet<2', None), - ((3, 6), 'autopep8', None), - ((3, 6), 'ruff', None), - ((3, 7), 'pymeshlab', None), - ((3, 5), 'embreex', None), - ((3, 6), 'svg.path', '4.1')] +lock = [ + ((3, 4), "lxml", "4.3.5"), + ((3, 4), "shapely", "1.6.4"), + ((3, 4), "pyglet", "1.4.10"), + ((3, 5), "sympy", None), + ((3, 6), "pyglet<2", None), + ((3, 6), "autopep8", None), + ((3, 6), "ruff", None), + ((3, 7), "pymeshlab", None), + ((3, 5), "embreex", None), + ((3, 6), "svg.path", "4.1"), +] for max_python, name, version in lock: if current <= max_python: # remove version-free requirements @@ -93,7 +108,7 @@ # if version is None drop that package if version is not None: # add working version locked requirements - requirements_easy.add('{}=={}'.format(name, version)) + requirements_easy.add("{}=={}".format(name, version)) def format_all(): @@ -109,104 +124,126 @@ def run_on(target): word_skip = "datas,coo,nd,files',filetests,ba,childs,whats" # files to skip spelling on file_skip = "*.pyc,*.zip,.DS_Store,*.js,./trimesh/resources" - spell = ['codespell', '-i', '3', - '--skip=' + file_skip, - '-L', word_skip, '-w', target] - print("Running: \n {} \n\n\n".format(' '.join(spell))) + spell = [ + "codespell", + "-i", + "3", + "--skip=" + file_skip, + "-L", + word_skip, + "-w", + target, + ] + print("Running: \n {} \n\n\n".format(" ".join(spell))) subprocess.check_call(spell) - formatter = ["autopep8", "--recursive", "--verbose", - "--in-place", "--aggressive", target] - print("Running: \n {} \n\n\n".format( - ' '.join(formatter))) + formatter = [ + "autopep8", + "--recursive", + "--verbose", + "--in-place", + "--aggressive", + target, + ] + print("Running: \n {} \n\n\n".format(" ".join(formatter))) subprocess.check_call(formatter) - flake = ['flake8', target] - print("Running: \n {} \n\n\n".format(' '.join(flake))) + flake = ["flake8", target] + print("Running: \n {} \n\n\n".format(" ".join(flake))) subprocess.check_call(flake) # run on our target locations - for t in ['trimesh', 'tests', 'examples']: + for t in ["trimesh", "tests", "examples"]: run_on(t) # if someone wants to output a requirements file # `python setup.py --list-all > requirements.txt` -if '--list-all' in sys.argv: +if "--list-all" in sys.argv: # will not include default requirements (numpy) - print('\n'.join(requirements_all)) + print("\n".join(requirements_all)) exit() -elif '--list-easy' in sys.argv: +elif "--list-easy" in sys.argv: # again will not include numpy+setuptools - print('\n'.join(requirements_easy)) + print("\n".join(requirements_easy)) exit() -elif '--list-test' in sys.argv: +elif "--list-test" in sys.argv: # again will not include numpy+setuptools - print('\n'.join(requirements_test)) + print("\n".join(requirements_test)) exit() -elif '--format' in sys.argv: +elif "--format" in sys.argv: format_all() exit() -elif '--bump' in sys.argv: +elif "--bump" in sys.argv: # bump the version number # convert current version to integers - bumped = [int(i) for i in __version__.split('.')] + bumped = [int(i) for i in __version__.split(".")] # increment the last field by one bumped[-1] += 1 # re-combine into a version string - version_new = '.'.join(str(i) for i in bumped) - print('version bump `{}` => `{}`'.format( - __version__, version_new)) + version_new = ".".join(str(i) for i in bumped) + print("version bump `{}` => `{}`".format(__version__, version_new)) # write back the original version file with # just the value replaced with the new one raw_new = _version_raw.replace(__version__, version_new) - with open(_version_file, 'w') as f: + with open(_version_file, "w") as f: f.write(raw_new) exit() # call the magical setuptools setup -setup(name='trimesh', - version=__version__, - description='Import, export, process, analyze and view triangular meshes.', - long_description=long_description, - long_description_content_type='text/markdown', - author='Michael Dawson-Haggerty', - author_email='mikedh@kerfed.com', - license='MIT', - url='https://github.com/mikedh/trimesh', - keywords='graphics mesh geometry 3D', - classifiers=[ - 'Development Status :: 4 - Beta', - 'License :: OSI Approved :: MIT License', - 'Programming Language :: Python', - 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - 'Programming Language :: Python :: 3.8', - 'Programming Language :: Python :: 3.9', - 'Programming Language :: Python :: 3.10', - 'Natural Language :: English', - 'Topic :: Scientific/Engineering'], - packages=[ - 'trimesh', - 'trimesh.ray', - 'trimesh.path', - 'trimesh.path.exchange', - 'trimesh.scene', - 'trimesh.voxel', - 'trimesh.visual', - 'trimesh.viewer', - 'trimesh.exchange', - 'trimesh.resources', - 'trimesh.interfaces'], - package_data={'trimesh': ['resources/templates/*', - 'resources/*.json', - 'resources/schema/*', - 'resources/schema/primitive/*.json', - 'resources/*.zip']}, - install_requires=list(requirements_default), - extras_require={'test': list(requirements_test), - 'easy': list(requirements_easy), - 'all': list(requirements_all)}) +setup( + name="trimesh", + version=__version__, + description="Import, export, process, analyze and view triangular meshes.", + long_description=long_description, + long_description_content_type="text/markdown", + author="Michael Dawson-Haggerty", + author_email="mikedh@kerfed.com", + license="MIT", + url="https://github.com/mikedh/trimesh", + keywords="graphics mesh geometry 3D", + classifiers=[ + "Development Status :: 4 - Beta", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", + "Programming Language :: Python :: 3.7", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Natural Language :: English", + "Topic :: Scientific/Engineering", + ], + packages=[ + "trimesh", + "trimesh.ray", + "trimesh.path", + "trimesh.path.exchange", + "trimesh.scene", + "trimesh.voxel", + "trimesh.visual", + "trimesh.viewer", + "trimesh.exchange", + "trimesh.resources", + "trimesh.interfaces", + ], + package_data={ + "trimesh": [ + "resources/templates/*", + "resources/*.json", + "resources/schema/*", + "resources/schema/primitive/*.json", + "resources/*.zip", + ] + }, + install_requires=list(requirements_default), + extras_require={ + "test": list(requirements_test), + "easy": list(requirements_easy), + "all": list(requirements_all), + "recommends": list(requirments_recommends), + }, +) diff --git a/tests/generic.py b/tests/generic.py index facdac4e4..871a5e305 100644 --- a/tests/generic.py +++ b/tests/generic.py @@ -85,8 +85,6 @@ def output_text(*args, **kwargs): try: import sympy as sp except ImportError as E: - if all_dependencies: - raise E sp = None try: From 5dc029a0047ac64377794d3704e7f2b9fafd031b Mon Sep 17 00:00:00 2001 From: Michael Dawson-Haggerty Date: Wed, 23 Aug 2023 16:41:23 -0400 Subject: [PATCH 10/14] fix typo --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 617ca81c5..7a07c79f2 100644 --- a/setup.py +++ b/setup.py @@ -244,6 +244,6 @@ def run_on(target): "test": list(requirements_test), "easy": list(requirements_easy), "all": list(requirements_all), - "recommends": list(requirments_recommends), + "recommends": list(requirements_recommends), }, ) From 8f00bb036b80ef48b5d36c17fd030654454714e7 Mon Sep 17 00:00:00 2001 From: Michael Dawson-Haggerty Date: Wed, 23 Aug 2023 17:01:56 -0400 Subject: [PATCH 11/14] docker uid and copy module --- Dockerfile | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/Dockerfile b/Dockerfile index 61c89fe84..db4f21b4e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,8 +20,8 @@ FROM base AS build RUN trimesh-setup --install build # copy in essential files -COPY --chown=user:user trimesh/ /home/user/trimesh -COPY --chown=user:user setup.py /home/user/ +COPY --chown=499 trimesh/ /home/user/trimesh +COPY --chown=499 setup.py /home/user/ # switch to non-root user USER user @@ -42,7 +42,7 @@ USER user WORKDIR /home/user # just copy over the results of the compiled packages -COPY --chown=user:user --from=build /home/user/.local /home/user/.local +COPY --chown=499 --from=build /home/user/.local /home/user/.local # Set environment variables for software rendering. ENV XVFB_WHD="1920x1080x24"\ @@ -55,19 +55,21 @@ ENV XVFB_WHD="1920x1080x24"\ FROM output AS tests # copy in tests and supporting files -COPY --chown=user:user tests ./tests/ -COPY --chown=user:user models ./models/ -COPY --chown=user:user setup.py . +COPY --chown=499 tests ./tests/ +COPY --chown=499 trimesh ./trimesh/ +COPY --chown=499 models ./models/ +COPY --chown=499 setup.py . +COPY --chown=499 pyproject.toml . # codecov looks at the git history -COPY --chown=user:user ./.git ./.git/ +COPY --chown=499 ./.git ./.git/ USER root RUN trimesh-setup --install=test,gltf_validator,llvmpipe,binvox USER user # install things like pytest -RUN pip install .[all,easy,recommends,tests] +RUN pip install -e .[all,easy,recommends,tests] # run pytest wrapped with xvfb for simple viewer tests RUN xvfb-run pytest --cov=trimesh \ @@ -90,11 +92,11 @@ USER root RUN trimesh-setup --install docs USER user -COPY --chown=user:user README.md . -COPY --chown=user:user docs ./docs/ -COPY --chown=user:user examples ./examples/ -COPY --chown=user:user models ./models/ -COPY --chown=user:user trimesh ./trimesh/ +COPY --chown=499 README.md . +COPY --chown=499 docs ./docs/ +COPY --chown=499 examples ./examples/ +COPY --chown=499 models ./models/ +COPY --chown=499 trimesh ./trimesh/ WORKDIR /home/user/docs RUN make From 8ac93b47dada59af55f5f0c88b98e4b0ad7b5b3b Mon Sep 17 00:00:00 2001 From: Michael Dawson-Haggerty Date: Wed, 23 Aug 2023 17:30:59 -0400 Subject: [PATCH 12/14] typo --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index db4f21b4e..7966dd793 100644 --- a/Dockerfile +++ b/Dockerfile @@ -69,7 +69,7 @@ RUN trimesh-setup --install=test,gltf_validator,llvmpipe,binvox USER user # install things like pytest -RUN pip install -e .[all,easy,recommends,tests] +RUN pip install -e .[all,easy,recommends,test] # run pytest wrapped with xvfb for simple viewer tests RUN xvfb-run pytest --cov=trimesh \ From 655f227c9f166b8d6fcd870786626a79a33c9040 Mon Sep 17 00:00:00 2001 From: Michael Dawson-Haggerty Date: Wed, 23 Aug 2023 17:33:08 -0400 Subject: [PATCH 13/14] version bump --- trimesh/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/trimesh/version.py b/trimesh/version.py index b21b2f52f..3b176b3f2 100644 --- a/trimesh/version.py +++ b/trimesh/version.py @@ -1,4 +1,4 @@ -__version__ = '3.23.3' +__version__ = '3.23.4' if __name__ == '__main__': # print version if run directly i.e. in a CI script From 475f921e3497b5c360c56ff269b29c7535147f62 Mon Sep 17 00:00:00 2001 From: Michael Dawson-Haggerty Date: Thu, 24 Aug 2023 13:28:12 -0400 Subject: [PATCH 14/14] version bump --- trimesh/creation.py | 5 +++-- trimesh/version.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/trimesh/creation.py b/trimesh/creation.py index 4c7657680..4d7897912 100644 --- a/trimesh/creation.py +++ b/trimesh/creation.py @@ -82,11 +82,11 @@ def revolve(linestring, if angle is None: # default to closing the revolution - angle = np.pi * 2 + angle = np.pi * 2.0 closed = True else: # check passed angle value - closed = angle >= ((np.pi * 2) - 1e-8) + closed = util.isclose(angle, np.pi * 2, atol=1e-10) if sections is None: # default to 32 sections for a full revolution @@ -166,6 +166,7 @@ def revolve(linestring, # if revolved curve starts and ends with zero radius # it should really be a valid volume, unless the sign # reversed on the input linestring + assert closed assert mesh.is_volume assert mesh.body_count == 1 diff --git a/trimesh/version.py b/trimesh/version.py index 3b176b3f2..5103cdf81 100644 --- a/trimesh/version.py +++ b/trimesh/version.py @@ -1,4 +1,4 @@ -__version__ = '3.23.4' +__version__ = '3.23.5' if __name__ == '__main__': # print version if run directly i.e. in a CI script