Skip to content

Commit 4eeb5a8

Browse files
committed
Domain: explicit kwargs for create/solve/build; expand docs. Export: add find_unionable_pairs and union_tubes_balanced with progress bar; defer remesh to final step.
1 parent 66b2424 commit 4eeb5a8

File tree

3 files changed

+484
-31
lines changed

3 files changed

+484
-31
lines changed

docs/api/domain.html

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -251,34 +251,44 @@ <h4>Parameters</h4>
251251

252252
<div class="api-method">
253253
<div class="api-method-signature">
254-
<code>create(**kwargs)</code>
254+
<code>create(min_patch_size=10, max_patch_size=20, overlap=0.2, feature_angle=30)</code>
255255
</div>
256256
<p>Create patches for the domain. This is the first step in building the implicit function.</p>
257257
<div class="api-method-params">
258258
<h4>Parameters</h4>
259259
<ul>
260-
<li><code>**kwargs</code>: Parameters passed to the allocation algorithm</li>
260+
<li><code>min_patch_size</code> (int, default=10): Minimum number of points required to form a patch.</li>
261+
<li><code>max_patch_size</code> (int, default=20): Target maximum number of points per patch (nearest-neighbor window).</li>
262+
<li><code>overlap</code> (float, default=0.2): Fraction [0, 1] controlling allowed overlap of points between patches.</li>
263+
<li><code>feature_angle</code> (float, default=30): Maximum angle in degrees between point-wise normal vectors for inclusion in the same patch (used only when normals are provided).</li>
261264
</ul>
262265
</div>
263266
</div>
264267

265268
<div class="api-method">
266269
<div class="api-method-signature">
267-
<code>solve(**kwargs)</code>
270+
<code>solve(method="L-BFGS-B", precision=9)</code>
268271
</div>
269272
<p>Solve the individual patch interpolation problems prior to blending.</p>
273+
<div class="api-method-params">
274+
<h4>Parameters</h4>
275+
<ul>
276+
<li><code>method</code> (str, default="L-BFGS-B"): Optimization method for SciPy's minimize (see Patch.solve).</li>
277+
<li><code>precision</code> (int, default=9): Decimal places to round the solved constants.</li>
278+
</ul>
279+
</div>
270280
</div>
271281

272282
<div class="api-method">
273283
<div class="api-method-signature">
274-
<code>build(**kwargs)</code>
284+
<code>build(resolution=25, skip_boundary=False)</code>
275285
</div>
276-
<p>Build the implicit function describing the domain.</p>
286+
<p>Build the implicit function describing the domain and optionally extract boundary/mesh artifacts.</p>
277287
<div class="api-method-params">
278288
<h4>Parameters</h4>
279289
<ul>
280-
<li><code>resolution</code> (int, default=25): Grid resolution for boundary extraction</li>
281-
<li><code>skip_boundary</code> (bool, default=False): Skip boundary generation</li>
290+
<li><code>resolution</code> (int, default=25): Grid resolution for boundary extraction.</li>
291+
<li><code>skip_boundary</code> (bool, default=False): If true, only assemble fast-evaluation structures and skip boundary and interior mesh generation.</li>
282292
</ul>
283293
</div>
284294
</div>
@@ -479,6 +489,34 @@ <h3>Creating a Domain from STL File</h3>
479489
print(f"Convexity: {domain.convexity:.3f}")</code></pre>
480490
</div>
481491

492+
<div class="api-example">
493+
<h3>Customized Patch Allocation</h3>
494+
<pre data-copy><code class="language-python">import pyvista as pv
495+
from svv.domain.domain import Domain
496+
497+
mesh = pv.read('vessel.stl')
498+
domain = Domain(mesh)
499+
500+
# Tune patch allocation for dense data with noisy normals
501+
domain.create(
502+
min_patch_size=8, # allow smaller patches in sparse areas
503+
max_patch_size=32, # include more neighbors in dense regions
504+
overlap=0.35, # increase overlap for smoother blending
505+
feature_angle=45 # be more tolerant to normal variation/noise
506+
)
507+
domain.solve()
508+
domain.build(resolution=30)</code></pre>
509+
<div class="callout tip">
510+
<strong>When to adjust defaults:</strong>
511+
<ul>
512+
<li><em>Dense point clouds:</em> increase <code>max_patch_size</code> to capture local context and reduce edge artifacts.</li>
513+
<li><em>Sparse/heterogeneous sampling:</em> decrease <code>min_patch_size</code> so isolated regions still form patches.</li>
514+
<li><em>Noisy or inconsistent normals:</em> raise <code>feature_angle</code> to admit neighbors with larger normal differences; lower it to preserve sharp features.</li>
515+
<li><em>Smoother blends vs. speed:</em> higher <code>overlap</code> yields smoother transitions across patches but increases compute.</li>
516+
</ul>
517+
</div>
518+
</div>
519+
482520
<div class="api-example">
483521
<h3>Boolean Operations on Domains</h3>
484522
<pre data-copy><code class="language-python"># Create two domains

svv/domain/domain.py

Lines changed: 81 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -167,17 +167,63 @@ def set_random_generator(self):
167167
self.random_generator = np.random.Generator(np.random.PCG64(seed=self.random_seed))
168168
return None
169169

170-
def create(self, **kwargs):
170+
def create(self,
171+
min_patch_size: int = 10,
172+
max_patch_size: int = 20,
173+
overlap: float = 0.2,
174+
feature_angle: float = 30) -> None:
171175
"""
172-
Create the patches for the domain. This is the first step in the process.
173-
:param kwargs:
174-
:return:
176+
Partition input data into spatial patches and initialize Patch objects.
177+
178+
This is the first step of the domain pipeline (create → solve → build).
179+
It groups the input point cloud (and optional normals) into overlapping
180+
local neighborhoods (“patches”) and instantiates a Patch for each one.
181+
Internally this delegates to `svv.domain.routines.allocate.allocate`,
182+
which performs the neighbor search, duplicate handling, and optional
183+
feature‑angle filtering.
184+
185+
Parameters
186+
----------
187+
min_patch_size : int, optional
188+
Minimum number of points required to form a patch. Default 10.
189+
max_patch_size : int, optional
190+
Target maximum points per patch (nearest‑neighbor window). Default 20.
191+
overlap : float, optional
192+
Fraction [0, 1] controlling allowed overlap of point indices between
193+
patches; higher permits more shared points. Default 0.2.
194+
feature_angle : float, optional
195+
Maximum allowed angle in degrees between point‑wise normal vectors
196+
for inclusion in the same patch as the seed point. Used only when
197+
normals are provided. Default 30.
198+
199+
Side Effects
200+
------------
201+
- Populates `self.patches` with Patch instances. Each Patch receives its
202+
subset of points (and normals if available) and, by default, builds a
203+
Kernel and sets initial values in `Patch.set_data`.
204+
205+
Notes
206+
-----
207+
- If `self.normals` is None, patches are created using spatial proximity only.
208+
- If normals are provided, the `feature_angle` criterion is enforced and
209+
duplicate points with incompatible normals are handled by `allocate`.
210+
- No solving or function assembly happens here; call `solve()` next to fit
211+
per‑patch coefficients, then `build()` to assemble the global implicit
212+
function and precompute fast‑evaluation arrays.
175213
"""
176214
self.patches = []
177215
if self.normals is None:
178-
patch_data = allocate(self.points, **kwargs)
216+
patch_data = allocate(self.points,
217+
min_patch_size=min_patch_size,
218+
max_patch_size=max_patch_size,
219+
overlap=overlap,
220+
feature_angle=feature_angle)
179221
else:
180-
patch_data = allocate(self.points, self.normals, **kwargs)
222+
patch_data = allocate(self.points, self.normals,
223+
min_patch_size=min_patch_size,
224+
max_patch_size=max_patch_size,
225+
overlap=overlap,
226+
feature_angle=feature_angle)
181227
for i in trange(len(patch_data), desc='Creating patches', unit='patch', leave=False):
182228
self.patches.append(Patch())
183229
if self.normals is None:
@@ -186,29 +232,46 @@ def create(self, **kwargs):
186232
self.patches[-1].set_data(patch_data[i][0], patch_data[i][1])
187233
return None
188234

189-
def solve(self, **kwargs):
235+
def solve(self, method: str = "L-BFGS-B", precision: int = 9) -> None:
190236
"""
191-
Solve the individual patch interpolation problems prior to blending.
237+
Solve each Patch's interpolation problem before blending.
192238
193239
Parameters
194240
----------
195-
None
241+
method : str, optional
242+
Optimization method passed to the underlying SciPy optimizer via
243+
`Patch.solve`. Default "L-BFGS-B".
244+
precision : int, optional
245+
Number of decimal places for rounding the solved constants per patch.
246+
Default 9.
196247
197-
Returns
198-
-------
199-
None
248+
Notes
249+
-----
250+
This step computes per‑patch coefficients used later by `build()` to
251+
assemble the global implicit function and fast‑evaluation arrays.
200252
"""
201253
for i in trange(len(self.patches), desc='Solving patches', unit='patch', leave=False):
202-
self.patches[i].solve(**kwargs)
254+
self.patches[i].solve(method=method, precision=precision)
203255
return None
204256

205-
def build(self, **kwargs):
257+
def build(self, resolution: int = 25, skip_boundary: bool = False) -> None:
206258
"""
207-
Build the implicit function describing the domain.
208-
:return:
259+
Assemble the global implicit function and optional boundary/mesh artifacts.
260+
261+
This combines per‑patch solutions into a structure that supports fast
262+
evaluation of the implicit field via `evaluate_fast`/`__call__`. When a
263+
Domain is loaded from a .dmn file, precomputed arrays may be present and
264+
this method will reuse them, only rebuilding missing lightweight indices.
265+
266+
Parameters
267+
----------
268+
resolution : int, optional
269+
Grid resolution used when extracting the boundary surface/curve.
270+
Higher values yield finer boundaries at increased cost. Default 25.
271+
skip_boundary : bool, optional
272+
When True, skip building boundary and interior mesh artifacts and
273+
only assemble fast‑evaluation structures. Default False.
209274
"""
210-
resolution = kwargs.get('resolution', 25)
211-
skip_boundary = kwargs.get('skip_boundary', False)
212275
# If this Domain was loaded from a .dmn file, it already has
213276
# A/B/C/D/PTS and possibly a function_tree. In that case, skip
214277
# rebuilding from patches (which will be empty) and only ensure

0 commit comments

Comments
 (0)