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/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)