loadpath 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- loadpath/README.md +294 -0
- loadpath/SKILL.md +584 -0
- loadpath/__init__.py +17 -0
- loadpath/__main__.py +1202 -0
- loadpath/am.py +316 -0
- loadpath/amgfast.py +326 -0
- loadpath/assembly.py +985 -0
- loadpath/benchmarks/__init__.py +0 -0
- loadpath/benchmarks/beams.py +266 -0
- loadpath/benchmarks/cases/ge-bracket.json +380 -0
- loadpath/benchmarks/evaluate.py +172 -0
- loadpath/benchmarks/mechanisms.py +416 -0
- loadpath/benchmarks/problems.py +140 -0
- loadpath/benchmarks/reference/bridge-3d.top3d.npz +0 -0
- loadpath/benchmarks/reference/cantilever-3d.top3d.npz +0 -0
- loadpath/benchmarks/reference/lbracket-2d.top88-ft2.npz +0 -0
- loadpath/benchmarks/reference/mbb-2d.top88-ft1.npz +0 -0
- loadpath/benchmarks/reference/mbb-2d.top88-ft2.npz +0 -0
- loadpath/benchmarks/reference.py +138 -0
- loadpath/benchmarks/run.py +301 -0
- loadpath/brep.py +1470 -0
- loadpath/bucklingopt.py +300 -0
- loadpath/buildsim.py +1490 -0
- loadpath/cadimport.py +904 -0
- loadpath/calibration.py +885 -0
- loadpath/cavities.py +211 -0
- loadpath/checks/__init__.py +0 -0
- loadpath/checks/dfm.py +354 -0
- loadpath/checks/exchanger.py +7 -0
- loadpath/checks/fea.py +212 -0
- loadpath/checks/fit.py +475 -0
- loadpath/checks/networks.py +260 -0
- loadpath/checks/opening.py +67 -0
- loadpath/checks/sdf_kit.py +7 -0
- loadpath/conjugate.py +267 -0
- loadpath/constrained.py +329 -0
- loadpath/contact.py +691 -0
- loadpath/coopt.py +342 -0
- loadpath/cores.py +163 -0
- loadpath/crease.py +1138 -0
- loadpath/critic.py +212 -0
- loadpath/crosscheck.py +1107 -0
- loadpath/cutcell.py +510 -0
- loadpath/data/lattice_buckling.json +439 -0
- loadpath/data/lattice_tensors.json +27449 -0
- loadpath/data/published_experiments.json +71 -0
- loadpath/decimate.py +177 -0
- loadpath/delivery.py +190 -0
- loadpath/directions.py +262 -0
- loadpath/domain.py +630 -0
- loadpath/drains.py +615 -0
- loadpath/durability.py +413 -0
- loadpath/editor.html +3135 -0
- loadpath/editor.py +610 -0
- loadpath/escape.py +354 -0
- loadpath/evals/__init__.py +0 -0
- loadpath/evals/baselines/coarse.json +1543 -0
- loadpath/evals/baselines/full.json +1497 -0
- loadpath/evals/brief_kinds/__init__.py +1 -0
- loadpath/evals/brief_kinds/heatx.py +387 -0
- loadpath/evals/brief_kinds/lattice.py +571 -0
- loadpath/evals/brief_kinds/multimat.py +593 -0
- loadpath/evals/briefs/assets/bracket.step +1201 -0
- loadpath/evals/briefs/assets/desk_arm.urdf +22 -0
- loadpath/evals/briefs/assets/elbow-gearbox.stl +0 -0
- loadpath/evals/briefs/assets/motor-body.stl +0 -0
- loadpath/evals/briefs/assets/sketch.png +0 -0
- loadpath/evals/briefs/base-adapter-fdm.json +492 -0
- loadpath/evals/briefs/bell-crank-lpbf.json +180 -0
- loadpath/evals/briefs/belt-tensioner-fdm.json +151 -0
- loadpath/evals/briefs/camera-boom-mjf.json +339 -0
- loadpath/evals/briefs/camera-mount-pla.json +203 -0
- loadpath/evals/briefs/coolant-isolator-petg.json +100 -0
- loadpath/evals/briefs/elbow-bearing-bracket-sls.json +300 -0
- loadpath/evals/briefs/elbow-gearbox-forearm-mjf.json +558 -0
- loadpath/evals/briefs/elbow-link-fatigue-fdm.json +251 -0
- loadpath/evals/briefs/elbow-stop-pad-tpu.json +39 -0
- loadpath/evals/briefs/forearm-fdm.json +146 -0
- loadpath/evals/briefs/forearm-servo-link-fdm.json +327 -0
- loadpath/evals/briefs/grip-pad-finger-mm.json +354 -0
- loadpath/evals/briefs/gripper-bracket-creep-fdm.json +340 -0
- loadpath/evals/briefs/gripper-finger-fdm.json +155 -0
- loadpath/evals/briefs/gripper-jaw-fatigue-mjf.json +328 -0
- loadpath/evals/briefs/gripper-palm-fdm.json +715 -0
- loadpath/evals/briefs/hot-stepper-mount-asa-fdm.json +471 -0
- loadpath/evals/briefs/hpu-oil-cooler-lpbf.json +121 -0
- loadpath/evals/briefs/isolated-camera-mount-mm.json +675 -0
- loadpath/evals/briefs/kart-charge-cooler-lpbf.json +93 -0
- loadpath/evals/briefs/lighten-step-bracket.json +220 -0
- loadpath/evals/briefs/nema17-mount-stl.json +429 -0
- loadpath/evals/briefs/pillow-block-sls.json +220 -0
- loadpath/evals/briefs/polyjet-fingertip-mm.json +305 -0
- loadpath/evals/briefs/rod-hook-pla.json +154 -0
- loadpath/evals/briefs/sensor-arm-resin.json +150 -0
- loadpath/evals/briefs/shelf-bracket-pla.json +158 -0
- loadpath/evals/briefs/shoulder-riser-mjf.json +487 -0
- loadpath/evals/briefs/sketch-clevis.json +229 -0
- loadpath/evals/briefs/spring-bracket-pacf.json +294 -0
- loadpath/evals/briefs/tool-flange-ti64.json +280 -0
- loadpath/evals/briefs/tray-arm-lattice-fdm.json +260 -0
- loadpath/evals/briefs/turret-fdm.json +281 -0
- loadpath/evals/briefs/upper-arm-sls.json +179 -0
- loadpath/evals/briefs/urdf-forearm.json +233 -0
- loadpath/evals/briefs/wrist-link-mjf.json +278 -0
- loadpath/evals/briefs/wrist-yoke-mjf.json +245 -0
- loadpath/evals/briefs.py +393 -0
- loadpath/evals/cases/arm-link-cast.json +157 -0
- loadpath/evals/cases/arm-link-driven.json +171 -0
- loadpath/evals/cases/arm-link-sandwich.json +315 -0
- loadpath/evals/cases/arm-link.json +111 -0
- loadpath/evals/cases/bell-crank.json +116 -0
- loadpath/evals/cases/grip-turret.json +479 -0
- loadpath/evals/cases/grip-upper-arm.json +253 -0
- loadpath/evals/cases/grip-wrist-link-mjf.json +929 -0
- loadpath/evals/cases/grip-wrist-yoke.json +831 -0
- loadpath/evals/cases/lpbf-intercooler-core.json +59 -0
- loadpath/evals/cases/mbb-beam.json +61 -0
- loadpath/evals/cases/motor-body.stl +0 -0
- loadpath/evals/cases/motor-mount.json +265 -0
- loadpath/evals/cases/printed-hook.json +77 -0
- loadpath/evals/cases/wall-bracket-fasteners.json +125 -0
- loadpath/evals/cases/wall-bracket.json +126 -0
- loadpath/evals/corpus.py +179 -0
- loadpath/evals/pareto.py +90 -0
- loadpath/evals/run.py +860 -0
- loadpath/examples/arm-link.json +25 -0
- loadpath/examples/bumpered-bracket-compression.json +225 -0
- loadpath/examples/bumpered-bracket-lattice.json +204 -0
- loadpath/examples/bumpered-bracket.json +34 -0
- loadpath/examples/gripper-finger-mm.json +185 -0
- loadpath/examples/heat-exchanger-counterflow.json +21 -0
- loadpath/examples/heat-exchanger-crossflow-tuned.json +48 -0
- loadpath/examples/heat-exchanger-crossflow.json +20 -0
- loadpath/examples/pedal-lattice.json +23 -0
- loadpath/examples/pressure-plenum.json +12 -0
- loadpath/examples/shelf-bracket.json +27 -0
- loadpath/exchanger.py +831 -0
- loadpath/fasteners.py +199 -0
- loadpath/fem.py +668 -0
- loadpath/femops.py +70 -0
- loadpath/fieldjit.py +981 -0
- loadpath/fieldops.py +119 -0
- loadpath/fields.py +3697 -0
- loadpath/fieldtorch.py +464 -0
- loadpath/flow.py +984 -0
- loadpath/flowtopo.py +300 -0
- loadpath/gpu.py +521 -0
- loadpath/heatx.py +1067 -0
- loadpath/heatx_thermal.py +432 -0
- loadpath/homogenized.py +886 -0
- loadpath/ibrep.py +552 -0
- loadpath/impact.py +693 -0
- loadpath/infill.py +376 -0
- loadpath/interface_library.py +340 -0
- loadpath/interlock.py +186 -0
- loadpath/isolator.py +149 -0
- loadpath/knn.py +204 -0
- loadpath/lattice.py +1065 -0
- loadpath/lattice3mf.py +760 -0
- loadpath/lattice_buckling.py +245 -0
- loadpath/lattice_grade.py +325 -0
- loadpath/lattice_orient.py +595 -0
- loadpath/lattice_print.py +159 -0
- loadpath/lattice_tensors.py +558 -0
- loadpath/latticestep.py +1210 -0
- loadpath/manufacture.py +446 -0
- loadpath/material_library.py +489 -0
- loadpath/materials.py +144 -0
- loadpath/mcp_server.py +786 -0
- loadpath/mechanism.py +218 -0
- loadpath/mechanism_nl.py +811 -0
- loadpath/meshops.py +452 -0
- loadpath/mg.py +853 -0
- loadpath/mma.py +214 -0
- loadpath/modal.py +210 -0
- loadpath/multimat.py +1788 -0
- loadpath/narrowband.py +418 -0
- loadpath/nlkernel.py +241 -0
- loadpath/nonlinear.py +1095 -0
- loadpath/orient.py +334 -0
- loadpath/porous.py +994 -0
- loadpath/pressure.py +171 -0
- loadpath/preview.py +335 -0
- loadpath/printcheck.py +263 -0
- loadpath/printsetup.py +207 -0
- loadpath/processcheck.py +376 -0
- loadpath/quick.py +113 -0
- loadpath/recall.py +406 -0
- loadpath/reconstruct.py +797 -0
- loadpath/render.py +436 -0
- loadpath/resolved.py +189 -0
- loadpath/schema.py +1701 -0
- loadpath/sdf_kit.py +843 -0
- loadpath/selfx.py +292 -0
- loadpath/shapes.py +584 -0
- loadpath/sharp.py +717 -0
- loadpath/simp.py +1170 -0
- loadpath/sizing.py +312 -0
- loadpath/slicer.py +321 -0
- loadpath/spec.py +1072 -0
- loadpath/step.py +806 -0
- loadpath/struts.py +639 -0
- loadpath/study.py +1937 -0
- loadpath/tasks.py +822 -0
- loadpath/thermal.py +1691 -0
- loadpath/threemf.py +487 -0
- loadpath/tune.py +437 -0
- loadpath/validation.py +145 -0
- loadpath/verify.py +1215 -0
- loadpath/viewer.html +694 -0
- loadpath/viewer.py +446 -0
- loadpath/viscoelastic.py +106 -0
- loadpath/voxelprint.py +582 -0
- loadpath/voxels.py +402 -0
- loadpath-0.1.1.dist-info/METADATA +319 -0
- loadpath-0.1.1.dist-info/RECORD +220 -0
- loadpath-0.1.1.dist-info/WHEEL +5 -0
- loadpath-0.1.1.dist-info/entry_points.txt +2 -0
- loadpath-0.1.1.dist-info/licenses/LICENSE +21 -0
- loadpath-0.1.1.dist-info/top_level.txt +1 -0
loadpath/selfx.py
ADDED
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
"""Self-intersection search over a triangle mesh, as a compiled kernel.
|
|
2
|
+
|
|
3
|
+
`crossing_faces(V, F, faces)` gives the faces among `faces` that cross
|
|
4
|
+
another face of the mesh (sharing no vertex with it), with the same exact
|
|
5
|
+
test as crease.crossing_faces' numpy path (no crossing if one triangle
|
|
6
|
+
lies wholly on one side of the other's plane; else a crossing if an edge
|
|
7
|
+
of either passes strictly through the other).
|
|
8
|
+
|
|
9
|
+
The numpy path finds candidate pairs with a k-d tree over face centroids
|
|
10
|
+
and then tests ~50 candidates per face as arrays: on a lattice of a few
|
|
11
|
+
million faces that was most of the meshing time (the relax's unfold and
|
|
12
|
+
the crease pass call it some twenty times a mesh). Here the query faces'
|
|
13
|
+
bounding boxes go into a spatial hash (cells about the size of the larger
|
|
14
|
+
faces), and every face of the mesh near them (a coarse bitmap first)
|
|
15
|
+
looks up the query faces whose boxes overlap its own and tests each pair
|
|
16
|
+
once, in parallel, with no intermediate arrays: ~35x faster on 4 cores.
|
|
17
|
+
Candidates are found exactly by box overlap, so this also finds the rare
|
|
18
|
+
crossing the centroid search's radius missed (a face much longer than
|
|
19
|
+
the 99th percentile).
|
|
20
|
+
|
|
21
|
+
Needs numba (a loadpath dependency); without it `available()` is False
|
|
22
|
+
and crease.crossing_faces keeps its numpy path. LOADPATH_SELFX=numpy
|
|
23
|
+
forces that path.
|
|
24
|
+
"""
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import os
|
|
28
|
+
|
|
29
|
+
import numpy as np
|
|
30
|
+
|
|
31
|
+
__all__ = ["available", "crossing_faces"]
|
|
32
|
+
|
|
33
|
+
_FAILED = False
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def available():
|
|
37
|
+
global _FAILED
|
|
38
|
+
if _FAILED or os.environ.get("LOADPATH_SELFX", "").lower() == "numpy":
|
|
39
|
+
return False
|
|
40
|
+
try:
|
|
41
|
+
_kernels()
|
|
42
|
+
except Exception: # noqa: BLE001 - no numba, or it can't compile here: the numpy path
|
|
43
|
+
_FAILED = True
|
|
44
|
+
return False
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
try:
|
|
49
|
+
import numba as nb
|
|
50
|
+
|
|
51
|
+
from . import meshops as _meshops # noqa: F401 - caps numba's threads (LOADPATH_THREADS)
|
|
52
|
+
except ImportError: # pragma: no cover - numba is a dependency; the numpy path without it
|
|
53
|
+
nb = None
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _kernels():
|
|
57
|
+
if nb is None:
|
|
58
|
+
raise ImportError("numba")
|
|
59
|
+
return boxes, build, query
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if nb is not None:
|
|
63
|
+
|
|
64
|
+
@nb.njit(cache=True)
|
|
65
|
+
def query_box(lo, hi, faces):
|
|
66
|
+
qlo = np.full(3, np.inf)
|
|
67
|
+
qhi = np.full(3, -np.inf)
|
|
68
|
+
for a in faces:
|
|
69
|
+
for k in range(3):
|
|
70
|
+
qlo[k] = min(qlo[k], lo[a, k])
|
|
71
|
+
qhi[k] = max(qhi[k], hi[a, k])
|
|
72
|
+
return qlo, qhi
|
|
73
|
+
|
|
74
|
+
@nb.njit(cache=True, inline="always")
|
|
75
|
+
def _hash(ix, iy, iz, mask):
|
|
76
|
+
h = (ix * np.int64(73856093)) ^ (iy * np.int64(19349663)) ^ (iz * np.int64(83492791))
|
|
77
|
+
return h & mask
|
|
78
|
+
|
|
79
|
+
@nb.njit(cache=True, parallel=True)
|
|
80
|
+
def boxes(V, F):
|
|
81
|
+
n = F.shape[0]
|
|
82
|
+
lo = np.empty((n, 3))
|
|
83
|
+
hi = np.empty((n, 3))
|
|
84
|
+
for i in nb.prange(n):
|
|
85
|
+
for k in range(3):
|
|
86
|
+
a = V[F[i, 0], k]
|
|
87
|
+
b = V[F[i, 1], k]
|
|
88
|
+
c = V[F[i, 2], k]
|
|
89
|
+
lo[i, k] = min(a, b, c)
|
|
90
|
+
hi[i, k] = max(a, b, c)
|
|
91
|
+
return lo, hi
|
|
92
|
+
|
|
93
|
+
@nb.njit(cache=True)
|
|
94
|
+
def build(lo, hi, ids, origin, h, mask):
|
|
95
|
+
"""CSR spatial hash of the faces `ids`: items[count[b]:count[b + 1]]
|
|
96
|
+
are the faces in bucket b."""
|
|
97
|
+
n = ids.shape[0]
|
|
98
|
+
nb_ = mask + 1
|
|
99
|
+
count = np.zeros(nb_ + 1, np.int64)
|
|
100
|
+
for r in range(n):
|
|
101
|
+
i = ids[r]
|
|
102
|
+
x0 = np.int64(np.floor((lo[i, 0] - origin[0]) / h))
|
|
103
|
+
y0 = np.int64(np.floor((lo[i, 1] - origin[1]) / h))
|
|
104
|
+
z0 = np.int64(np.floor((lo[i, 2] - origin[2]) / h))
|
|
105
|
+
x1 = np.int64(np.floor((hi[i, 0] - origin[0]) / h))
|
|
106
|
+
y1 = np.int64(np.floor((hi[i, 1] - origin[1]) / h))
|
|
107
|
+
z1 = np.int64(np.floor((hi[i, 2] - origin[2]) / h))
|
|
108
|
+
for ix in range(x0, x1 + 1):
|
|
109
|
+
for iy in range(y0, y1 + 1):
|
|
110
|
+
for iz in range(z0, z1 + 1):
|
|
111
|
+
count[_hash(ix, iy, iz, mask) + 1] += 1
|
|
112
|
+
for b in range(nb_):
|
|
113
|
+
count[b + 1] += count[b]
|
|
114
|
+
fill = count[:-1].copy()
|
|
115
|
+
items = np.empty(count[nb_], np.int64)
|
|
116
|
+
for r in range(n):
|
|
117
|
+
i = ids[r]
|
|
118
|
+
x0 = np.int64(np.floor((lo[i, 0] - origin[0]) / h))
|
|
119
|
+
y0 = np.int64(np.floor((lo[i, 1] - origin[1]) / h))
|
|
120
|
+
z0 = np.int64(np.floor((lo[i, 2] - origin[2]) / h))
|
|
121
|
+
x1 = np.int64(np.floor((hi[i, 0] - origin[0]) / h))
|
|
122
|
+
y1 = np.int64(np.floor((hi[i, 1] - origin[1]) / h))
|
|
123
|
+
z1 = np.int64(np.floor((hi[i, 2] - origin[2]) / h))
|
|
124
|
+
for ix in range(x0, x1 + 1):
|
|
125
|
+
for iy in range(y0, y1 + 1):
|
|
126
|
+
for iz in range(z0, z1 + 1):
|
|
127
|
+
b = _hash(ix, iy, iz, mask)
|
|
128
|
+
items[fill[b]] = i
|
|
129
|
+
fill[b] += 1
|
|
130
|
+
return count, items
|
|
131
|
+
|
|
132
|
+
@nb.njit(cache=True, inline="always")
|
|
133
|
+
def seg_tri(p, q, t0, t1, t2):
|
|
134
|
+
# crease._seg_tri for one segment and one triangle (eps 1e-9)
|
|
135
|
+
eps = 1e-9
|
|
136
|
+
d0, d1, d2 = q[0] - p[0], q[1] - p[1], q[2] - p[2]
|
|
137
|
+
e10, e11, e12 = t1[0] - t0[0], t1[1] - t0[1], t1[2] - t0[2]
|
|
138
|
+
e20, e21, e22 = t2[0] - t0[0], t2[1] - t0[1], t2[2] - t0[2]
|
|
139
|
+
h0 = d1 * e22 - d2 * e21
|
|
140
|
+
h1 = d2 * e20 - d0 * e22
|
|
141
|
+
h2 = d0 * e21 - d1 * e20
|
|
142
|
+
a = e10 * h0 + e11 * h1 + e12 * h2
|
|
143
|
+
if not abs(a) > 1e-14:
|
|
144
|
+
return False
|
|
145
|
+
f = 1.0 / a
|
|
146
|
+
s0, s1, s2 = p[0] - t0[0], p[1] - t0[1], p[2] - t0[2]
|
|
147
|
+
u = f * (s0 * h0 + s1 * h1 + s2 * h2)
|
|
148
|
+
if not u > eps:
|
|
149
|
+
return False
|
|
150
|
+
q0 = s1 * e12 - s2 * e11
|
|
151
|
+
q1 = s2 * e10 - s0 * e12
|
|
152
|
+
q2 = s0 * e11 - s1 * e10
|
|
153
|
+
v = f * (d0 * q0 + d1 * q1 + d2 * q2)
|
|
154
|
+
if not (v > eps and u + v < 1 - eps):
|
|
155
|
+
return False
|
|
156
|
+
t = f * (e20 * q0 + e21 * q1 + e22 * q2)
|
|
157
|
+
return t > eps and t < 1 - eps
|
|
158
|
+
|
|
159
|
+
@nb.njit(cache=True, inline="always")
|
|
160
|
+
def apart(V, FX, FY):
|
|
161
|
+
# every vertex of Y strictly on one side of X's plane
|
|
162
|
+
x0, x1, x2 = V[FX[0]], V[FX[1]], V[FX[2]]
|
|
163
|
+
a0, a1, a2 = x1[0] - x0[0], x1[1] - x0[1], x1[2] - x0[2]
|
|
164
|
+
b0, b1, b2 = x2[0] - x0[0], x2[1] - x0[1], x2[2] - x0[2]
|
|
165
|
+
n0 = a1 * b2 - a2 * b1
|
|
166
|
+
n1 = a2 * b0 - a0 * b2
|
|
167
|
+
n2 = a0 * b1 - a1 * b0
|
|
168
|
+
tol = 1e-12 * (n0 * n0 + n1 * n1 + n2 * n2)
|
|
169
|
+
pos = 0
|
|
170
|
+
neg = 0
|
|
171
|
+
for k in range(3):
|
|
172
|
+
y = V[FY[k]]
|
|
173
|
+
s = (y[0] - x0[0]) * n0 + (y[1] - x0[1]) * n1 + (y[2] - x0[2]) * n2
|
|
174
|
+
if s > tol:
|
|
175
|
+
pos += 1
|
|
176
|
+
elif s < -tol:
|
|
177
|
+
neg += 1
|
|
178
|
+
return pos == 3 or neg == 3
|
|
179
|
+
|
|
180
|
+
@nb.njit(cache=True, inline="always")
|
|
181
|
+
def pair_hit(V, fa, fb):
|
|
182
|
+
"""Do faces fa and fb (vertex triples) cross, sharing no vertex?"""
|
|
183
|
+
for u in range(3):
|
|
184
|
+
for w in range(3):
|
|
185
|
+
if fa[u] == fb[w]:
|
|
186
|
+
return False
|
|
187
|
+
if apart(V, fa, fb) or apart(V, fb, fa):
|
|
188
|
+
return False
|
|
189
|
+
for k in range(3):
|
|
190
|
+
if seg_tri(V[fa[k]], V[fa[(k + 1) % 3]], V[fb[0]], V[fb[1]], V[fb[2]]):
|
|
191
|
+
return True
|
|
192
|
+
if seg_tri(V[fb[k]], V[fb[(k + 1) % 3]], V[fa[0]], V[fa[1]], V[fa[2]]):
|
|
193
|
+
return True
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
@nb.njit(cache=True)
|
|
197
|
+
def coarse_marks(lo, hi, faces, origin, H, cmask):
|
|
198
|
+
"""A bitmap (hashed) of the coarse cells, H across, the query
|
|
199
|
+
faces' boxes reach."""
|
|
200
|
+
m = np.zeros(cmask + 1, np.bool_)
|
|
201
|
+
for r in range(faces.shape[0]):
|
|
202
|
+
i = faces[r]
|
|
203
|
+
x0 = np.int64(np.floor((lo[i, 0] - origin[0]) / H))
|
|
204
|
+
y0 = np.int64(np.floor((lo[i, 1] - origin[1]) / H))
|
|
205
|
+
z0 = np.int64(np.floor((lo[i, 2] - origin[2]) / H))
|
|
206
|
+
x1 = np.int64(np.floor((hi[i, 0] - origin[0]) / H))
|
|
207
|
+
y1 = np.int64(np.floor((hi[i, 1] - origin[1]) / H))
|
|
208
|
+
z1 = np.int64(np.floor((hi[i, 2] - origin[2]) / H))
|
|
209
|
+
for ix in range(x0, x1 + 1):
|
|
210
|
+
for iy in range(y0, y1 + 1):
|
|
211
|
+
for iz in range(z0, z1 + 1):
|
|
212
|
+
m[_hash(ix, iy, iz, cmask)] = True
|
|
213
|
+
return m
|
|
214
|
+
|
|
215
|
+
@nb.njit(cache=True, parallel=True)
|
|
216
|
+
def query(V, F, lo, hi, count, items, origin, h, mask, cm, H, cmask, out):
|
|
217
|
+
"""Every face of the mesh whose box reaches a coarse cell of the
|
|
218
|
+
query faces looks up the query faces whose boxes overlap its own
|
|
219
|
+
(the hash holds only the query faces), and tests each pair once."""
|
|
220
|
+
for a in nb.prange(F.shape[0]):
|
|
221
|
+
# the coarse cells first: most faces are nowhere near a query face
|
|
222
|
+
x0 = np.int64(np.floor((lo[a, 0] - origin[0]) / H))
|
|
223
|
+
y0 = np.int64(np.floor((lo[a, 1] - origin[1]) / H))
|
|
224
|
+
z0 = np.int64(np.floor((lo[a, 2] - origin[2]) / H))
|
|
225
|
+
x1 = np.int64(np.floor((hi[a, 0] - origin[0]) / H))
|
|
226
|
+
y1 = np.int64(np.floor((hi[a, 1] - origin[1]) / H))
|
|
227
|
+
z1 = np.int64(np.floor((hi[a, 2] - origin[2]) / H))
|
|
228
|
+
near = False
|
|
229
|
+
for ix in range(x0, x1 + 1):
|
|
230
|
+
for iy in range(y0, y1 + 1):
|
|
231
|
+
for iz in range(z0, z1 + 1):
|
|
232
|
+
if cm[_hash(ix, iy, iz, cmask)]:
|
|
233
|
+
near = True
|
|
234
|
+
if not near:
|
|
235
|
+
continue
|
|
236
|
+
fa = F[a]
|
|
237
|
+
x0 = np.int64(np.floor((lo[a, 0] - origin[0]) / h))
|
|
238
|
+
y0 = np.int64(np.floor((lo[a, 1] - origin[1]) / h))
|
|
239
|
+
z0 = np.int64(np.floor((lo[a, 2] - origin[2]) / h))
|
|
240
|
+
x1 = np.int64(np.floor((hi[a, 0] - origin[0]) / h))
|
|
241
|
+
y1 = np.int64(np.floor((hi[a, 1] - origin[1]) / h))
|
|
242
|
+
z1 = np.int64(np.floor((hi[a, 2] - origin[2]) / h))
|
|
243
|
+
for ix in range(x0, x1 + 1):
|
|
244
|
+
for iy in range(y0, y1 + 1):
|
|
245
|
+
for iz in range(z0, z1 + 1):
|
|
246
|
+
bk = _hash(ix, iy, iz, mask)
|
|
247
|
+
for j in range(count[bk], count[bk + 1]):
|
|
248
|
+
b = items[j]
|
|
249
|
+
if b == a:
|
|
250
|
+
continue
|
|
251
|
+
if (lo[b, 0] > hi[a, 0] or lo[b, 1] > hi[a, 1] or lo[b, 2] > hi[a, 2]
|
|
252
|
+
or hi[b, 0] < lo[a, 0] or hi[b, 1] < lo[a, 1] or hi[b, 2] < lo[a, 2]):
|
|
253
|
+
continue
|
|
254
|
+
# test the pair once: in the cell holding the
|
|
255
|
+
# low corner of the boxes' overlap
|
|
256
|
+
cx = np.int64(np.floor((max(lo[a, 0], lo[b, 0]) - origin[0]) / h))
|
|
257
|
+
cy = np.int64(np.floor((max(lo[a, 1], lo[b, 1]) - origin[1]) / h))
|
|
258
|
+
cz = np.int64(np.floor((max(lo[a, 2], lo[b, 2]) - origin[2]) / h))
|
|
259
|
+
if cx != ix or cy != iy or cz != iz:
|
|
260
|
+
continue
|
|
261
|
+
if pair_hit(V, fa, F[b]):
|
|
262
|
+
out[a] = 1
|
|
263
|
+
out[b] = 1
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def crossing_faces(V, F, faces):
|
|
267
|
+
"""Faces among `faces` that cross another face of (V, F) (sharing no
|
|
268
|
+
vertex with it), and the faces they cross: crease.crossing_faces."""
|
|
269
|
+
faces = np.ascontiguousarray(faces, np.int64)
|
|
270
|
+
if not len(faces):
|
|
271
|
+
return faces
|
|
272
|
+
_kernels()
|
|
273
|
+
V = np.ascontiguousarray(V, np.float64)
|
|
274
|
+
F = np.ascontiguousarray(F, np.int64)
|
|
275
|
+
lo, hi = boxes(V, F)
|
|
276
|
+
qlo, qhi = query_box(lo, hi, faces)
|
|
277
|
+
# cells about a large face (90th percentile of a sample): most faces
|
|
278
|
+
# sit in 1-8 cells
|
|
279
|
+
s = faces if len(faces) <= 100_000 else faces[:: len(faces) // 100_000]
|
|
280
|
+
ext = (hi[s] - lo[s]).max(axis=1)
|
|
281
|
+
h = float(max(np.percentile(ext, 90), 1e-9 * max(float(np.abs(qhi - qlo).max()), 1e-30)))
|
|
282
|
+
origin = qlo - 0.5 * h
|
|
283
|
+
n = len(faces)
|
|
284
|
+
mask = np.int64((1 << max(10, int(np.ceil(np.log2(max(4 * n, 1)))))) - 1)
|
|
285
|
+
count, items = build(lo, hi, faces, origin, h, mask)
|
|
286
|
+
# coarse cells 8 fine ones across mark where the query faces are
|
|
287
|
+
H = 8.0 * h
|
|
288
|
+
cmask = np.int64((1 << max(10, int(np.ceil(np.log2(max(2 * n, 1)))))) - 1)
|
|
289
|
+
cm = coarse_marks(lo, hi, faces, origin, H, cmask)
|
|
290
|
+
out = np.zeros(len(F), np.uint8)
|
|
291
|
+
query(V, F, lo, hi, count, items, origin, h, mask, cm, H, cmask, out)
|
|
292
|
+
return np.flatnonzero(out)
|