framefield¤
The frame-field route: solve a cross field, trace its separatrices into a coarse layout, and densify along the field.
Classes¤
FieldDecomposition
¤
FieldDecomposition(
background: BackgroundMesh | None,
field: CrossField | None,
separatrices: list[Separatrix],
tracer: Tracer | None,
)
Coarse quad layout from a cross field's separatrices.
Attributes:
-
background(BackgroundMesh) – -
field(CrossField) – -
separatrices(list[Separatrix]) – -
tracer(Tracer) – -
inputs(dict) –The arguments
from_boundarywas called with, so the same route can be rebuilt on another domain (a symmetric unit, say). -
mesh(CoarsePseudoQuadMesh or None) –The coarse layout, once
decomposition_meshhas run -- or the triangulated backstop after aquad_meshthat fell back. -
dense(QuadMesh or None) –The last mesh
quad_meshreturned. -
polylines(list or None) –The network the layout was built from, plus
user_curves. -
guides(list) –The guide curves the field was solved with.
-
symmetry(Symmetry or None) –The in-solver symmetry group (
framefield.symmetry). -
symmetry_report(SymmetryReport or None) –What
find_symmetryfound. -
trace_report, snap_report, densify_stats, edit_notes(dict) –What tracing, singularity snapping, densification and
edit_coarse/edges_to_curvesdid. -
repair_notes(list[str]) –Everything that degraded the result, in words. See
warnings. -
last_error(str or None) –The traceback of an exception
quad_meshcaught and fell back from. -
user_curves(list) –Curves the user drew on the layout (a line added in Rhino); kept in
polylinesso the edge they made densifies along them. Set withset_user_curves. -
field_aware(bool) –Falsedensifies patch interiors as plain Coons patches. For A/B measurements only.
- Tutorial Overview Two routes to a coarse layout
Methods:¤
coarse_mesh
¤
The coarse quad layout -- the name both front ends answer to.
See decomposition_mesh.
decomposition_mesh
¤
The all-quad coarse mesh, cached so densities and strips set on it survive.
Falls back to a one-polygon or triangulated layout; route says which.
Parameters:
-
poles(list[[x, y, z]], default:()) –Preferred pole positions: where a triangular patch the repair turns into a pseudo-quad puts its collapsed corner.
-
force(bool, default:False) –Rebuild even when the cached layout would do.
Returns:
-
CoarsePseudoQuadMesh–
decomposition_polylines
¤
All the polylines forming the decomposition -- separatrices and walls.
densify
¤
The layout densified, patch edges along their separatrices and patch interiors along the field.
Parameters:
-
coarse(CoarsePseudoQuadMesh, default:None) –Strips collected and densities set. Defaults to
mesh. -
**kwargs(Any, default:{}) –Passed to
densify.field_densification.
Returns:
-
QuadMesh–
edges_to_curves
¤
{(u, v): polyline} for densification: exact, then boundary arc, then (edited only) warped, then chord.
The tally is kept in edit_notes['edges'].
edit_coarse
¤
edit_coarse(
geometry: Any, poles: Any = None, snap_tol: float | None = None, strict: bool = True
) -> CoarsePseudoQuadMesh
Take a hand-edited coarse layout in place of the generated one: weld, snap, repair and check it.
Parameters:
-
geometry(mesh or list) –The edited layout: a mesh, or one closed polyline per patch with one point per corner. See
editing.rebuild.coarse_from_skeleton. -
poles(list[[x, y, z]], default:None) –Preferred pole positions. Defaults to the current layout's.
-
snap_tol(float, default:None) –How far a corner on the layout's boundary may be moved onto a wall. Defaults to half the background spacing;
0disables snapping. -
strict(bool, default:True) –Raise if the edited layout will not densify (default), rather than keep it with a note.
Returns:
-
CoarsePseudoQuadMesh–A NEW mesh: vertices are renumbered and no strips are collected.
find_symmetry
¤
find_symmetry(
tol: float | None = None,
include: tuple[str, ...] = ("walls", "holes", "guides", "poles"),
max_order: int = 12,
) -> SymmetryReport
Detect the symmetry of the domain -- walls, holes, guides.
Works without a solve (solve=False). Kept on symmetry_report.
Returns:
from_boundary
classmethod
¤
from_boundary(
outer_boundary: Any,
inner_boundaries: Any = None,
guides: Any = None,
mode: str = "perpendicular",
target_length: float | None = None,
orthogonal: bool | None = None,
guide_weight: float | None = 1.0,
guide_band: float | None = None,
relax: bool = False,
tau: float | None = None,
symmetry: Symmetry | str | None = "auto",
solve: bool = True,
field_tau: float | None = None,
) -> FieldDecomposition
Solve the field and trace its separatrices.
Parameters:
-
outer_boundary(list[[x, y, z]]) – -
inner_boundaries(list[list[[x, y, z]]], default:None) – -
guides(list[list[[x, y, z]]], default:None) –Guide curves -- cables, force lines. A LIST of curves; each may be a list of points or a compas
Polyline. -
mode(optional, default:'perpendicular') –As
CrossField.from_boundary.target_lengthis the background spacing, not the quad size -- that is set later byset_strips_density_targetorquad_mesh. With the defaultsymmetry='auto'a symmetric input gives a symmetric layout; an asymmetric one is unaffected. -
target_length(optional, default:'perpendicular') –As
CrossField.from_boundary.target_lengthis the background spacing, not the quad size -- that is set later byset_strips_density_targetorquad_mesh. With the defaultsymmetry='auto'a symmetric input gives a symmetric layout; an asymmetric one is unaffected. -
guide_weight(optional, default:'perpendicular') –As
CrossField.from_boundary.target_lengthis the background spacing, not the quad size -- that is set later byset_strips_density_targetorquad_mesh. With the defaultsymmetry='auto'a symmetric input gives a symmetric layout; an asymmetric one is unaffected. -
guide_band(optional, default:'perpendicular') –As
CrossField.from_boundary.target_lengthis the background spacing, not the quad size -- that is set later byset_strips_density_targetorquad_mesh. With the defaultsymmetry='auto'a symmetric input gives a symmetric layout; an asymmetric one is unaffected. -
relax(optional, default:'perpendicular') –As
CrossField.from_boundary.target_lengthis the background spacing, not the quad size -- that is set later byset_strips_density_targetorquad_mesh. With the defaultsymmetry='auto'a symmetric input gives a symmetric layout; an asymmetric one is unaffected. -
tau(optional, default:'perpendicular') –As
CrossField.from_boundary.target_lengthis the background spacing, not the quad size -- that is set later byset_strips_density_targetorquad_mesh. With the defaultsymmetry='auto'a symmetric input gives a symmetric layout; an asymmetric one is unaffected. -
symmetry(optional, default:'perpendicular') –As
CrossField.from_boundary.target_lengthis the background spacing, not the quad size -- that is set later byset_strips_density_targetorquad_mesh. With the defaultsymmetry='auto'a symmetric input gives a symmetric layout; an asymmetric one is unaffected. -
orthogonal(bool, default:None) –Only orthogonal (cross) fields are implemented;
Falseraises. -
solve(bool, default:True) –Falsestores the inputs and returns without solving, forfind_symmetryandsymmetry_unit. Anything that needs the field raises untilsolveis called. -
field_tau(float, default:None) –Old name of
tau.
from_mesh
classmethod
¤
from_mesh(trimesh: Mesh, **kwargs: Any) -> FieldDecomposition
Construct from a triangulation's BOUNDARY, as
SkeletonDecomposition.from_mesh does. The interior is rebuilt at a
spacing a field can live on.
get_field
¤
get_field() -> CrossField
The cross field, to hand to densification(field=...) of any
coarse layout. The same object as field.
quad_mesh
¤
quad_mesh(
target_length: float | None = None,
density: int | None = None,
coarse: Any = None,
densities: dict[Any, int] | None = None,
strict: bool = False,
) -> QuadMesh
An all-quad mesh of the domain, always: via the field layout, else one polygon, else a triangulation.
route says which route ran and warnings why.
Parameters:
-
target_length(float, default:None) –Target quad edge length.
-
density(int, default:None) –Fixed subdivision per coarse edge instead; takes precedence.
-
coarse(mesh or list, default:None) –A hand-edited layout, put through
edit_coarse. Once edited, the edit stays in use. -
densities(dict, default:None) –{strip key: density}over the base pass. A key naming no strip is noted, not raised. Strip keys renumber whenever the layout is rebuilt. -
strict(bool, default:False) –Re-raise an exception from densification instead of falling back. For tests and debugging.
Returns:
-
QuadMesh–
quality
¤
The numbers to gate on: element quality of a mesh, as a dict.
Parameters:
-
mesh(Mesh, default:None) –Defaults to the last mesh
quad_meshreturned. -
low_angle(float, default:None) –Threshold for
share_below, in degrees.
Returns:
-
dict–quality.mesh_quality's metrics, plusroute,coverage(mesh area over domain area) andcoarse_faces.
report
¤
The network's and the field's reports, plus face counts of the layout.
route
¤
route() -> str
Which route produced the last layout or mesh: 'field', 'polygon' or 'triangulation'.
set_user_curves
¤
Adopt the curves a user drew on the layout, replacing any set before, and add them to polylines.
solve
¤
solve() -> FieldDecomposition
The solved decomposition for the stored inputs -- a NEW object, or this one if it is already solved.
symmetry_unit
¤
symmetry_unit(
keys: Any = None,
centre: str = "route",
seam: Any = None,
report: SymmetryReport | None = None,
) -> SymmetricUnit
Solve and trace one symmetric unit of the domain with this decomposition's settings.
Parameters are those of SkeletonDecomposition.symmetry_unit.
Returns:
-
SymmetricUnit–
CrossField
¤
CrossField(
background: BackgroundMesh,
u: dict[int, complex],
relaxed: bool = False,
iterations: int | None = None,
residual: float | None = None,
)
A boundary-aligned cross field on a planar triangulation.
Attributes:
-
background(BackgroundMesh) – -
u(dict[int, complex]) –exp(i*4*theta)per vertex;|u| = 1only whenrelaxed. -
theta(dict[int, float]) –A representative cross angle per vertex, in (-pi/4, pi/4].
-
relaxed(bool) –Which solver produced this field.
-
iterations, residual((int, float or None)) –Diffusion steps taken and the final
max|u_k+1 - u_k|, when relaxed. -
guides(list) –The guide curves the field was solved with. Densification spends element quality on alignment only when there are some.
-
symmetry(Symmetry or None) –The symmetry group of the input.
-
singularity_points(dict[int, [x, y, z]]) –Where
symmetry.snap_singularitiesput each singularity; empty when no symmetry was applied. -
snap_report(dict) –What the snapping did.
-
inputs(dict or None) –FieldInputs.provenanceof what this field was solved from;Nonefor a field built throughsolvedirectly.
- Reference framefield Classes
Attributes¤
__data__
property
¤
compas Data: the save_to_json payload, so a field can
travel inside a larger document (a session) and come back exactly.
Methods:¤
__from_jsondata__
classmethod
¤
__from_jsondata__(data: dict[str, Any]) -> CrossField
Rebuild from __jsondata__'s payload.
__getstate__
¤
Everything but the point locator, for pickle and deepcopy.
__jsondata__
¤
The payload save_to_json writes: plain types plus one Mesh.
angle_in_face
¤
densify
¤
densify(
coarse: Any,
edges_to_curves: dict[tuple[int, int], list[list[float]]] | None = None,
spend: bool | None = None,
**kwargs: Any,
) -> tuple[Any, dict[str, Any]]
Densify a coarse layout with this field steering patch interiors.
What coarse.densification(field=field) calls; call it directly to
get the statistics as well.
Parameters:
-
coarse(CoarseQuadMesh or CoarsePseudoQuadMesh) –Strips collected and densities set.
-
edges_to_curves(dict, default:None) –{(u, v): polyline}, asdensificationtakes it. -
spend(bool, default:None) –Whether element quality may be spent on alignment. Defaults to whether this field has guides.
-
**kwargs(Any, default:{}) –Passed to
densify.field_densification.
Returns:
-
(QuadMesh, dict)–The dense mesh (also set on
coarse) and the per-patch counts.
directions
¤
The four arms of the cross at a vertex, as unit vectors.
from_boundary
classmethod
¤
from_boundary(
outer_boundary: Any,
inner_boundaries: Any = None,
guides: Any = None,
mode: str = "perpendicular",
target_length: float | None = None,
guide_weight: float | None = 1.0,
guide_band: float | None = None,
relax: bool = False,
tau: float | None = None,
symmetry: Symmetry | str | None = "auto",
) -> CrossField
A field over a domain: background, constraints, solve and symmetry, without tracing.
Parameters:
-
outer_boundary(list[[x, y, z]]) – -
inner_boundaries(list[list[[x, y, z]]], default:None) – -
guides(list[list[[x, y, z]]], default:None) –Guide curves -- cables, force lines. A LIST of curves; a lone curve is accepted too.
-
mode(('perpendicular', 'tangent'), default:'perpendicular') –See
constraints.from_curves. -
target_length(float, default:None) –Background spacing -- not the quad size.
-
guide_weight(float, default:1.0) –Weight of the guide constraints,
Nonefor hard. -
guide_band(float, default:None) –How far from a guide it constrains the field. Defaults to the background spacing.
-
relax(bool, default:False) –Use the diffusion + normalisation solver; see the module docstring.
-
tau(float, default:None) –Diffusion time per step,
relaxonly. -
symmetry(Symmetry or 'auto' or None, default:'auto') –'auto'detects the input's symmetry group and keeps it through the background, the field and its singularities.Noneignores symmetry. An asymmetric input is unaffected either way.
Returns:
-
CrossField–With
guides,symmetry,singularity_points,snap_reportandinputsset.
load_from_json
classmethod
¤
load_from_json(filepath: str, default: Any = None) -> CrossField | Any
Read a field back. The inverse of save_to_json.
Does NOT check that the field still belongs to your domain -- ask
mismatch straight afterwards, every time.
Parameters:
-
filepath(str) – -
default(optional, default:None) –Returned when the file does not exist.
Raises:
-
ValueError–If the file is not a field, or is newer than this version reads.
locator
¤
locator() -> PointLocator
Point location on this field's background. Built once, cached, never saved -- it is derived from the background alone.
matches
¤
Whether this field was solved from these inputs. False when it
carries no record of its inputs: unknown is not equal.
mismatch
¤
Why this field does not belong to these inputs, or None.
Ask it of every field read back from disk before densifying with it.
Returns:
-
str or None–The first difference, as a sentence.
poincare_hopf
¤
Check that the interior indices sum to the field's measured boundary winding.
Returns:
-
dict–interior_index_sum,boundary_winding,residual_radians,singular_facesandok.
report
¤
poincare_hopf plus the solve's diagnostics.
min_magnitude and mean_magnitude are only informative when the
field is not relaxed -- relaxation pins every magnitude to 1.
save_to_json
¤
Write this field to a JSON file, making the folder if needed. Returns filepath.
Returns:
-
str–filepath.
singularities
¤
solve
classmethod
¤
solve(
background: BackgroundMesh,
constraints: list[Constraint] | None = None,
relax: bool = False,
tau: float | None = None,
tol: float = 1e-09,
max_iterations: int = 20000,
) -> CrossField
Smoothest cross field satisfying constraints, with uniform edge weights.
Parameters:
-
background(BackgroundMesh) – -
constraints(list[Constraint], default:None) –Defaults to
from_boundary(background): tangent to every wall. -
relax(bool, default:False) –Keep
|u| = 1by diffusion + normalisation. See the module docstring. -
tau(float, default:None) –Diffusion time per step,
relaxonly. Defaults to(domain diagonal / 25) ** 2-- a property of the domain, so the steady state does not move when the background is refined. -
tol(optional, default:1e-09) –Steady-state test on
max|u_k+1 - u_k|,relaxonly. -
max_iterations(optional, default:1e-09) –Steady-state test on
max|u_k+1 - u_k|,relaxonly.
Returns:
FieldInputs
dataclass
¤
FieldInputs(
outer_boundary: Any,
inner_boundaries: Any = None,
guides: Any = None,
mode: str = "perpendicular",
target_length: float | None = None,
guide_weight: float | None = 1.0,
guide_band: float | None = None,
relax: bool = False,
tau: float | None = None,
symmetry: Any = "auto",
)
Everything a field is solved from, with the defaults; see CrossField.from_boundary.
-
Reference
framefield
Classes
FieldInputs Methods:resolved
BackgroundMesh
¤
BackgroundMesh(
mesh: Mesh,
outer: list[list[float]],
inners: list[list[list[float]]],
target_length: float,
)
A well-shaped triangulation of a planar domain, with boundary tangents.
Attributes:
-
mesh(Mesh) –The triangulation. All faces have positive area in XY.
-
outer(list[[x, y, z]]) –The discretised outer boundary, open (no repeated last point).
-
inners(list[list[[x, y, z]]]) –The discretised holes, same convention.
-
target_length(float) –The spacing the triangulation was built at.
-
Reference
framefield
Classes
BackgroundMesh Methods:from_boundary
Attributes¤
Methods:¤
boundary_tangents
¤
face_basis
¤
Orthonormal tangent basis of a face: world XY, on a planar domain.
from_boundary
classmethod
¤
from_boundary(
outer_boundary: list[list[float]],
inner_boundaries: list[list[list[float]]] | None = None,
target_length: float | None = None,
margin: float = 0.45,
symmetry: Symmetry | None = None,
alpha: float = 0.02,
d_min: int = 5,
) -> BackgroundMesh
Triangulate the region inside outer_boundary and outside the holes.
Parameters:
-
outer_boundary(list[[x, y, z]]) –The domain outline, closed or open.
-
inner_boundaries(list[list[[x, y, z]]], default:None) –Holes.
-
target_length(float, default:None) –Edge length, for the walls and the interior grid alike. Defaults to
alphatimes the bounding-box diagonal (thesis eq. 4.1, seediscretise_boundary). -
margin(float, default:0.45) –Interior points' clearance from the walls, as a fraction of
target_length. -
symmetry(Symmetry, default:None) –Passed to
interior_grid. -
alpha(float, default:0.02) –target_lengthas a fraction of the diagonal, when not given. -
d_min(int, default:5) –Fewest points per boundary loop.
Returns:
Constraint
module-attribute
¤
Constraint = namedtuple('Constraint', 'vkey direction weight')
- Reference framefield Constraints
-
Reference
framefield
Classes
CrossField Methods:solve
PointLocator
¤
PointLocator(background: BackgroundMesh)
Point location on a BackgroundMesh, using a face grid cached as plain arrays.
Parameters:
-
background(BackgroundMesh) –
-
Reference
framefield
Classes
CrossField Methods:locator
Methods:¤
locate
¤
locate(
point: list[float], hint: int | None = None
) -> tuple[int, tuple[float, float, float]] | None
Containing face and barycentric coordinates, or None if outside.
hint and its neighbours are tried first: consecutive queries almost
always land in the same or an adjacent triangle.
locate_arrays
¤
locate for a batch, without a hint, as arrays.
Returns:
-
(ndarray, ndarray, ndarray)–statusper point -- 0 when no face accepts it, 1 when exactly one does, 2 when several do (AMBIGUOUS: which onelocatepicks then depends on its hint) -- and, wherestatusis 1, the face's position infkeysand the barycentric coordinates. Status 0 and 1 are exactly whatlocatereturns for ANY hint.
locate_many
¤
locate_arrays as a list: per point (fkey, barycentric),
None, or AMBIGUOUS.
nearest
¤
The face whose centroid is closest, within the surrounding 3 x 3 cells,
with barycentric (1/3, 1/3, 1/3). For a point just outside the domain.
neighbours
¤
Edge-adjacent faces, in the order mesh.face_neighbors gives them.
Symmetry
¤
Symmetry(
centre: list[float],
elements: list[tuple[int, int, int, int, bool, str]],
steps: tuple[str, ...] = STEPS,
)
A subgroup of the square's 8 symmetries, about a centre.
Attributes:
-
centre([x, y, z]) – -
elements(list[tuple]) –A subset of
ELEMENTS, always containing the identity. -
steps(tuple[str]) –Which of
STEPSto apply.
-
Reference
framefield
Classes
Symmetry Methods:detect
Methods:¤
apply
¤
g applied to a point, about centre.
detect
classmethod
¤
detect(
loops: list[list[list[float]]],
centre: list[float] | None = None,
tol: float = 1e-06,
) -> Symmetry
The subgroup mapping the union of loops (walls, holes and guides) onto itself, as a point set.
Parameters:
-
loops(list[list[[x, y, z]]]) –EVERY curve the field will see -- outer boundary, holes and guides; a symmetric plate with an off-centre cable is not symmetric.
-
centre([x, y, z], default:None) –Defaults to the centre of the outer boundary's bounding box, which any subgroup of the square's symmetries fixes.
-
tol(float, default:1e-06) –Point-matching tolerance. Loose, because CAD sampling of a mirrored curve is not bit-exact.
Returns:
-
Symmetry–
Constraints¤
from_boundary
¤
from_boundary(
background: BackgroundMesh,
weight: float | None = None,
corner_tolerance: float = 0.1,
) -> list[Constraint]
Pin the field tangent to the wall at every boundary vertex; 45/135-degree corners are left free.
Parameters:
-
background(BackgroundMesh) – -
weight(float, default:None) –None(default) for hard constraints. -
corner_tolerance(float, default:0.1) –Averaged representations shorter than this leave the vertex free.
Returns:
-
list[Constraint]–
from_curves
¤
from_curves(
background: BackgroundMesh,
curves: Any,
mode: str = "perpendicular",
band: float | None = None,
weight: float | None = 1.0,
skip_boundary: bool = True,
) -> list[Constraint]
Pin the field along guide curves at every vertex within band, independent of curve order.
Parameters:
-
background(BackgroundMesh) – -
curves(list[list[[x, y, z]]]) –The guide curves; a single curve may also be passed. Points may be lists, tuples or compas
Points. -
mode(('perpendicular', 'tangent'), default:'perpendicular') –Whether mesh edges should run across the curve or along it. For a cross field the two are the same constraint; the parameter matters once the frame is not orthogonal.
-
band(float, default:None) –Only vertices within this distance of a curve are constrained. Defaults to
background.target_length. -
weight(float, default:1.0) –Least-squares weight, or
Nonefor hard. Soft by default: a guide that disagrees with a wall should bend, not break the solve. -
skip_boundary(bool, default:True) –Leave boundary vertices to
from_boundary; the wall must win.
Returns:
-
list[Constraint]–
Densification and relaxation¤
field_densification
¤
field_densification(
coarse: CoarsePseudoQuadMesh,
field: CrossField,
locator: Any = None,
edges_to_curves: dict[tuple[int, int], list[list[float]]] | None = None,
iterations: int = ITERATIONS,
tikhonov: float = TIKHONOV,
stiffness: tuple[float, ...] = STIFFNESS,
guard: bool = True,
spend: bool = False,
field_aware: bool = True,
early_exit: bool = True,
) -> tuple[Any, dict[str, Any]]
Densify a coarse layout with the field steering ordinary patch interiors.
A drop-in for CoarsePseudoQuadMesh.densification.
Parameters:
-
coarse(CoarsePseudoQuadMesh) –Strips collected and densities set.
-
field(CrossField) – -
locator(PointLocator, default:None) –Defaults to
field.locator(). ATraceris accepted too. -
edges_to_curves(dict, default:None) –{(u, v): polyline}. An edge missing from it densifies as a chord. -
iterations(optional, default:ITERATIONS) –Passed to
relax_patch. -
tikhonov(optional, default:ITERATIONS) –Passed to
relax_patch. -
stiffness(optional, default:ITERATIONS) –Passed to
relax_patch. -
guard(optional, default:ITERATIONS) –Passed to
relax_patch. -
spend(optional, default:ITERATIONS) –Passed to
relax_patch. -
early_exit(optional, default:ITERATIONS) –Passed to
relax_patch. -
field_aware(bool, default:True) –Falsereproducesdiscrete_coons_patchexactly, for A/B tests.
Returns:
-
(mesh, dict)–The dense mesh, and counts:
patches;relaxed,guardedandflat(fromrelax_patch);poles(pseudo-quads, kept Coons);coons(patches left Coons becausefield_awareis off);stiffness(per relaxed patch) andfolds.seam_edgemaps the geometric key of every patch-boundary node to(u, v, index)-- the coarse edge it lies on and its position along it -- whichrelax.relax_meshneeds to slide seam nodes without reordering them.
relax_mesh
¤
relax_mesh(
mesh: Mesh,
seam_edge: dict[str, tuple[int, int, int]] | None = None,
edges_to_curves: dict[tuple[int, int], list[list[float]]] | None = None,
corner_angle: float = 30.0,
schedule: Sequence[tuple[int, float]] = SCHEDULE,
seams: str = "free",
pin_singularities: bool = False,
) -> dict[str, Any]
Relax a dense mesh globally, its outline sliding along itself.
Modifies mesh in place, and only if no quality measure gets worse.
Parameters:
-
mesh(Mesh) –The dense mesh.
-
seam_edge(dict, default:None) –{geometric_key: (u, v, index)}, as inFieldDecomposition.densify_stats['seam_edge']. Needed forseamsother than'free'. -
edges_to_curves(dict, default:None) –{(u, v): [point, ...]}, fromFieldDecomposition.edges_to_curves. -
corner_angle(float, default:30.0) –Kink angle, in degrees, past which a boundary vertex is a pinned corner.
-
schedule(sequence[int, float], default:SCHEDULE) –(iterations, damping)settings, gentlest first. -
seams(('free', 'slide', 'fixed'), default:'free') –What the patch-seam vertices may do.
'free'(the default, and the only one measured to help) lets them leave their separatrix;'slide'keeps them on it;'fixed'pins them. -
pin_singularities(bool, default:False) –Hold irregular vertices and poles in place. Off by default.
Returns:
-
dict–accepted-- the setting used, orNoneif the mesh was left untouched;before/after--(min angle, max angle, max aspect);sliding/pinned-- how many vertices of each kind.
- Tutorial Smoothing and quality Smoothing functions
Quality¤
mesh_quality
¤
Element quality of a quad (or pseudo-quad) mesh, as plain numbers.
Parameters:
-
mesh(Mesh) – -
low_angle(float, default:LOW_ANGLE) –Threshold for
share_below, in degrees.
Returns:
-
dict–faces,poles,min_angle,max_angle,aspect_max,share_below,low_angle,irregular_interior, andworst_face-- the key of the face carryingmin_angle, so a failure can be looked at rather than merely counted.
Notes
Aspect ratio is longest edge over shortest edge, per face, worst over
the mesh. A perfect grid reads 1.00. It is inf when a face has a
zero-length edge, which is what hard_floor rejects on.
- Tutorial Smoothing and quality Measuring quality
curve_alignment
¤
curve_alignment(
mesh: Mesh,
curve: list[list[float]],
radius: float = ALIGNMENT_RADIUS,
spacing: float = 0.5,
) -> float
Mean of curve_alignment_profile, in degrees; check the profile too.
hard_floor
¤
guide_metrics
¤
How well a mesh carries its guides, as numbers rather than a verdict.
Parameters:
-
mesh(Mesh) –A quad or pseudo-quad mesh.
-
guides(sequence[sequence[point]]) –The guide curves, as polylines -- the same objects fed to the field.
-
steep(float, default:STEEP) –Degrees off horizontal past which a guide station counts as tilted.
Returns:
-
dict–faces-- faces the guides pass through, the population everything else is measured over. Zero means the metric could not see the guide at all, and every other value is None.along-- median angle between the guide and the face family nearer to it, in degrees. 0 is a course running along the guide.across/across_within/across_tilted-- median angle of the other family, the share of it withinACROSS_TOLERANCEof 90 degrees, and the median over tilted stations only. 90 is an interface meeting the guide square.off_centre/off_centre_worst-- the guide's offset from the face centre in units of half the face width across it. 0 is mid-block, 1 is on the joint.poles-- pseudo-quad faces skipped, having no two families to compare.