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/meshops.py
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
"""Mesh bookkeeping for the field mesher as compiled kernels.
|
|
2
|
+
|
|
3
|
+
field_mesh's relax, unfold and debris filter spend most of their time not
|
|
4
|
+
in the field but in array bookkeeping over millions of faces: face
|
|
5
|
+
normals, vertex normals, the vertex-face incidence and the edge
|
|
6
|
+
adjacency the relax smooths over. As numpy each is several passes of
|
|
7
|
+
random-access gathers and a sort (an edge list through scipy's coo->csr
|
|
8
|
+
is ~2 s on a 3M-face lattice); here each is one parallel pass.
|
|
9
|
+
|
|
10
|
+
Every function has the numpy result it replaces as its fallback (no
|
|
11
|
+
numba, or LOADPATH_MESHOPS=numpy), and gives the same numbers up to the
|
|
12
|
+
order floating-point sums are taken in.
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import numba as nb
|
|
22
|
+
except ImportError: # pragma: no cover - numba is a dependency; the numpy paths without it
|
|
23
|
+
nb = None
|
|
24
|
+
|
|
25
|
+
__all__ = ["available", "nearest_on_triangles", "face_cross", "triple", "vertex_faces", "vertex_sum", "neighbors", "faces_of"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def available():
|
|
29
|
+
return nb is not None and os.environ.get("LOADPATH_MESHOPS", "").lower() != "numpy"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _cap_threads():
|
|
33
|
+
# LOADPATH_THREADS caps the compiled kernels' threads as it does peval's
|
|
34
|
+
env = os.environ.get("LOADPATH_THREADS")
|
|
35
|
+
if nb is not None and env:
|
|
36
|
+
try:
|
|
37
|
+
nb.set_num_threads(max(1, min(int(env), nb.config.NUMBA_NUM_THREADS)))
|
|
38
|
+
except Exception: # noqa: BLE001 - a bad value: numba's default
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
_cap_threads()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
if nb is not None:
|
|
46
|
+
|
|
47
|
+
@nb.njit(cache=True, parallel=True)
|
|
48
|
+
def _face_cross(V, F):
|
|
49
|
+
n = F.shape[0]
|
|
50
|
+
out = np.empty((n, 3))
|
|
51
|
+
for i in nb.prange(n):
|
|
52
|
+
a, b, c = F[i, 0], F[i, 1], F[i, 2]
|
|
53
|
+
b0, b1, b2 = V[b, 0] - V[a, 0], V[b, 1] - V[a, 1], V[b, 2] - V[a, 2]
|
|
54
|
+
c0, c1, c2 = V[c, 0] - V[a, 0], V[c, 1] - V[a, 1], V[c, 2] - V[a, 2]
|
|
55
|
+
out[i, 0] = b1 * c2 - b2 * c1
|
|
56
|
+
out[i, 1] = b2 * c0 - b0 * c2
|
|
57
|
+
out[i, 2] = b0 * c1 - b1 * c0
|
|
58
|
+
return out
|
|
59
|
+
|
|
60
|
+
@nb.njit(cache=True, parallel=True)
|
|
61
|
+
def _triple(V, F):
|
|
62
|
+
n = F.shape[0]
|
|
63
|
+
out = np.empty(n)
|
|
64
|
+
for i in nb.prange(n):
|
|
65
|
+
a, b, c = F[i, 0], F[i, 1], F[i, 2]
|
|
66
|
+
out[i] = (V[a, 0] * (V[b, 1] * V[c, 2] - V[b, 2] * V[c, 1])
|
|
67
|
+
+ V[a, 1] * (V[b, 2] * V[c, 0] - V[b, 0] * V[c, 2])
|
|
68
|
+
+ V[a, 2] * (V[b, 0] * V[c, 1] - V[b, 1] * V[c, 0]))
|
|
69
|
+
return out
|
|
70
|
+
|
|
71
|
+
@nb.njit(cache=True)
|
|
72
|
+
def _vertex_faces(F, n):
|
|
73
|
+
nf = F.shape[0]
|
|
74
|
+
ptr = np.zeros(n + 1, np.int64)
|
|
75
|
+
for i in range(nf):
|
|
76
|
+
for k in range(3):
|
|
77
|
+
ptr[F[i, k] + 1] += 1
|
|
78
|
+
for v in range(n):
|
|
79
|
+
ptr[v + 1] += ptr[v]
|
|
80
|
+
fill = ptr[:-1].copy()
|
|
81
|
+
out = np.empty(3 * nf, np.int64)
|
|
82
|
+
for i in range(nf): # faces in ascending order within each vertex
|
|
83
|
+
for k in range(3):
|
|
84
|
+
v = F[i, k]
|
|
85
|
+
out[fill[v]] = i
|
|
86
|
+
fill[v] += 1
|
|
87
|
+
return ptr, out
|
|
88
|
+
|
|
89
|
+
@nb.njit(cache=True, parallel=True)
|
|
90
|
+
def _vertex_sum(ptr, fidx, X):
|
|
91
|
+
n = ptr.shape[0] - 1
|
|
92
|
+
m = X.shape[1]
|
|
93
|
+
out = np.zeros((n, m))
|
|
94
|
+
for v in nb.prange(n):
|
|
95
|
+
for j in range(ptr[v], ptr[v + 1]):
|
|
96
|
+
f = fidx[j]
|
|
97
|
+
for k in range(m):
|
|
98
|
+
out[v, k] += X[f, k]
|
|
99
|
+
return out
|
|
100
|
+
|
|
101
|
+
@nb.njit(cache=True, parallel=True)
|
|
102
|
+
def _neighbor_count(F, ptr, fidx, seam, use_seam):
|
|
103
|
+
n = ptr.shape[0] - 1
|
|
104
|
+
cnt = np.zeros(n, np.int64)
|
|
105
|
+
for v in nb.prange(n):
|
|
106
|
+
m = ptr[v + 1] - ptr[v]
|
|
107
|
+
buf = np.empty(2 * m, np.int64)
|
|
108
|
+
k = 0
|
|
109
|
+
for j in range(ptr[v], ptr[v + 1]):
|
|
110
|
+
f = fidx[j]
|
|
111
|
+
for c in range(3):
|
|
112
|
+
u = F[f, c]
|
|
113
|
+
if u == v:
|
|
114
|
+
continue
|
|
115
|
+
if use_seam and seam[v] and not seam[u]:
|
|
116
|
+
continue
|
|
117
|
+
dup = False
|
|
118
|
+
for t in range(k):
|
|
119
|
+
if buf[t] == u:
|
|
120
|
+
dup = True
|
|
121
|
+
break
|
|
122
|
+
if not dup:
|
|
123
|
+
buf[k] = u
|
|
124
|
+
k += 1
|
|
125
|
+
cnt[v] = k
|
|
126
|
+
return cnt
|
|
127
|
+
|
|
128
|
+
@nb.njit(cache=True, parallel=True)
|
|
129
|
+
def _neighbor_fill(F, ptr, fidx, seam, use_seam, indptr, indices):
|
|
130
|
+
n = ptr.shape[0] - 1
|
|
131
|
+
for v in nb.prange(n):
|
|
132
|
+
k = indptr[v]
|
|
133
|
+
k0 = k
|
|
134
|
+
for j in range(ptr[v], ptr[v + 1]):
|
|
135
|
+
f = fidx[j]
|
|
136
|
+
for c in range(3):
|
|
137
|
+
u = F[f, c]
|
|
138
|
+
if u == v:
|
|
139
|
+
continue
|
|
140
|
+
if use_seam and seam[v] and not seam[u]:
|
|
141
|
+
continue
|
|
142
|
+
dup = False
|
|
143
|
+
for t in range(k0, k):
|
|
144
|
+
if indices[t] == u:
|
|
145
|
+
dup = True
|
|
146
|
+
break
|
|
147
|
+
if not dup:
|
|
148
|
+
indices[k] = u
|
|
149
|
+
k += 1
|
|
150
|
+
# sorted columns, as scipy keeps them
|
|
151
|
+
indices[k0:k].sort()
|
|
152
|
+
|
|
153
|
+
@nb.njit(cache=True, parallel=True)
|
|
154
|
+
def _min_cos(ptr, fidx, fn, N):
|
|
155
|
+
n = ptr.shape[0] - 1
|
|
156
|
+
out = np.ones(n)
|
|
157
|
+
for v in nb.prange(n):
|
|
158
|
+
m = np.inf if ptr[v + 1] > ptr[v] else 1.0
|
|
159
|
+
for j in range(ptr[v], ptr[v + 1]):
|
|
160
|
+
f = fidx[j]
|
|
161
|
+
c = fn[f, 0] * N[v, 0] + fn[f, 1] * N[v, 1] + fn[f, 2] * N[v, 2]
|
|
162
|
+
if c < m:
|
|
163
|
+
m = c
|
|
164
|
+
out[v] = m
|
|
165
|
+
return out
|
|
166
|
+
|
|
167
|
+
@nb.njit(cache=True, parallel=True)
|
|
168
|
+
def _row_dots(A, B, ia, ib):
|
|
169
|
+
n = ia.shape[0]
|
|
170
|
+
out = np.empty(n)
|
|
171
|
+
for i in nb.prange(n):
|
|
172
|
+
a, b = ia[i], ib[i]
|
|
173
|
+
out[i] = A[a, 0] * B[b, 0] + A[a, 1] * B[b, 1] + A[a, 2] * B[b, 2]
|
|
174
|
+
return out
|
|
175
|
+
|
|
176
|
+
@nb.njit(cache=True, parallel=True)
|
|
177
|
+
def _edge_mates(F, ptr, fidx):
|
|
178
|
+
nf = F.shape[0]
|
|
179
|
+
mate = np.full(3 * nf, -1, np.int64)
|
|
180
|
+
for f in nb.prange(nf):
|
|
181
|
+
for k in range(3):
|
|
182
|
+
a = F[f, k]
|
|
183
|
+
b = F[f, (k + 1) % 3]
|
|
184
|
+
best = -1
|
|
185
|
+
for j in range(ptr[a], ptr[a + 1]):
|
|
186
|
+
g = fidx[j]
|
|
187
|
+
if g == f:
|
|
188
|
+
continue
|
|
189
|
+
for c in range(3):
|
|
190
|
+
u = F[g, c]
|
|
191
|
+
w = F[g, (c + 1) % 3]
|
|
192
|
+
if (u == a and w == b) or (u == b and w == a):
|
|
193
|
+
h = 3 * g + c
|
|
194
|
+
if best < 0 or h < best:
|
|
195
|
+
best = h
|
|
196
|
+
mate[3 * f + k] = best
|
|
197
|
+
return mate
|
|
198
|
+
|
|
199
|
+
@nb.njit(cache=True, parallel=True)
|
|
200
|
+
def _mark_faces(ptr, fidx, verts, out):
|
|
201
|
+
for r in nb.prange(verts.shape[0]):
|
|
202
|
+
v = verts[r]
|
|
203
|
+
for j in range(ptr[v], ptr[v + 1]):
|
|
204
|
+
out[fidx[j]] = True
|
|
205
|
+
|
|
206
|
+
@nb.njit(cache=True, nogil=True, inline="always")
|
|
207
|
+
def _closest(px, py, pz, T, t):
|
|
208
|
+
# fields._closest_on_triangles for one point and triangle (Ericson
|
|
209
|
+
# 5.1.5), its cases in the same order (a later case wins)
|
|
210
|
+
ax, ay, az = T[t, 0, 0], T[t, 0, 1], T[t, 0, 2]
|
|
211
|
+
bx, by, bz = T[t, 1, 0], T[t, 1, 1], T[t, 1, 2]
|
|
212
|
+
cx, cy, cz = T[t, 2, 0], T[t, 2, 1], T[t, 2, 2]
|
|
213
|
+
abx, aby, abz = bx - ax, by - ay, bz - az
|
|
214
|
+
acx, acy, acz = cx - ax, cy - ay, cz - az
|
|
215
|
+
apx, apy, apz = px - ax, py - ay, pz - az
|
|
216
|
+
d1 = abx * apx + aby * apy + abz * apz
|
|
217
|
+
d2 = acx * apx + acy * apy + acz * apz
|
|
218
|
+
bpx, bpy, bpz = px - bx, py - by, pz - bz
|
|
219
|
+
d3 = abx * bpx + aby * bpy + abz * bpz
|
|
220
|
+
d4 = acx * bpx + acy * bpy + acz * bpz
|
|
221
|
+
cpx, cpy, cpz = px - cx, py - cy, pz - cz
|
|
222
|
+
d5 = abx * cpx + aby * cpy + abz * cpz
|
|
223
|
+
d6 = acx * cpx + acy * cpy + acz * cpz
|
|
224
|
+
va = d3 * d6 - d5 * d4
|
|
225
|
+
vb = d5 * d2 - d1 * d6
|
|
226
|
+
vc = d1 * d4 - d3 * d2
|
|
227
|
+
if (d6 >= 0) and (d5 <= d6):
|
|
228
|
+
return cx, cy, cz
|
|
229
|
+
if (d3 >= 0) and (d4 <= d3):
|
|
230
|
+
return bx, by, bz
|
|
231
|
+
if (d1 <= 0) and (d2 <= 0):
|
|
232
|
+
return ax, ay, az
|
|
233
|
+
e1 = d4 - d3
|
|
234
|
+
e2 = d5 - d6
|
|
235
|
+
if (va <= 0) and (e1 >= 0) and (e2 >= 0):
|
|
236
|
+
tt = e1 / (e1 + e2)
|
|
237
|
+
return bx + (cx - bx) * tt, by + (cy - by) * tt, bz + (cz - bz) * tt
|
|
238
|
+
if (vb <= 0) and (d2 >= 0) and (d6 <= 0):
|
|
239
|
+
tt = d2 / (d2 - d6)
|
|
240
|
+
return ax + acx * tt, ay + acy * tt, az + acz * tt
|
|
241
|
+
if (vc <= 0) and (d1 >= 0) and (d3 <= 0):
|
|
242
|
+
tt = d1 / (d1 - d3)
|
|
243
|
+
return ax + abx * tt, ay + aby * tt, az + abz * tt
|
|
244
|
+
den = 1.0 / (va + vb + vc)
|
|
245
|
+
v = vb * den
|
|
246
|
+
w = vc * den
|
|
247
|
+
return ax + abx * v + acx * w, ay + aby * v + acy * w, az + abz * v + acz * w
|
|
248
|
+
|
|
249
|
+
@nb.njit(cache=True, nogil=True, inline="always")
|
|
250
|
+
def _nearest_row(Q, cand, T, Nrm, tie_abs, signed, i, d2s, cs, out_d2, out_s):
|
|
251
|
+
px, py, pz = Q[i, 0], Q[i, 1], Q[i, 2]
|
|
252
|
+
m = cand.shape[1]
|
|
253
|
+
best = np.inf
|
|
254
|
+
ib = 0
|
|
255
|
+
for j in range(m):
|
|
256
|
+
qx, qy, qz = _closest(px, py, pz, T, cand[i, j])
|
|
257
|
+
dx, dy, dz = qx - px, qy - py, qz - pz
|
|
258
|
+
d2 = dx * dx + dy * dy + dz * dz
|
|
259
|
+
d2s[j] = d2
|
|
260
|
+
cs[j, 0], cs[j, 1], cs[j, 2] = qx, qy, qz
|
|
261
|
+
if d2 < best:
|
|
262
|
+
best = d2
|
|
263
|
+
ib = j
|
|
264
|
+
out_d2[i] = best
|
|
265
|
+
out_s[i] = 1.0
|
|
266
|
+
if signed:
|
|
267
|
+
lim = best * (1 + 1e-6) + tie_abs
|
|
268
|
+
nx = ny = nz = 0.0
|
|
269
|
+
for j in range(m):
|
|
270
|
+
if d2s[j] <= lim:
|
|
271
|
+
t = cand[i, j]
|
|
272
|
+
nx += Nrm[t, 0]
|
|
273
|
+
ny += Nrm[t, 1]
|
|
274
|
+
nz += Nrm[t, 2]
|
|
275
|
+
sn = (px - cs[ib, 0]) * nx + (py - cs[ib, 1]) * ny + (pz - cs[ib, 2]) * nz
|
|
276
|
+
if sn < 0:
|
|
277
|
+
out_s[i] = -1.0
|
|
278
|
+
|
|
279
|
+
@nb.njit(cache=True, nogil=True)
|
|
280
|
+
def _nearest_serial(Q, cand, T, Nrm, tie_abs, signed, out_d2, out_s):
|
|
281
|
+
m = cand.shape[1]
|
|
282
|
+
d2s = np.empty(m)
|
|
283
|
+
cs = np.empty((m, 3))
|
|
284
|
+
for i in range(Q.shape[0]):
|
|
285
|
+
_nearest_row(Q, cand, T, Nrm, tie_abs, signed, i, d2s, cs, out_d2, out_s)
|
|
286
|
+
|
|
287
|
+
@nb.njit(cache=True, parallel=True)
|
|
288
|
+
def _nearest_parallel(Q, cand, T, Nrm, tie_abs, signed, out_d2, out_s):
|
|
289
|
+
m = cand.shape[1]
|
|
290
|
+
n = Q.shape[0]
|
|
291
|
+
nchunk = 64
|
|
292
|
+
for c in nb.prange(nchunk):
|
|
293
|
+
d2s = np.empty(m)
|
|
294
|
+
cs = np.empty((m, 3))
|
|
295
|
+
for i in range(c * n // nchunk, (c + 1) * n // nchunk):
|
|
296
|
+
_nearest_row(Q, cand, T, Nrm, tie_abs, signed, i, d2s, cs, out_d2, out_s)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def nearest_on_triangles(Q, cand, T, normals=None, tie_abs=0.0, parallel=False):
|
|
300
|
+
"""For each point Q[i], the closest of triangles T[cand[i]] (T: (n, 3, 3),
|
|
301
|
+
cand: (len(Q), k)): squared distance, and with `normals` the side, by
|
|
302
|
+
the normals of every candidate whose closest point ties the best (as
|
|
303
|
+
fields.MeshField and sharp.mesh_distance sign them: -1 inside). One
|
|
304
|
+
compiled pass without the (points, k, 3) temporaries; `parallel` runs
|
|
305
|
+
it on numba's threads (callers already on threads leave it off)."""
|
|
306
|
+
Q = np.ascontiguousarray(Q, np.float64)
|
|
307
|
+
cand = np.ascontiguousarray(cand, np.int64)
|
|
308
|
+
T = np.ascontiguousarray(T, np.float64)
|
|
309
|
+
signed = normals is not None
|
|
310
|
+
N = np.ascontiguousarray(normals, np.float64) if signed else np.zeros((1, 3))
|
|
311
|
+
d2 = np.empty(len(Q))
|
|
312
|
+
s = np.empty(len(Q))
|
|
313
|
+
if len(Q):
|
|
314
|
+
(_nearest_parallel if parallel else _nearest_serial)(Q, cand, T, N, float(tie_abs), signed, d2, s)
|
|
315
|
+
return d2, s
|
|
316
|
+
|
|
317
|
+
def _ints(F):
|
|
318
|
+
return np.ascontiguousarray(F, np.int64)
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
def face_cross(V, F):
|
|
322
|
+
"""(b - a) x (c - a) for every face (a, b, c)."""
|
|
323
|
+
if available() and len(F):
|
|
324
|
+
return _face_cross(np.ascontiguousarray(V, np.float64), _ints(F))
|
|
325
|
+
from .narrowband import _face_cross_np
|
|
326
|
+
|
|
327
|
+
return _face_cross_np(V, F)
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def triple(V, F):
|
|
331
|
+
"""a . (b x c) per face."""
|
|
332
|
+
if available() and len(F):
|
|
333
|
+
return _triple(np.ascontiguousarray(V, np.float64), _ints(F))
|
|
334
|
+
from .narrowband import _triple_np
|
|
335
|
+
|
|
336
|
+
return _triple_np(V, F)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def vertex_faces(F, n):
|
|
340
|
+
"""CSR vertex -> faces: faces ptr[v]:ptr[v + 1] of `idx` hold vertex v
|
|
341
|
+
(each face once per corner it has there; ascending within a vertex)."""
|
|
342
|
+
F = _ints(F)
|
|
343
|
+
if available():
|
|
344
|
+
return _vertex_faces(F, int(n))
|
|
345
|
+
flat = F.ravel()
|
|
346
|
+
o = np.argsort(flat, kind="stable")
|
|
347
|
+
ptr = np.searchsorted(flat[o], np.arange(int(n) + 1))
|
|
348
|
+
return ptr, o // 3
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def vertex_sum(ptr, idx, X):
|
|
352
|
+
"""Per vertex, the sum of X over its faces (rows of X are faces)."""
|
|
353
|
+
X = np.ascontiguousarray(X, np.float64)
|
|
354
|
+
if available():
|
|
355
|
+
return _vertex_sum(ptr, idx, X.reshape(len(X), -1)).reshape((len(ptr) - 1,) + X.shape[1:])
|
|
356
|
+
v = np.repeat(np.arange(len(ptr) - 1), np.diff(ptr))
|
|
357
|
+
out = np.zeros((len(ptr) - 1,) + X.shape[1:])
|
|
358
|
+
np.add.at(out, v, X[idx])
|
|
359
|
+
return out
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def neighbors(F, n, seam=None, vf=None):
|
|
363
|
+
"""The mesh's edge adjacency as CSR (indptr, indices): each vertex's
|
|
364
|
+
distinct neighbours along an edge, sorted. With `seam` (bool per
|
|
365
|
+
vertex), a seam vertex keeps only its seam neighbours."""
|
|
366
|
+
F = _ints(F)
|
|
367
|
+
n = int(n)
|
|
368
|
+
if available():
|
|
369
|
+
ptr, idx = vf if vf is not None else _vertex_faces(F, n)
|
|
370
|
+
use = seam is not None
|
|
371
|
+
s = np.ascontiguousarray(seam, np.bool_) if use else np.zeros(1, np.bool_)
|
|
372
|
+
cnt = _neighbor_count(F, ptr, idx, s, use)
|
|
373
|
+
indptr = np.zeros(n + 1, np.int64)
|
|
374
|
+
np.cumsum(cnt, out=indptr[1:])
|
|
375
|
+
indices = np.empty(int(indptr[-1]), np.int64)
|
|
376
|
+
_neighbor_fill(F, ptr, idx, s, use, indptr, indices)
|
|
377
|
+
return indptr, indices
|
|
378
|
+
import scipy.sparse as sp
|
|
379
|
+
|
|
380
|
+
a = np.r_[F[:, 0], F[:, 1], F[:, 2]]
|
|
381
|
+
b = np.r_[F[:, 1], F[:, 2], F[:, 0]]
|
|
382
|
+
r, c = np.r_[a, b], np.r_[b, a]
|
|
383
|
+
if seam is not None:
|
|
384
|
+
seam = np.asarray(seam, bool)
|
|
385
|
+
k = ~seam[r] | seam[c]
|
|
386
|
+
r, c = r[k], c[k]
|
|
387
|
+
A = sp.coo_matrix((np.ones(len(r), np.float32), (r, c)), shape=(n, n)).tocsr()
|
|
388
|
+
A.sum_duplicates()
|
|
389
|
+
A.sort_indices()
|
|
390
|
+
return A.indptr.astype(np.int64), A.indices.astype(np.int64)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def min_cos(vf, fn, N):
|
|
394
|
+
"""Per vertex, the least cosine between its unit normal N and the unit
|
|
395
|
+
normals fn of the faces round it (1 where it has none)."""
|
|
396
|
+
ptr, idx = vf
|
|
397
|
+
if available():
|
|
398
|
+
return _min_cos(ptr, idx, np.ascontiguousarray(fn, np.float64), np.ascontiguousarray(N, np.float64))
|
|
399
|
+
cnt = np.diff(ptr)
|
|
400
|
+
c = (fn[idx] * N[np.repeat(np.arange(len(cnt)), cnt)]).sum(axis=1)
|
|
401
|
+
lo = np.ones(len(cnt))
|
|
402
|
+
has = np.flatnonzero(cnt)
|
|
403
|
+
lo[has] = np.minimum.reduceat(c, ptr[has])
|
|
404
|
+
return lo
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
def row_dots(A, B, ia, ib):
|
|
408
|
+
"""(A[ia] * B[ib]).sum(axis=1) for (n, 3) arrays."""
|
|
409
|
+
ia = np.ascontiguousarray(ia, np.int64)
|
|
410
|
+
ib = np.ascontiguousarray(ib, np.int64)
|
|
411
|
+
if available():
|
|
412
|
+
return _row_dots(np.ascontiguousarray(A, np.float64), np.ascontiguousarray(B, np.float64), ia, ib)
|
|
413
|
+
return (A[ia] * B[ib]).sum(axis=1)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def edge_pairs(F, vf=None):
|
|
417
|
+
"""The mesh's edges as pairs of half-edges (h1 < h2; half-edge h is
|
|
418
|
+
corner h % 3 of face h // 3, from it to the next corner): the two
|
|
419
|
+
faces that share each edge. An edge with one face is left out, and
|
|
420
|
+
one with more than two gives its first two."""
|
|
421
|
+
F = _ints(F)
|
|
422
|
+
if available():
|
|
423
|
+
ptr, idx = vf if vf is not None else _vertex_faces(F, int(F.max()) + 1 if len(F) else 0)
|
|
424
|
+
mate = _edge_mates(F, ptr, idx)
|
|
425
|
+
h1 = np.flatnonzero(mate > np.arange(len(mate)))
|
|
426
|
+
h2 = mate[h1]
|
|
427
|
+
# a non-manifold edge: keep only its first pair
|
|
428
|
+
return h1, h2
|
|
429
|
+
a, b = F.ravel(), np.roll(F, -1, axis=1).ravel()
|
|
430
|
+
key = np.minimum(a, b) * np.int64(F.max() + 1) + np.maximum(a, b)
|
|
431
|
+
o = np.argsort(key, kind="stable")
|
|
432
|
+
ks = key[o]
|
|
433
|
+
p = np.flatnonzero(ks[1:] == ks[:-1])
|
|
434
|
+
first = np.r_[True, ks[1:] != ks[:-1]]
|
|
435
|
+
p = p[first[p]] # the first two of each edge
|
|
436
|
+
h1, h2 = o[p], o[p + 1]
|
|
437
|
+
s = np.argsort(h1)
|
|
438
|
+
return h1[s], h2[s]
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
def faces_of(vf, verts, nf):
|
|
442
|
+
"""Sorted distinct faces round the vertices `verts` (vf: vertex_faces)."""
|
|
443
|
+
ptr, idx = vf
|
|
444
|
+
verts = np.ascontiguousarray(verts, np.int64)
|
|
445
|
+
out = np.zeros(int(nf), np.bool_)
|
|
446
|
+
if available():
|
|
447
|
+
_mark_faces(ptr, idx, verts, out)
|
|
448
|
+
else:
|
|
449
|
+
a, ln = ptr[verts], ptr[verts + 1] - ptr[verts]
|
|
450
|
+
j = np.repeat(a - np.cumsum(ln) + ln, ln) + np.arange(int(ln.sum()))
|
|
451
|
+
out[idx[j]] = True
|
|
452
|
+
return np.flatnonzero(out)
|