Skip to content

Commit ff1983e

Browse files
authored
Merge pull request #2209 from mikedh/feat/sample_original
Feature: Include Original
2 parents 2e2c787 + 088f46e commit ff1983e

3 files changed

Lines changed: 87 additions & 14 deletions

File tree

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ requires = ["setuptools >= 61.0", "wheel"]
55
[project]
66
name = "trimesh"
77
requires-python = ">=3.7"
8-
version = "4.3.0"
8+
version = "4.3.1"
99
authors = [{name = "Michael Dawson-Haggerty", email = "mikedh@kerfed.com"}]
1010
license = {file = "LICENSE.md"}
1111
description = "Import, export, process, analyze and view triangular meshes."

tests/test_path_sample.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import numpy as np
2+
3+
4+
def test_resample_original():
5+
# check to see if `include_original` works
6+
7+
from shapely.geometry import Polygon
8+
9+
from trimesh.path.traversal import resample_path
10+
11+
ori = np.array([[0, 0], [1, 0], [1, 1], [0, 1], [0, 0]], dtype=np.float64)
12+
13+
re = resample_path(ori, step=0.25, include_original=True)
14+
15+
a, b = Polygon(ori), Polygon(re)
16+
assert np.isclose(a.area, b.area)
17+
assert np.isclose(a.length, b.length)
18+
19+
20+
if __name__ == "__main__":
21+
test_resample_original()

trimesh/path/traversal.py

Lines changed: 65 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import numpy as np
44

55
from .. import constants, grouping, util
6+
from ..typed import ArrayLike, Integer, NDArray, Number, Optional
67
from .util import is_ccw
78

89
try:
@@ -246,7 +247,7 @@ def discretize_path(entities, vertices, path, scale=1.0):
246247

247248

248249
class PathSample:
249-
def __init__(self, points):
250+
def __init__(self, points: ArrayLike):
250251
# make sure input array is numpy
251252
self._points = np.array(points)
252253
# find the direction of each segment
@@ -263,7 +264,28 @@ def __init__(self, points):
263264
# note that this is sorted
264265
self._cum_norm = np.cumsum(self._norms)
265266

266-
def sample(self, distances):
267+
def sample(
268+
self, distances: ArrayLike, include_original: bool = False
269+
) -> NDArray[np.float64]:
270+
"""
271+
Return points at the distances along the path requested.
272+
273+
Parameters
274+
----------
275+
distances
276+
Distances along the path to sample at.
277+
include_original
278+
Include the original vertices even if they are not
279+
specified in `distance`. Useful as this will return
280+
a result with identical area and length, however
281+
indexes of `distance` will not correspond with result.
282+
283+
Returns
284+
--------
285+
samples : (n, dimension)
286+
Samples requested.
287+
`n==len(distances)` if not `include_original`
288+
"""
267289
# return the indices in cum_norm that each sample would
268290
# need to be inserted at to maintain the sorted property
269291
positions = np.searchsorted(self._cum_norm, distances)
@@ -275,15 +297,35 @@ def sample(self, distances):
275297
direction = self._unit_vec[positions]
276298
# find out which vertex we're offset from
277299
origin = self._points[positions]
300+
278301
# just the parametric equation for a line
279302
resampled = origin + (direction * projection.reshape((-1, 1)))
280303

304+
if include_original:
305+
# find the insertion index of the original positions
306+
unique, index = np.unique(positions, return_index=True)
307+
# see if we already have this point
308+
ok = projection[index] > 1e-12
309+
310+
# insert the original vertices into the resampled array
311+
resampled = np.insert(resampled, index[ok], self._points[unique[ok]], axis=0)
312+
281313
return resampled
282314

283-
def truncate(self, distance):
315+
def truncate(self, distance: Number) -> NDArray[np.float64]:
284316
"""
285317
Return a truncated version of the path.
286318
Only one vertex (at the endpoint) will be added.
319+
320+
Parameters
321+
----------
322+
distance
323+
Distance along the path to truncate at.
324+
325+
Returns
326+
----------
327+
path
328+
Path clipped to `distance` requested.
287329
"""
288330
position = np.searchsorted(self._cum_norm, distance)
289331
offset = distance - self._cum_norm[position - 1]
@@ -304,7 +346,13 @@ def truncate(self, distance):
304346
return truncated
305347

306348

307-
def resample_path(points, count=None, step=None, step_round=True):
349+
def resample_path(
350+
points: ArrayLike,
351+
count: Optional[Integer] = None,
352+
step: Optional[Number] = None,
353+
step_round: bool = True,
354+
include_original: bool = False,
355+
) -> NDArray[np.float64]:
308356
"""
309357
Given a path along (n,d) points, resample them such that the
310358
distance traversed along the path is constant in between each
@@ -320,18 +368,21 @@ def resample_path(points, count=None, step=None, step_round=True):
320368
Parameters
321369
----------
322370
points: (n, d) float
323-
Points in space
371+
Points in space
324372
count : int,
325-
Number of points to sample evenly (aka np.linspace)
373+
Number of points to sample evenly (aka np.linspace)
326374
step : float
327-
Distance each step should take along the path (aka np.arange)
375+
Distance each step should take along the path (aka np.arange)
376+
step_round
377+
Alter `step` to the nearest integer division of overall length.
378+
include_original
379+
Include the exact original points in the output.
328380
329381
Returns
330382
----------
331383
resampled : (j,d) float
332384
Points on the path
333385
"""
334-
335386
points = np.array(points, dtype=np.float64)
336387
# generate samples along the perimeter from kwarg count or step
337388
if (count is not None) and (step is not None):
@@ -351,12 +402,13 @@ def resample_path(points, count=None, step=None, step_round=True):
351402
elif step is not None:
352403
samples = np.arange(0, sampler.length, step)
353404

354-
resampled = sampler.sample(samples)
405+
resampled = sampler.sample(samples, include_original=include_original)
355406

356-
check = util.row_norm(points[[0, -1]] - resampled[[0, -1]])
357-
assert check[0] < constants.tol_path.merge
358-
if count is not None:
359-
assert check[1] < constants.tol_path.merge
407+
if constants.tol.strict:
408+
check = util.row_norm(points[[0, -1]] - resampled[[0, -1]])
409+
assert check[0] < constants.tol_path.merge
410+
if count is not None:
411+
assert check[1] < constants.tol_path.merge
360412

361413
return resampled
362414

0 commit comments

Comments
 (0)