Skip to content

Commit

Permalink
Merge pull request #2206 from mikedh/feat/sweepvec
Browse files Browse the repository at this point in the history
Vectorize Sweep
  • Loading branch information
mikedh committed Apr 8, 2024
2 parents 9300a2b + 9084e12 commit 45565d9
Show file tree
Hide file tree
Showing 13 changed files with 407 additions and 311 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Expand Up @@ -63,7 +63,7 @@ COPY --chown=499 pyproject.toml .
COPY --chown=499 ./.git ./.git/

USER root
RUN trimesh-setup --install=test,gltf_validator,llvmpipe,binvox
RUN trimesh-setup --install=test,gmsh,gltf_validator,llvmpipe,binvox
USER user

# install things like pytest
Expand Down
3 changes: 2 additions & 1 deletion docker/trimesh-setup
Expand Up @@ -46,7 +46,8 @@ config_json = """
"openscad",
"curl",
"git"
]
],
"gmsh": ["libxft2", "libxinerama-dev", "libxcursor1","libgomp1"]
},
"fetch": {
"gltf_validator": {
Expand Down
1 change: 1 addition & 0 deletions tests/data/random_tree.json
@@ -0,0 +1 @@
[[0, 710], [0, 906], [0, 758], [1, 205], [1, 82], [2, 750], [3, 323], [3, 209], [7, 999], [8, 394], [9, 298], [9, 224], [10, 850], [12, 619], [13, 179], [14, 178], [14, 218], [15, 740], [15, 307], [16, 248], [19, 788], [20, 817], [20, 155], [22, 512], [22, 773], [23, 534], [27, 75], [27, 994], [28, 718], [30, 708], [30, 353], [31, 810], [32, 683], [33, 191], [33, 20], [34, 955], [36, 635], [36, 329], [36, 964], [36, 421], [37, 511], [38, 876], [39, 347], [39, 712], [39, 193], [40, 552], [40, 937], [40, 956], [41, 18], [41, 789], [41, 133], [42, 843], [43, 19], [43, 803], [43, 918], [45, 979], [46, 467], [46, 625], [47, 692], [48, 469], [49, 854], [51, 720], [56, 342], [59, 576], [61, 902], [62, 526], [62, 184], [62, 226], [63, 171], [63, 451], [63, 974], [64, 233], [64, 995], [65, 456], [65, 117], [67, 89], [67, 481], [67, 950], [67, 340], [70, 328], [71, 546], [72, 857], [72, 360], [72, 610], [73, 547], [75, 110], [75, 483], [77, 647], [78, 614], [80, 620], [80, 258], [82, 252], [82, 352], [82, 665], [83, 771], [83, 734], [84, 422], [85, 648], [85, 903], [86, 827], [86, 947], [86, 958], [89, 654], [91, 70], [92, 587], [93, 251], [93, 299], [93, 501], [95, 180], [95, 836], [95, 235], [97, 81], [100, 754], [100, 8], [101, 108], [101, 111], [101, 64], [102, 766], [103, 129], [103, 453], [103, 543], [103, 554], [103, 48], [107, 924], [107, 154], [107, 331], [107, 425], [111, 264], [111, 424], [113, 502], [116, 149], [117, 355], [117, 851], [117, 908], [118, 426], [118, 504], [118, 779], [118, 490], [119, 442], [120, 589], [121, 756], [121, 916], [122, 496], [124, 829], [124, 998], [126, 522], [126, 765], [127, 273], [127, 484], [127, 351], [127, 666], [130, 957], [130, 869], [132, 561], [133, 335], [133, 738], [133, 230], [135, 649], [135, 632], [136, 782], [138, 287], [141, 832], [142, 69], [143, 76], [144, 978], [145, 246], [146, 212], [149, 460], [149, 638], [149, 436], [150, 105], [151, 499], [151, 537], [152, 819], [153, 339], [153, 847], [154, 141], [154, 846], [155, 984], [155, 988], [157, 256], [158, 787], [159, 878], [159, 943], [159, 751], [161, 701], [162, 584], [163, 670], [164, 882], [165, 586], [167, 276], [167, 63], [169, 47], [170, 883], [170, 904], [173, 570], [174, 448], [179, 592], [182, 550], [182, 10], [183, 315], [184, 446], [186, 705], [186, 309], [188, 297], [189, 521], [191, 320], [192, 728], [193, 188], [193, 488], [193, 804], [194, 439], [195, 234], [195, 755], [195, 774], [196, 608], [196, 907], [199, 513], [199, 354], [202, 890], [202, 925], [203, 760], [204, 400], [206, 693], [206, 934], [208, 283], [208, 573], [209, 143], [209, 674], [210, 790], [211, 772], [212, 823], [212, 262], [212, 3], [215, 524], [218, 745], [220, 385], [221, 816], [221, 896], [222, 901], [223, 66], [224, 465], [224, 936], [225, 443], [225, 377], [226, 71], [226, 767], [227, 240], [228, 673], [230, 951], [235, 221], [236, 838], [239, 600], [241, 383], [244, 227], [244, 975], [245, 138], [245, 163], [245, 748], [247, 314], [248, 194], [249, 219], [256, 90], [256, 588], [256, 613], [258, 739], [260, 583], [261, 497], [262, 781], [262, 407], [263, 895], [265, 21], [265, 699], [265, 793], [266, 168], [266, 326], [266, 545], [266, 204], [267, 225], [268, 880], [276, 42], [277, 440], [277, 833], [281, 405], [281, 22], [282, 378], [285, 158], [286, 762], [287, 557], [288, 74], [293, 244], [294, 292], [294, 634], [296, 597], [296, 892], [296, 211], [297, 247], [298, 271], [298, 601], [300, 132], [300, 818], [301, 596], [303, 636], [306, 336], [307, 41], [308, 269], [308, 549], [309, 761], [309, 858], [309, 38], [309, 519], [310, 52], [310, 844], [312, 968], [315, 49], [317, 106], [317, 657], [318, 605], [319, 333], [322, 571], [323, 137], [323, 744], [324, 311], [325, 920], [327, 253], [329, 837], [330, 909], [331, 645], [332, 84], [332, 985], [333, 229], [333, 714], [336, 855], [338, 123], [338, 459], [340, 275], [340, 174], [340, 260], [341, 217], [341, 277], [342, 668], [342, 849], [343, 437], [343, 621], [343, 877], [345, 341], [345, 485], [345, 991], [346, 840], [348, 9], [350, 165], [351, 831], [353, 356], [354, 664], [355, 660], [356, 182], [358, 308], [360, 631], [360, 80], [361, 987], [362, 131], [365, 420], [365, 606], [365, 466], [366, 55], [367, 266], [367, 637], [367, 196], [367, 124], [369, 848], [370, 387], [374, 72], [375, 797], [376, 835], [377, 85], [379, 93], [380, 444], [380, 952], [380, 455], [381, 445], [384, 828], [384, 578], [387, 458], [387, 926], [388, 862], [391, 517], [392, 973], [392, 199], [393, 417], [394, 5], [394, 305], [394, 62], [395, 477], [396, 933], [397, 607], [397, 317], [397, 970], [400, 368], [400, 122], [400, 558], [401, 800], [403, 100], [404, 369], [406, 769], [407, 509], [408, 187], [411, 32], [411, 741], [412, 859], [414, 29], [415, 577], [417, 791], [419, 594], [421, 361], [424, 661], [424, 681], [424, 306], [425, 727], [425, 101], [425, 792], [427, 743], [427, 962], [427, 492], [427, 30], [428, 688], [429, 399], [430, 17], [430, 536], [432, 945], [433, 222], [433, 696], [435, 539], [436, 449], [436, 875], [436, 764], [436, 913], [441, 170], [441, 702], [444, 134], [445, 655], [446, 358], [452, 591], [453, 156], [455, 295], [455, 401], [456, 169], [457, 161], [457, 441], [458, 250], [458, 652], [459, 894], [459, 864], [461, 408], [462, 538], [463, 775], [466, 195], [468, 12], [469, 820], [469, 202], [469, 395], [471, 671], [477, 626], [478, 515], [478, 719], [478, 717], [481, 564], [481, 826], [482, 13], [482, 103], [483, 125], [485, 866], [487, 327], [487, 102], [488, 54], [488, 150], [489, 852], [490, 523], [490, 872], [492, 863], [492, 976], [493, 630], [493, 966], [497, 26], [497, 40], [498, 429], [498, 706], [499, 929], [500, 899], [501, 259], [502, 796], [503, 144], [504, 409], [505, 213], [505, 67], [506, 713], [506, 815], [507, 533], [508, 996], [509, 874], [509, 121], [510, 853], [510, 932], [514, 542], [514, 798], [515, 551], [516, 68], [517, 953], [519, 430], [519, 312], [520, 255], [520, 603], [523, 25], [528, 948], [530, 824], [531, 569], [531, 993], [532, 967], [533, 166], [533, 669], [533, 898], [534, 282], [535, 856], [536, 715], [537, 373], [537, 942], [538, 915], [540, 704], [541, 691], [542, 350], [542, 954], [545, 96], [547, 476], [548, 296], [550, 822], [553, 280], [553, 364], [555, 206], [556, 249], [557, 23], [557, 46], [559, 370], [560, 274], [560, 290], [561, 176], [561, 192], [561, 404], [562, 716], [564, 270], [565, 148], [569, 403], [571, 265], [573, 128], [574, 157], [574, 707], [574, 860], [577, 930], [578, 641], [579, 432], [581, 238], [581, 679], [581, 687], [581, 799], [582, 595], [582, 682], [583, 568], [583, 972], [585, 582], [587, 472], [587, 34], [589, 313], [589, 495], [593, 402], [596, 388], [597, 57], [597, 153], [598, 900], [599, 267], [600, 622], [601, 291], [601, 431], [601, 830], [603, 389], [604, 639], [608, 1], [610, 302], [610, 553], [610, 928], [610, 914], [611, 120], [612, 961], [612, 33], [614, 776], [616, 99], [616, 479], [616, 982], [617, 289], [617, 263], [618, 709], [620, 491], [620, 37], [622, 757], [622, 812], [622, 397], [624, 633], [624, 555], [624, 61], [625, 139], [626, 207], [626, 541], [626, 36], [627, 321], [627, 423], [629, 478], [632, 612], [633, 87], [635, 435], [635, 646], [635, 842], [636, 730], [638, 921], [640, 201], [641, 886], [642, 406], [642, 910], [645, 434], [645, 135], [647, 629], [647, 808], [648, 643], [648, 733], [649, 473], [650, 486], [650, 510], [652, 228], [653, 415], [654, 887], [655, 140], [655, 821], [656, 413], [656, 917], [656, 167], [659, 729], [660, 785], [662, 73], [664, 318], [664, 759], [664, 562], [666, 663], [666, 784], [666, 345], [669, 136], [671, 814], [671, 889], [672, 324], [672, 971], [673, 611], [674, 689], [676, 410], [678, 690], [681, 452], [682, 532], [684, 200], [686, 650], [688, 572], [689, 294], [689, 677], [689, 83], [689, 749], [691, 753], [692, 580], [693, 770], [694, 742], [694, 92], [695, 91], [695, 386], [695, 529], [695, 731], [695, 507], [696, 480], [696, 653], [697, 593], [697, 210], [697, 980], [698, 992], [699, 366], [700, 115], [702, 427], [703, 752], [705, 286], [707, 615], [708, 367], [714, 464], [714, 173], [715, 667], [715, 983], [715, 514], [716, 359], [716, 556], [716, 118], [716, 989], [717, 414], [717, 624], [718, 685], [718, 700], [718, 811], [718, 873], [720, 231], [722, 97], [722, 372], [722, 119], [722, 348], [723, 272], [723, 735], [726, 678], [727, 190], [727, 675], [729, 506], [730, 698], [731, 598], [732, 651], [734, 14], [734, 616], [735, 189], [735, 376], [735, 301], [736, 181], [739, 332], [742, 737], [744, 215], [746, 535], [746, 293], [747, 109], [747, 223], [748, 489], [749, 16], [749, 563], [749, 960], [751, 977], [752, 343], [756, 623], [758, 186], [759, 468], [763, 938], [764, 939], [764, 78], [765, 7], [766, 658], [767, 806], [767, 891], [770, 462], [772, 747], [772, 927], [773, 79], [773, 618], [774, 380], [775, 334], [776, 6], [776, 530], [777, 15], [780, 627], [785, 575], [790, 428], [791, 310], [792, 159], [794, 95], [795, 599], [795, 802], [796, 482], [796, 795], [798, 346], [798, 642], [800, 39], [801, 454], [802, 44], [802, 617], [802, 392], [803, 198], [804, 881], [807, 59], [807, 500], [807, 303], [808, 834], [808, 365], [810, 865], [810, 457], [811, 778], [812, 418], [813, 447], [813, 694], [816, 919], [817, 304], [817, 861], [817, 885], [818, 88], [818, 288], [818, 396], [819, 525], [819, 463], [819, 946], [819, 164], [822, 242], [822, 145], [824, 172], [824, 813], [825, 384], [826, 723], [827, 888], [828, 51], [829, 98], [829, 393], [829, 986], [829, 697], [831, 382], [831, 726], [831, 870], [834, 278], [835, 450], [836, 470], [836, 783], [837, 325], [838, 374], [839, 794], [841, 316], [841, 65], [842, 721], [842, 2], [844, 197], [844, 965], [845, 390], [845, 963], [846, 559], [847, 214], [848, 662], [849, 50], [849, 363], [849, 746], [850, 494], [850, 997], [853, 379], [853, 330], [855, 544], [855, 412], [857, 371], [857, 609], [857, 319], [857, 736], [858, 565], [860, 185], [861, 867], [862, 349], [862, 944], [864, 4], [864, 777], [865, 438], [865, 162], [865, 493], [866, 805], [869, 241], [869, 801], [869, 807], [870, 114], [871, 285], [871, 239], [873, 912], [874, 644], [874, 487], [875, 461], [877, 183], [880, 505], [881, 531], [882, 780], [882, 338], [884, 841], [884, 911], [884, 941], [885, 871], [886, 160], [888, 216], [889, 528], [889, 152], [891, 232], [892, 684], [892, 786], [892, 381], [893, 763], [894, 203], [895, 602], [895, 680], [895, 686], [895, 151], [896, 254], [896, 676], [896, 724], [896, 585], [897, 337], [898, 433], [902, 113], [905, 711], [905, 839], [906, 540], [906, 672], [909, 931], [910, 905], [911, 11], [911, 503], [913, 58], [913, 640], [913, 579], [914, 146], [915, 703], [916, 220], [919, 357], [920, 112], [920, 940], [921, 419], [922, 391], [922, 548], [922, 261], [922, 126], [923, 104], [923, 566], [923, 45], [924, 659], [924, 28], [926, 560], [927, 959], [929, 60], [929, 498], [930, 574], [931, 94], [931, 474], [931, 581], [933, 981], [934, 142], [935, 281], [937, 177], [937, 949], [938, 416], [938, 300], [939, 237], [939, 520], [939, 695], [940, 35], [940, 175], [940, 567], [941, 879], [941, 508], [942, 897], [943, 590], [944, 279], [944, 768], [945, 344], [945, 362], [945, 86], [953, 935], [955, 257], [956, 245], [956, 990], [956, 923], [957, 656], [958, 475], [958, 107], [959, 527], [960, 375], [960, 922], [964, 56], [965, 268], [967, 27], [968, 116], [969, 604], [969, 322], [970, 43], [972, 398], [972, 411], [976, 628], [977, 31], [979, 77], [980, 893], [981, 130], [984, 516], [984, 809], [985, 722], [985, 884], [986, 284], [986, 732], [988, 24], [989, 147], [989, 208], [990, 243], [991, 53], [991, 471], [992, 127], [993, 518], [995, 845], [995, 969], [997, 725], [997, 825], [997, 236], [999, 868]]
4 changes: 1 addition & 3 deletions tests/test_creation.py
Expand Up @@ -170,9 +170,7 @@ def test_path_sweep(self):

# Extrude
for engine in self.engines:
mesh = g.trimesh.creation.sweep_polygon(
poly, path_closed, closed=True, engine=engine
)
mesh = g.trimesh.creation.sweep_polygon(poly, path_closed, engine=engine)
assert mesh.is_volume

def test_annulus(self):
Expand Down
8 changes: 4 additions & 4 deletions tests/test_scenegraph.py
Expand Up @@ -206,13 +206,13 @@ def test_shortest_path(self):
return

tf = g.trimesh.transformations
# start with creating a random tree
edgelist = {}
tree = g.nx.random_tree(n=1000, seed=0, create_using=g.nx.DiGraph)
edges = list(tree.edges)
# start with a known good random tree
edges = [tuple(row) for row in g.data["random_tree"]]
tree = g.nx.from_edgelist(edges, create_using=g.nx.DiGraph)

r_choices = g.random((len(edges), 2))
r_matrices = g.random_transforms(len(edges))
edgelist = {}
for e, r_choice, r_mat in zip(edges, r_choices, r_matrices):
data = {}
if r_choice[0] > 0.5:
Expand Down
1 change: 1 addition & 0 deletions tests/test_step.py
Expand Up @@ -18,6 +18,7 @@ def test_basic(self):
b = g.get_mesh("featuretype.STEP")
assert len(b.geometry) == 1
m = next(iter(b.geometry.values()))
m.merge_vertices(merge_tex=True, merge_norm=True)
assert m.is_watertight


Expand Down
130 changes: 130 additions & 0 deletions tests/test_sweep.py
@@ -0,0 +1,130 @@
import numpy as np
from pyinstrument import Profiler
from shapely.geometry import Point, Polygon

import trimesh
from trimesh.creation import sweep_polygon

trimesh.tol.strict = True


def arc_2d(final_angle, d=1, start_angle=0, splits=20):
xs = np.linspace(start_angle, final_angle, splits)
return np.stack((d / 2 * np.cos(xs), d / 2 * np.sin(xs)), axis=1)


def test_simple_closed(h1=1, w=2, r=4.4):
# a simple closed path
square = Polygon([(-0.01, -h1 / 2), (w, -h1 / 2), (w, +h1 / 2), (-0.01, +h1 / 2)])
circle = Point([0, 0]).buffer(1.0)

# a simple square of side `r`
path = [[0, 0, 0], [r, 0, 0], [r, r, 0], [0, r, 0], [0, 0, 0]]

a = sweep_polygon(square, path)
assert a.is_volume

aa = sweep_polygon(square, path, connect=False)
assert aa.is_volume

# should have the same bounds but no longer a volume
aaa = sweep_polygon(square, path, connect=False, cap=False)
assert np.allclose(aa.bounds, aaa.bounds)
assert not aaa.is_volume

b = sweep_polygon(circle, path)
assert b.is_volume


def test_simple_extrude(height=10):
# make sure sweep behaves okay on a simple single segment path
# this should be identical to the extrude operation
circle = Point([0, 0]).buffer(1.0)

# a simple extrusion
path = [[0, 0, 0], [0, 0, height]]
# will be running asserts inside function
b = sweep_polygon(circle, path)

# should be a straight extrude along Z
expected = np.append(np.reshape(circle.bounds, (2, 2)).ptp(axis=0), height)
assert np.allclose(expected, b.extents)

# should be well constructed
assert b.is_volume
# volume should correspond to expected cylinder area
assert np.isclose(b.volume, circle.area * height)


def test_simple_open():
circle = Point([0, 0]).buffer(1.0)
theta = np.linspace(0.0, np.pi, 100)
path = np.column_stack([np.cos(theta), np.sin(theta), np.zeros(len(theta))]) * 10

a = sweep_polygon(circle, path)
assert a.is_volume


def test_spline_3D():
circle = Point([0, 0]).buffer(1.0)
# make a 3D noodle using spline smoothing
path = trimesh.path.simplify.resample_spline(
[[0, 0, 0], [4, 4, 0], [5, 5, 0], [10, 0, 10], [0, 20, 0]], smooth=0.25, count=100
)

a = sweep_polygon(circle, path)
assert a.is_volume
assert a.body_count == 1


def test_screw():
h = 200
d = 8
h1 = 1
w = 2
d = d - w
h2 = h1

lead = 2
splits_per_turn = 4 * 20
polygon = np.array([(-0.01, -h1 / 2), (w, -h2 / 2), (w, +h2 / 2), (-0.01, +h1 / 2)])
h_ = h + h1
n = int(np.ceil(splits_per_turn * h_ / lead))
turns = n / splits_per_turn
xys = arc_2d(2 * np.pi * turns, d, splits=n)
zs = np.linspace(0, h_, n)[:, None]
spin_path = np.concatenate((xys, zs), axis=1)

with Profiler() as P:
x = sweep_polygon(Polygon(polygon), spin_path)
P.print()

assert x.is_volume
# should have produced a result corresponding to the input dimensions
assert np.allclose(x.extents[:2], d + w * 2, atol=0.01)
assert np.allclose(x.extents[2], h + h1 * 2, atol=0.01)

# check without capping: should be the same size just without caps
o = sweep_polygon(Polygon(polygon), spin_path, cap=False, connect=False)
assert np.allclose(x.bounds, o.bounds)
assert not o.is_volume

# try spinning along an angle
sweep_polygon(
Polygon(polygon), spin_path, angles=np.linspace(0.0, np.pi / 2, len(spin_path))
)

circle = Point([0, 0]).buffer(1.0)
theta = np.linspace(0.0, np.pi, 100)
path = np.column_stack([np.cos(theta), np.sin(theta), np.zeros(len(theta))]) * 10

a = sweep_polygon(circle, path)
assert a.is_volume


if __name__ == "__main__":
test_simple_closed()
test_simple_extrude()
test_simple_open()
test_screw()
test_spline_3D()
25 changes: 25 additions & 0 deletions tests/test_transformations.py
Expand Up @@ -227,6 +227,31 @@ def test_symbolic_euler(self):
# they should be the same matrix
assert g.np.allclose(s, n)

def test_symbolic_translate(self):
# some of the functions have been modified to support `sympy.Symbol`
# values which is useful for calculating final rotations symbolically
try:
import sympy as sp
except BaseException:
return

translate = g.trimesh.transformations.translation_matrix

x, y, z = sp.symbols("x y z")

m = translate([x, y, z])

for T in g.random((100, 3)):
# get the euler matrix evaluated from the symbolic matrix
s = g.np.array(
m.subs({x: T[0], y: T[1], z: T[2]}).evalf(), dtype=g.np.float64
)
# get it from a numeric scalar
n = translate(T)

# they should be the same matrix
assert g.np.allclose(s, n)


if __name__ == "__main__":
g.trimesh.util.attach_to_log()
Expand Down

0 comments on commit 45565d9

Please sign in to comment.