|
Wildmeshing Toolkit
|
Functions | |
| eval_selection (expr, names) | |
| read_groups (path) | |
| point_scalar (m, name) | |
| front_diags (m) | |
| front_sag_map (path) | |
| read_groups_vtu (path) | |
| read_any (path) | |
| interfaces (points, dim, groups) | |
| amips2d_quality (points, tris) | |
| compact (points, cells) | |
| sample_regular_grid (pts, vals, q) | |
| dist_to_segments (q, a, b) | |
| dist_to_triangles (q, t0, t1, t2) | |
| read_frame_labels (d) | |
| step_label (stem, token=None) | |
| frame_key (p) | |
| resolve (args) | |
| load (msh, cfg) | |
| register_frame (prefix, points, dim, surf, err, mesh, sizing=None, diags=None, delta=None) | |
| load_phi_grid (meshes) | |
| main () | |
Variables | |
| tuple | C_INPUT = (0.910, 0.525, 0.165) |
| tuple | C_OFFSET = (0.169, 0.498, 0.831) |
| tuple | C_INNER = (0.55, 0.55, 0.58) |
| tuple | C_OTHER = (0.55, 0.75, 0.55) |
| tuple | C_BAD = (0.839, 0.153, 0.157) |
| tuple | C_BAND = (0.45, 0.66, 0.90) |
| list | TAG_COLORS |
| int | MAX_ENERGY = 1e50 |
| dict | NON_GROUP_CELL_FIELDS = {"amips"} |
| tuple | FRONT_DIAGS |
| str | FRONT_SAG = "front_sag_ratio" |
| FRAME_RE = __import__("re").compile(r"(\d+)(?=\D*$)") | |
| SEQ_RE = re.compile(r"_(\d{5})$") | |
| STEP_RE = re.compile(r"step_(\d+)_r(\d+)([ABS])(?:(\d+)(?:_([A-Za-z][A-Za-z-]*))?|_end)$") | |
| TOKEN_RE = re.compile(r"r(\d+)([ABS])(?:(\d+)(?:_([A-Za-z][A-Za-z-]*))?|_end)$") | |
| list | LAYERS |
Polyscope viewer for a topological_offset result: region surfaces + distance error.
./visualize_offset.py runs/prism # a run directory
./visualize_offset.py out.msh # mesh alone
./visualize_offset.py out.msh config.json # mesh + the config it ran under
./visualize_offset.py runs/prism --lazy # long series: read frames on demand
Needs polyscope, meshio and numpy. The simwild conda env has all three:
conda activate simwild && python visualize_offset.py <run dir>
Otherwise any env with those packages works (see the triwild scripts README for a venv).
The output .msh holds one cell block per physical group -- `ambient`, one group per tag,
and the offset output tag(s). Nothing in the file marks the surfaces the optimization is
about, so they are derived here the way the C++ derives them from the labels: an
interface face (3D) or edge (2D) is one whose two incident cells lie in different
groups, and
input surface boundary of the non-ambient, non-offset groups -- the complex the
offset is measured against (orange)
offset surface offset group against ambient: the band's OUTER surface, the one
supposed to sit at target_distance. Coloured by |dist-delta|/delta
by default (reds, white 0 -> dark red at the max, range shown in the
panel); switchable to raw distance (viridis) or solid blue
inner interface offset group against the input groups: hugs the complex at distance
~0 by construction, drawn dim and off by default, so it cannot be
mistaken for the offset
region boundaries every facet where the two sides' tag memberships differ (green);
on by default when the input-surface layer is empty
envelope curves the EnvelopeSurface line entity, 2D outputs only: the
region-boundary geometry the envelope was built from (orange)
bad quality 2D only, off by default: the background triangles whose AMIPS energy is
above a threshold, drawn in red with their vertices as red points. The
energy is wmtk::AMIPS2D_energy transcribed (2 = equilateral, MAX_ENERGY
= 1e50 for inverted or degenerate), so it is the same number stop_energy
is compared against -- the threshold starts AT the config's stop_energy
and is adjustable on a log10 slider. The background mesh also carries it
as a "quality (AMIPS)" cell scalar.
The offset surface also carries the run's own front diagnostics, off by default, which are
what a convergence question is answered with:
front: Newton step / bar front_vertex_conv_ratio() as written by write_vtu(). This is
the ONLY per-vertex quantity the convergence test reads, so
<= 1 means the loop considers the vertex placed. Range 0..2.
front: residual / delta the vertex's true distance to the level set over the target
distance. 0 is on the level set. The test never reads this.
front: |grad Phi| the field gradient the objective's pull is built from.
front: move dir . field normal
|cos| between the direction the placement may move the vertex
in and the direction that reduces its distance. 0 means the
test measures a step that cannot reduce the distance at all,
which is what an envelope-held front vertex gets.
front: distance to complex / delta
the plain Euclidean distance from the vertex to the input
complex, over the target distance: 1 is exactly on the offset.
Read straight off the BVH rather than from the vertex's region
field, so it is the same number under either offset_field and is
the independent check on where the front really is.
front: sag / tube the OTHER half of the test, on the edge (2D) / face (3D) rather
than on its corners: how far the level set curves away from the
chord, over the same tube. > 1 with both corners on the level
set is what makes a chord refinable. Read from the frame's sag
companion (`_front.vtu` in 2D, `_off.vtu` in 3D).
Red on the residual layer and dark on the Newton-step layer at the same vertex means
stationary in the wrong place: no gradient to move along, so the test calls it placed. That is
what a vertex trapped on the medial axis of the field looks like. Frames written before those
fields existed simply do not offer the layers.
The offset surface carries two scalar layers: distance to the input surface, and
|distance - delta| / delta. delta comes from the config (`target_distance`, else
`target_distance_rel` x the mesh bounding-box diagonal -- the box is the frozen
background bbox, so the output diagonal equals the input one). Without a config the
defaults (rel 0.01, tag "offset") are assumed and said so.
Two honesty notes on the error layer:
* The reference surface is the band's INNER interface (which hugs the input complex by
construction, whatever the selection expression was), not the complex the C++ BVH
holds -- they differ where the band does not fully wrap the complex. Distances to it
are exact point-to-facet.
* It is sampled at offset VERTICES, like compute_distance_deviation() -- so it inherits
that metric's known blind spot between vertices.
| visualize_offset.amips2d_quality | ( | points, | |
| tris | |||
| ) |
Per-triangle AMIPS energy, TRANSCRIBED FROM THE C++ so the numbers are the same ones. This is wmtk::AMIPS2D_energy (src/wmtk/utils/AMIPS2D.cpp) vectorised, wrapped in the guard from TriOptimizerMesh::get_quality: a non-finite energy, or one below 2 - 1e-3 (which an inverted or degenerate triangle produces), is reported as MAX_ENERGY rather than as a small number. Getting that guard wrong would paint inverted triangles as the BEST in the mesh, which is the one error that would make this layer actively misleading. Matching the C++ is the whole point: it means "bad" here is the same "bad" the optimizer's stop_energy is compared against, so a triangle highlighted in the viewer is a triangle the run itself is still working on. The energy of an equilateral triangle is 2. -> (N,) float array, one entry per row of `tris`.
| visualize_offset.dist_to_segments | ( | q, | |
| a, | |||
| b | |||
| ) |
(nq,) exact distance from each query point to the nearest of the segments (a, b).
| visualize_offset.dist_to_triangles | ( | q, | |
| t0, | |||
| t1, | |||
| t2 | |||
| ) |
(nq,) exact distance from each query point to the nearest of the triangles. Point sampling is NOT good enough here: the input complex is frozen at construction resolution, so its edges are comparable to delta, and a sampled reference inflates the distance by up to half an edge -- measured as avg err 0.30 where the C++ log said 0.08. So: exact point-triangle distance (Ericson, Real-Time Collision Detection 5.1.5), vectorized over query x triangle and chunked.
| visualize_offset.eval_selection | ( | expr, | |
| names | |||
| ) |
The offset_selection boolean expression, on one cell's group memberships. Same language the C++ parses: tag names, & | !, parentheses; 'ambient' (spec default spells it '_' or '!_') means "in no tag group". Names are substituted with their membership truth value, then the expression is evaluated -- nothing but booleans and the three operators survives the substitution, so eval sees no cell data.
| visualize_offset.frame_key | ( | p | ) |
Sort debug frames by the counter in their name, not lexically -- debug_9 precedes debug_10, which a plain sort gets backwards. A `step_` frame sorts on its GLOBAL counter, which is run order across both phases by construction. The trailing-number fallback would sort those on the pass index instead and shuffle the phases together, so the step form is matched first.
| visualize_offset.front_diags | ( | m | ) |
{field name: per-vertex array with the sentinels as NaN} for whichever fields are present.
| visualize_offset.front_sag_map | ( | path | ) |
{sorted vertex tuple: sag ratio} from the frame's sag companion; {} where there is none.
| visualize_offset.interfaces | ( | points, | |
| dim, | |||
| groups | |||
| ) |
{(group a, group b): facet array}, a < b, plus (group, '') for domain boundary.
| visualize_offset.load | ( | msh, | |
| cfg | |||
| ) |
Everything main() draws, as plain arrays -- no polyscope.
| visualize_offset.load_phi_grid | ( | meshes | ) |
The sampled smooth offset potential, written beside the result as `<output>_phi.vtu`. THE OFFSET IS A LEVEL SET of a field that exists everywhere, and the result mesh only ever samples that field along one curve -- so when the offset lands somewhere unexpected, the mesh alone cannot say whether the field is wrong or the optimization failed to reach it. This layer is the field itself, drawn as a dense background surface with `phi` as a VERTEX scalar so polyscope's isoline mode draws the level set directly. Written only when phi_grid_resolution > 0 in the config. Returns None when absent.
| visualize_offset.point_scalar | ( | m, | |
| name | |||
| ) |
A per-vertex scalar from the file, flattened; None where the writer did not emit it. .msh result files carry `sizing` (m_sizing_scalar per vertex) and write_vtu() frames carry it as `sizing_scalar` in both dimensions, so `sizing` falls back to that name; older .msh files written before write_msh_groups() emitted it do not, and for those the sizing layer simply is not offered.
| visualize_offset.read_frame_labels | ( | d | ) |
{frame index: label} from <something>_frames.txt in directory `d`; {} if there is none.
| visualize_offset.read_groups | ( | path | ) |
(points, dim, {group name: cell array}, envelope segments) from the .msh.
| visualize_offset.read_groups_vtu | ( | path | ) |
(points, dim, {group name: cell array}, envelope segments) from a debug .vtu frame.
The debug frames the optimization writes are .vtu, not .msh, and carry their groups
differently: write_vtu() emits one CELL FIELD per tag (1 where the cell carries it) plus
`offset_tag`, which is 1 on the band and is derived from the construction LABEL rather than
from the tags -- so it is the reliable band marker even where a tag was written elsewhere.
Only 0/1 INDICATOR fields are groups. The 2D writer also emits the per-cell scalar `amips`,
which is not a membership, and reading it as a group drew phantom regions: on the
pre-optimize frames only the faces a pass had touched carry a quality yet, so `amips`
selected a scattered subset of cells, which then showed up as a "tag boundary: amips" curve
and as region-boundary loops around every touched patch, and shifted every real tag's color
by one. Skipped by name (NON_GROUP_CELL_FIELDS), and any other cell field that is not
exactly 0/1 is skipped too.
| visualize_offset.register_frame | ( | prefix, | |
| points, | |||
| dim, | |||
| surf, | |||
| err, | |||
| mesh, | |||
sizing = None, |
|||
diags = None, |
|||
delta = None |
|||
| ) |
Register one frame's layers, all disabled.
Returns ({layer label: structure}, extras) where extras holds what the sizing toggle needs
to re-add quantities with the desired enabled state: `off` = (offset-surface structure,
its compacted sizing rows, (err values, err max) or None) and `bg` = (background structure,
sizing, class values, defined_on). Re-adding rather than handles, because this polyscope's
add_scalar_quantity returns None -- and re-adding under the same name is the API's setter.
The displaced quantity must be restored on untoggle: polyscope keeps one active scalar per
structure, so enabling one silently disables the other and unchecking would otherwise leave
the structure colorless.
| visualize_offset.resolve | ( | args | ) |
Command line -> (list of mesh paths, config dict or {}, stride).
| visualize_offset.sample_regular_grid | ( | pts, | |
| vals, | |||
| q | |||
| ) |
Bilinear sample of a field defined on the regular grid `pts` (as written by write_phi_grid) at the query points `q`. None if `pts` is not such a grid.
| visualize_offset.step_label | ( | stem, | |
token = None |
|||
| ) |
'r1A3' (a manifest label) or a legacy 'step_00007_r1A3' name -> 'round 1 phase A3'. Returns None when neither form matches, so callers can fall back to the file name.
| tuple visualize_offset.FRONT_DIAGS |
| list visualize_offset.LAYERS |
| list visualize_offset.TAG_COLORS |