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.
Files changed (220) hide show
  1. loadpath/README.md +294 -0
  2. loadpath/SKILL.md +584 -0
  3. loadpath/__init__.py +17 -0
  4. loadpath/__main__.py +1202 -0
  5. loadpath/am.py +316 -0
  6. loadpath/amgfast.py +326 -0
  7. loadpath/assembly.py +985 -0
  8. loadpath/benchmarks/__init__.py +0 -0
  9. loadpath/benchmarks/beams.py +266 -0
  10. loadpath/benchmarks/cases/ge-bracket.json +380 -0
  11. loadpath/benchmarks/evaluate.py +172 -0
  12. loadpath/benchmarks/mechanisms.py +416 -0
  13. loadpath/benchmarks/problems.py +140 -0
  14. loadpath/benchmarks/reference/bridge-3d.top3d.npz +0 -0
  15. loadpath/benchmarks/reference/cantilever-3d.top3d.npz +0 -0
  16. loadpath/benchmarks/reference/lbracket-2d.top88-ft2.npz +0 -0
  17. loadpath/benchmarks/reference/mbb-2d.top88-ft1.npz +0 -0
  18. loadpath/benchmarks/reference/mbb-2d.top88-ft2.npz +0 -0
  19. loadpath/benchmarks/reference.py +138 -0
  20. loadpath/benchmarks/run.py +301 -0
  21. loadpath/brep.py +1470 -0
  22. loadpath/bucklingopt.py +300 -0
  23. loadpath/buildsim.py +1490 -0
  24. loadpath/cadimport.py +904 -0
  25. loadpath/calibration.py +885 -0
  26. loadpath/cavities.py +211 -0
  27. loadpath/checks/__init__.py +0 -0
  28. loadpath/checks/dfm.py +354 -0
  29. loadpath/checks/exchanger.py +7 -0
  30. loadpath/checks/fea.py +212 -0
  31. loadpath/checks/fit.py +475 -0
  32. loadpath/checks/networks.py +260 -0
  33. loadpath/checks/opening.py +67 -0
  34. loadpath/checks/sdf_kit.py +7 -0
  35. loadpath/conjugate.py +267 -0
  36. loadpath/constrained.py +329 -0
  37. loadpath/contact.py +691 -0
  38. loadpath/coopt.py +342 -0
  39. loadpath/cores.py +163 -0
  40. loadpath/crease.py +1138 -0
  41. loadpath/critic.py +212 -0
  42. loadpath/crosscheck.py +1107 -0
  43. loadpath/cutcell.py +510 -0
  44. loadpath/data/lattice_buckling.json +439 -0
  45. loadpath/data/lattice_tensors.json +27449 -0
  46. loadpath/data/published_experiments.json +71 -0
  47. loadpath/decimate.py +177 -0
  48. loadpath/delivery.py +190 -0
  49. loadpath/directions.py +262 -0
  50. loadpath/domain.py +630 -0
  51. loadpath/drains.py +615 -0
  52. loadpath/durability.py +413 -0
  53. loadpath/editor.html +3135 -0
  54. loadpath/editor.py +610 -0
  55. loadpath/escape.py +354 -0
  56. loadpath/evals/__init__.py +0 -0
  57. loadpath/evals/baselines/coarse.json +1543 -0
  58. loadpath/evals/baselines/full.json +1497 -0
  59. loadpath/evals/brief_kinds/__init__.py +1 -0
  60. loadpath/evals/brief_kinds/heatx.py +387 -0
  61. loadpath/evals/brief_kinds/lattice.py +571 -0
  62. loadpath/evals/brief_kinds/multimat.py +593 -0
  63. loadpath/evals/briefs/assets/bracket.step +1201 -0
  64. loadpath/evals/briefs/assets/desk_arm.urdf +22 -0
  65. loadpath/evals/briefs/assets/elbow-gearbox.stl +0 -0
  66. loadpath/evals/briefs/assets/motor-body.stl +0 -0
  67. loadpath/evals/briefs/assets/sketch.png +0 -0
  68. loadpath/evals/briefs/base-adapter-fdm.json +492 -0
  69. loadpath/evals/briefs/bell-crank-lpbf.json +180 -0
  70. loadpath/evals/briefs/belt-tensioner-fdm.json +151 -0
  71. loadpath/evals/briefs/camera-boom-mjf.json +339 -0
  72. loadpath/evals/briefs/camera-mount-pla.json +203 -0
  73. loadpath/evals/briefs/coolant-isolator-petg.json +100 -0
  74. loadpath/evals/briefs/elbow-bearing-bracket-sls.json +300 -0
  75. loadpath/evals/briefs/elbow-gearbox-forearm-mjf.json +558 -0
  76. loadpath/evals/briefs/elbow-link-fatigue-fdm.json +251 -0
  77. loadpath/evals/briefs/elbow-stop-pad-tpu.json +39 -0
  78. loadpath/evals/briefs/forearm-fdm.json +146 -0
  79. loadpath/evals/briefs/forearm-servo-link-fdm.json +327 -0
  80. loadpath/evals/briefs/grip-pad-finger-mm.json +354 -0
  81. loadpath/evals/briefs/gripper-bracket-creep-fdm.json +340 -0
  82. loadpath/evals/briefs/gripper-finger-fdm.json +155 -0
  83. loadpath/evals/briefs/gripper-jaw-fatigue-mjf.json +328 -0
  84. loadpath/evals/briefs/gripper-palm-fdm.json +715 -0
  85. loadpath/evals/briefs/hot-stepper-mount-asa-fdm.json +471 -0
  86. loadpath/evals/briefs/hpu-oil-cooler-lpbf.json +121 -0
  87. loadpath/evals/briefs/isolated-camera-mount-mm.json +675 -0
  88. loadpath/evals/briefs/kart-charge-cooler-lpbf.json +93 -0
  89. loadpath/evals/briefs/lighten-step-bracket.json +220 -0
  90. loadpath/evals/briefs/nema17-mount-stl.json +429 -0
  91. loadpath/evals/briefs/pillow-block-sls.json +220 -0
  92. loadpath/evals/briefs/polyjet-fingertip-mm.json +305 -0
  93. loadpath/evals/briefs/rod-hook-pla.json +154 -0
  94. loadpath/evals/briefs/sensor-arm-resin.json +150 -0
  95. loadpath/evals/briefs/shelf-bracket-pla.json +158 -0
  96. loadpath/evals/briefs/shoulder-riser-mjf.json +487 -0
  97. loadpath/evals/briefs/sketch-clevis.json +229 -0
  98. loadpath/evals/briefs/spring-bracket-pacf.json +294 -0
  99. loadpath/evals/briefs/tool-flange-ti64.json +280 -0
  100. loadpath/evals/briefs/tray-arm-lattice-fdm.json +260 -0
  101. loadpath/evals/briefs/turret-fdm.json +281 -0
  102. loadpath/evals/briefs/upper-arm-sls.json +179 -0
  103. loadpath/evals/briefs/urdf-forearm.json +233 -0
  104. loadpath/evals/briefs/wrist-link-mjf.json +278 -0
  105. loadpath/evals/briefs/wrist-yoke-mjf.json +245 -0
  106. loadpath/evals/briefs.py +393 -0
  107. loadpath/evals/cases/arm-link-cast.json +157 -0
  108. loadpath/evals/cases/arm-link-driven.json +171 -0
  109. loadpath/evals/cases/arm-link-sandwich.json +315 -0
  110. loadpath/evals/cases/arm-link.json +111 -0
  111. loadpath/evals/cases/bell-crank.json +116 -0
  112. loadpath/evals/cases/grip-turret.json +479 -0
  113. loadpath/evals/cases/grip-upper-arm.json +253 -0
  114. loadpath/evals/cases/grip-wrist-link-mjf.json +929 -0
  115. loadpath/evals/cases/grip-wrist-yoke.json +831 -0
  116. loadpath/evals/cases/lpbf-intercooler-core.json +59 -0
  117. loadpath/evals/cases/mbb-beam.json +61 -0
  118. loadpath/evals/cases/motor-body.stl +0 -0
  119. loadpath/evals/cases/motor-mount.json +265 -0
  120. loadpath/evals/cases/printed-hook.json +77 -0
  121. loadpath/evals/cases/wall-bracket-fasteners.json +125 -0
  122. loadpath/evals/cases/wall-bracket.json +126 -0
  123. loadpath/evals/corpus.py +179 -0
  124. loadpath/evals/pareto.py +90 -0
  125. loadpath/evals/run.py +860 -0
  126. loadpath/examples/arm-link.json +25 -0
  127. loadpath/examples/bumpered-bracket-compression.json +225 -0
  128. loadpath/examples/bumpered-bracket-lattice.json +204 -0
  129. loadpath/examples/bumpered-bracket.json +34 -0
  130. loadpath/examples/gripper-finger-mm.json +185 -0
  131. loadpath/examples/heat-exchanger-counterflow.json +21 -0
  132. loadpath/examples/heat-exchanger-crossflow-tuned.json +48 -0
  133. loadpath/examples/heat-exchanger-crossflow.json +20 -0
  134. loadpath/examples/pedal-lattice.json +23 -0
  135. loadpath/examples/pressure-plenum.json +12 -0
  136. loadpath/examples/shelf-bracket.json +27 -0
  137. loadpath/exchanger.py +831 -0
  138. loadpath/fasteners.py +199 -0
  139. loadpath/fem.py +668 -0
  140. loadpath/femops.py +70 -0
  141. loadpath/fieldjit.py +981 -0
  142. loadpath/fieldops.py +119 -0
  143. loadpath/fields.py +3697 -0
  144. loadpath/fieldtorch.py +464 -0
  145. loadpath/flow.py +984 -0
  146. loadpath/flowtopo.py +300 -0
  147. loadpath/gpu.py +521 -0
  148. loadpath/heatx.py +1067 -0
  149. loadpath/heatx_thermal.py +432 -0
  150. loadpath/homogenized.py +886 -0
  151. loadpath/ibrep.py +552 -0
  152. loadpath/impact.py +693 -0
  153. loadpath/infill.py +376 -0
  154. loadpath/interface_library.py +340 -0
  155. loadpath/interlock.py +186 -0
  156. loadpath/isolator.py +149 -0
  157. loadpath/knn.py +204 -0
  158. loadpath/lattice.py +1065 -0
  159. loadpath/lattice3mf.py +760 -0
  160. loadpath/lattice_buckling.py +245 -0
  161. loadpath/lattice_grade.py +325 -0
  162. loadpath/lattice_orient.py +595 -0
  163. loadpath/lattice_print.py +159 -0
  164. loadpath/lattice_tensors.py +558 -0
  165. loadpath/latticestep.py +1210 -0
  166. loadpath/manufacture.py +446 -0
  167. loadpath/material_library.py +489 -0
  168. loadpath/materials.py +144 -0
  169. loadpath/mcp_server.py +786 -0
  170. loadpath/mechanism.py +218 -0
  171. loadpath/mechanism_nl.py +811 -0
  172. loadpath/meshops.py +452 -0
  173. loadpath/mg.py +853 -0
  174. loadpath/mma.py +214 -0
  175. loadpath/modal.py +210 -0
  176. loadpath/multimat.py +1788 -0
  177. loadpath/narrowband.py +418 -0
  178. loadpath/nlkernel.py +241 -0
  179. loadpath/nonlinear.py +1095 -0
  180. loadpath/orient.py +334 -0
  181. loadpath/porous.py +994 -0
  182. loadpath/pressure.py +171 -0
  183. loadpath/preview.py +335 -0
  184. loadpath/printcheck.py +263 -0
  185. loadpath/printsetup.py +207 -0
  186. loadpath/processcheck.py +376 -0
  187. loadpath/quick.py +113 -0
  188. loadpath/recall.py +406 -0
  189. loadpath/reconstruct.py +797 -0
  190. loadpath/render.py +436 -0
  191. loadpath/resolved.py +189 -0
  192. loadpath/schema.py +1701 -0
  193. loadpath/sdf_kit.py +843 -0
  194. loadpath/selfx.py +292 -0
  195. loadpath/shapes.py +584 -0
  196. loadpath/sharp.py +717 -0
  197. loadpath/simp.py +1170 -0
  198. loadpath/sizing.py +312 -0
  199. loadpath/slicer.py +321 -0
  200. loadpath/spec.py +1072 -0
  201. loadpath/step.py +806 -0
  202. loadpath/struts.py +639 -0
  203. loadpath/study.py +1937 -0
  204. loadpath/tasks.py +822 -0
  205. loadpath/thermal.py +1691 -0
  206. loadpath/threemf.py +487 -0
  207. loadpath/tune.py +437 -0
  208. loadpath/validation.py +145 -0
  209. loadpath/verify.py +1215 -0
  210. loadpath/viewer.html +694 -0
  211. loadpath/viewer.py +446 -0
  212. loadpath/viscoelastic.py +106 -0
  213. loadpath/voxelprint.py +582 -0
  214. loadpath/voxels.py +402 -0
  215. loadpath-0.1.1.dist-info/METADATA +319 -0
  216. loadpath-0.1.1.dist-info/RECORD +220 -0
  217. loadpath-0.1.1.dist-info/WHEEL +5 -0
  218. loadpath-0.1.1.dist-info/entry_points.txt +2 -0
  219. loadpath-0.1.1.dist-info/licenses/LICENSE +21 -0
  220. 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)