Proposed display names aligned to the naming grammar in Vocabulary §6:
Verb Object [(Backend)]
Status: display names APPLIED for Meshing (30, 2026-07-30), Attribute
(64, 2026-08-04), Creation (38, 2026-08-05), Geometry (38, 2026-08-26)
and Selection (30, 2026-08-27).
Counts are as of each round; the archive has grown to 327 filters since, so a root's
current size exceeds what its round covered.
Any total below other than 327 names the dated registry audited during that round;
it is not a current implementation count.
Remaining roots, largest first: Repair (24), Document (22),
Parametrization (17), Measurement (14), Transfer (13), Texture (3 plus 7 filters
sitting at the bare Texture root, which needs a subcategory decision first).
Filters added to a root after its round are not automatically conformant. A sweep on
2026-08-26 found exactly one such drift (Improve Triangulation (TrueForm), fixed in
round 4); eleven others flag only because they are dual-categorised into a root whose
round has not run yet, and will be renamed there.
Python names are shown too (verb_object[_backend]) because they should change in the
same breath, but they are pass 2 — they are the scripting contract, so they land
separately from the display names.
- Lead with a canonical verb — 61 % of current names do not.
- Never repeat the category — the menu already says
Meshing/Simplification, soSimplification: Clustering Decimationsays it twice. - Backend in parentheses only to disambiguate competing implementations — required by the algorithm-archive model, not decoration.
- Title Case, no irregular capitals —
Delete ALL Faces→Remove All Faces. Remove, notDelete— a settled ruling in the verb lexicon.
Currently every name begins Mesh Boolean:, repeating the category. The result of a
boolean is a conventionally named object, so these are named for the result rather than
the action — the noun-phrase exception recorded in Vocabulary §6.
| Current | Proposed | Python |
|---|---|---|
| Mesh Boolean: Union | Mesh Union | mesh_union |
| Mesh Boolean: Intersection | Mesh Intersection | mesh_intersection |
| Mesh Boolean: Difference | Mesh Difference | mesh_difference |
| Mesh Boolean: Symmetric Difference (XOR) | Mesh Symmetric Difference | mesh_symmetric_difference |
Keeping the nouns also means no new boolean verbs are needed — an earlier draft would
have added Unite/Intersect/Subtract/Exclude to the lexicon for these four filters
alone.
Pure application of the Remove-not-Delete ruling, plus the casing rule.
| Current | Proposed | Python |
|---|---|---|
| Delete ALL Faces | Remove All Faces | remove_all_faces |
| Delete Selected Faces | Remove Selected Faces | remove_selected_faces |
| Delete Selected Vertices | Remove Selected Vertices | remove_selected_vertices |
| Delete Selected Faces and Vertices | Remove Selected Faces and Vertices | remove_selected_faces_and_vertices |
| Current | Proposed | Python |
|---|---|---|
| Remeshing: Isotropic Explicit Remeshing | Remesh Isotropically | remesh_isotropically |
| Uniform Mesh Resampling | Remesh Uniformly by Volumetric Resampling | remesh_uniformly_by_volumetric_resampling |
| Curvature flipping optimization | Flip Edges by Curvature | flip_edges_by_curvature |
| Planar flipping optimization | Flip Edges by Planarity | flip_edges_by_planarity |
| Cut mesh along crease edges | Cut Along Crease Edges | cut_along_crease_edges |
Remesh Isotropically drops "Explicit": there is no implicit variant to distinguish it
from, so the word only added length. It stays in the description.
Remesh Uniformly by Volumetric Resampling names the method because the filter really is
volumetric — it samples a signed distance field on a regular grid and reconstructs with
marching cubes. An earlier draft read Resample Uniformly, which broke the verb lexicon:
Resample is a rejected synonym for Remesh on surfaces. The canonical verb leads,
and "resampling" survives only as the method.
Despite the marching cubes, it stays Meshing/Remeshing rather than
Creation/Reconstruction: the discriminator is the input, and here the input is an
existing valid mesh, not unstructured data from which a surface must be inferred.
Moved out of Remeshing into their own subcategory (applied): converting between
triangle and quad representation is a distinct concern from improving element shape, and
this set is expected to grow.
| Current | Proposed | Python |
|---|---|---|
| Tri to Quad by 4-8 Subdivision | Convert to Quads by 4-8 Subdivision | convert_to_quads_by_4_8_subdivision |
| Tri to Quad by smart triangle pairing | Convert to Quads by Triangle Pairing | convert_to_quads_by_triangle_pairing |
| Turn into Quad-Dominant mesh | Convert to Quad-Dominant Mesh | convert_to_quad_dominant_mesh |
| Turn into a Pure-Triangular mesh | Convert to Pure Triangles | convert_to_pure_triangles |
This is where the archive model bites: three quadric-edge-collapse implementations coexist, so the backend suffix is load-bearing rather than decorative.
| Current | Proposed | Python |
|---|---|---|
| Simplification: Quadric Edge Collapse Decimation | Simplify by Quadric Edge Collapse | simplify_by_quadric_edge_collapse |
| Simplification: Original QSlim Quadric Edge Collapse | Simplify by Quadric Edge Collapse (QSlim) | simplify_by_quadric_edge_collapse_qslim |
| Simplification: Quadric Edge Collapse Decimation (with texture) | Simplify by Quadric Edge Collapse with Texture | simplify_by_quadric_edge_collapse_with_texture |
| Simplification: Clustering Decimation | Simplify by Vertex Clustering | simplify_by_vertex_clustering |
| Simplification: Edge Collapse for Marching Cube meshes | Simplify Marching-Cubes Mesh by Edge Collapse | simplify_marching_cubes_mesh_by_edge_collapse |
| Point Cloud Simplification | Simplify Point Cloud | simplify_point_cloud |
"Decimation" disappears throughout — it is a rejected synonym for Simplify, and
Simplification: … Decimation said the same thing twice. "Original" is dropped from the
QSlim entry: the parenthesised backend already carries that information.
| Current | Proposed | Python |
|---|---|---|
| Subdivision Surfaces: Loop | Subdivide by Loop | subdivide_by_loop |
| Subdivision Surfaces: LS3 Loop | Subdivide by LS3 Loop | subdivide_by_ls3_loop |
| Subdivision Surfaces: Butterfly Subdivision | Subdivide by Butterfly | subdivide_by_butterfly |
| Subdivision Surfaces: Midpoint | Subdivide by Midpoint | subdivide_by_midpoint |
| Subdivision Surfaces: Catmull-Clark | Subdivide by Catmull-Clark | subdivide_by_catmull_clark |
| Subdivision Surfaces: Doo Sabin | Subdivide by Doo-Sabin | subdivide_by_doo_sabin |
| Refine User-Defined | Refine by User Expression | refine_by_user_expression |
The set becomes perfectly regular: Subdivide by <Scheme>. Doo Sabin gains its
conventional hyphen. Refine is kept for the one genuinely adaptive, predicate-driven
case, which is exactly the exception the verb lexicon allows.
Two, both now recorded in Vocabulary §3:
| Verb | Means | Why the existing lexicon was not enough |
|---|---|---|
Flip |
Reconnect by flipping edges; connectivity changes, vertices do not move | A precise, well-known mesh operation; Remesh is too coarse for it |
Cut |
Split a surface along a curve, introducing a boundary | Split is claimed by layer operations, so reusing it here would be ambiguous |
Should the triangle↔quad conversions have their own subcategory?Yes —Meshing/Quad, applied. The ontology and the four descriptors are updated; the set is expected to grow.IsKept as proposed.Simplify by Quadric Edge Collapse with Texturetoo long?Verb or noun for the booleans?Noun:Mesh Union,Mesh Intersection,Mesh Difference,Mesh Symmetric Difference. Recorded in the vocabulary as the named-result exception so they are not "corrected" back to verb-first.
Applied: all 30 display names. Verified against the 272-filter registry present on 2026-07-30: no old name survived as a filter name, no display names were duplicated, JSON remained intact, and the build and tests were clean.
Note for whoever regenerates the docs: docs/api/filters.md is generated from the
descriptors; fix stale filter entries by running --generate-docs, not by hand-editing
individual generated sections.
Status: APPLIED (2026-08-04). 56 names changed, 8 already conformed, 1 filter
removed as a duplicate. Verified mechanically against the 277-filter registry present
on 2026-08-04: JSON valid across all 31 descriptor files, no old name survived as a
filter name, no display name was duplicated, every proposed name was present, the
build was clean, and test results were unchanged from the pre-change
baseline (FilterTests 22/1, FilterCreationTests 19/0, DocumentTests 15/6 — the
failures are pre-existing and were confirmed identical with the changes stashed).
The largest root and the least regular: 61 of the 65 names do not lead with a verb, and
the same operation is spelled three different ways across plugins (Re-Compute,
Compute, bare noun). It is also where the scalar-not-quality ruling
(Vocabulary §4) first bites — 17 names say Quality today.
Three cross-cutting patterns do most of the work:
| Pattern | Applies to | Example |
|---|---|---|
Compute <Object> by Expression |
the 8 filter_expression entries |
Per Vertex Color Function → Compute Vertex Color by Expression |
Sharpen <Object> by Unsharp Mask |
the 3 unsharp entries here (a 4th is in Geometry) |
UnSharp Mask Color → Sharpen Vertex Color by Unsharp Mask |
Compute <Object> (<Backend>) |
competing curvature / geodesic implementations | Discrete Curvatures → Compute Curvature (Discrete) |
| Current | Proposed | Python |
|---|---|---|
| Re-Compute Vertex Normals | Compute Vertex Normals | compute_vertex_normals |
| Re-Compute Face Normals | Compute Face Normals | compute_face_normals |
| Re-Compute Per-Polygon Face Normals | Compute Polygon Face Normals | compute_polygon_face_normals |
| Compute normals for point sets | Compute Point Cloud Normals | compute_point_cloud_normals |
| Per Vertex Normal Function | Compute Vertex Normals by Expression | compute_vertex_normals_by_expression |
| Per Face Normal Function | Compute Face Normals by Expression | compute_face_normals_by_expression |
| Normalize Vertex Normals | Normalize Vertex Normals | normalize_vertex_normals |
| Normalize Face Normals | Normalize Face Normals | normalize_face_normals |
| Re-Orient vertex normals using cameras | Orient Vertex Normals by Cameras | orient_vertex_normals_by_cameras |
| Reorient Face Normals by Geometry | Orient Face Normals by Ray Casting | orient_face_normals_by_ray_casting |
| Smooth Face Normals | Smooth Face Normals | smooth_face_normals |
| Smooth normals on point sets | Smooth Point Cloud Normals | smooth_point_cloud_normals |
| UnSharp Mask Normals | Sharpen Face Normals by Unsharp Mask | sharpen_face_normals_by_unsharp_mask |
Re-Compute → Compute is the casing rule in Vocabulary §5 applied
literally; the "Re-" carried no information, since every one of these overwrites whatever
was there.
Orient Face Normals by Ray Casting keeps its method in the name because it competes
with Repair/Topology's coherent-orientation filter, which works purely topologically.
The two are now visibly a pair rather than two unrelated names.
The Quality → Scalar root-and-branch pass. Note that in the geodesic family the word
disappears entirely rather than being replaced: the object of the sentence is the
distance, and the category already says the result is a scalar.
| Current | Proposed | Python |
|---|---|---|
| Compute Border Distance Quality | Compute Geodesic Distance from Border | compute_geodesic_distance_from_border |
| Compute Geodesic Distance Quality from Point | Compute Geodesic Distance from Point | compute_geodesic_distance_from_point |
| Compute Geodesic Distance Quality from Selection | Compute Geodesic Distance from Selection | compute_geodesic_distance_from_selection |
| Compute Heat Geodesic Distance Quality from Selection | Compute Geodesic Distance from Selection (Heat Method) | compute_geodesic_distance_from_selection_heat |
| Compute Ambient Occlusion | Compute Face Ambient Occlusion | compute_face_ambient_occlusion |
| — | Compute Point Cloud Ambient Occlusion | compute_point_cloud_ambient_occlusion |
| Compute Obscurance | Compute Obscurance | compute_obscurance |
| Compute Shape Diameter Function | Compute Shape Diameter Function | compute_shape_diameter_function |
| Generate Scalar Harmonic Field | Compute Harmonic Scalar Field | compute_harmonic_scalar_field |
| Per Face Geometric Quality | Compute Face Scalar from Geometry | compute_face_scalar_from_geometry |
| Per Face Texture Distortion | Compute UV Distortion | compute_uv_distortion |
| Quality from raster coverage (Vertex) | Compute Vertex Scalar from Raster Coverage | compute_vertex_scalar_from_raster_coverage |
| Quality from raster coverage (Face) | Compute Face Scalar from Raster Coverage | compute_face_scalar_from_raster_coverage |
| Vertex Quality from Camera | Compute Vertex Scalar from Camera | compute_vertex_scalar_from_camera |
| Per Vertex Quality Function | Compute Vertex Scalar by Expression | compute_vertex_scalar_by_expression |
| Per Face Quality Function | Compute Face Scalar by Expression | compute_face_scalar_by_expression |
| Clamp Vertex Quality | Clamp Vertex Scalar | clamp_vertex_scalar |
| Saturate Vertex Quality | Clamp Vertex Scalar Gradient | clamp_vertex_scalar_gradient |
| Smooth Vertex Quality | Smooth Vertex Scalar | smooth_vertex_scalar |
| UnSharp Mask Quality | Sharpen Vertex Scalar by Unsharp Mask | sharpen_vertex_scalar_by_unsharp_mask |
Generate → Compute: Generate is a rejected synonym for Create, and Create means
produce a new layer. This filter writes an attribute on the current mesh, so the verb
is Compute.
Saturate Vertex Quality is the one outright translation: nothing about it saturates anything: it bounds the spatial gradient of the field so values cannot change faster than a given rate. Clamp Vertex Scalar Gradient says what it does, and pairs it with the filter directly above:
| Bounds | |
|---|---|
Clamp Vertex Scalar |
the values |
Clamp Vertex Scalar Gradient |
the rate of change of the values |
Caveat recorded so the name is not "corrected" later: the implementation is one-sided, only ever decreasing values, so it is not a symmetric two-sided clamp. It does enforce |∇q| ≤ threshold, which is a clamp of the gradient, and the description spells out the conservative behaviour.
Three competing curvature estimators plus the directions filter. Backend in parentheses
is load-bearing here, exactly as in Meshing/Simplification.
| Current | Proposed | Python |
|---|---|---|
| Discrete Curvatures | Compute Curvature (Discrete) | compute_curvature_discrete |
| Compute APSS Curvature Quality | Compute Curvature (APSS) | compute_curvature_apss |
| Compute RIMLS Curvature Quality | Compute Curvature (RIMLS) | compute_curvature_rimls |
| Compute curvature principal directions | Compute Principal Curvature Directions | compute_principal_curvature_directions |
The three estimators become directly comparable in the menu, which is the point of the
archive model. Today they sort apart (Compute APSS…, Compute RIMLS…, Discrete…) and
read as unrelated filters.
| Current | Proposed | Python |
|---|---|---|
| Define New Per Vertex Custom Scalar Attribute | Define Custom Vertex Scalar Attribute | define_custom_vertex_scalar_attribute |
| Define New Per Vertex Custom Point Attribute | Define Custom Vertex Point Attribute | define_custom_vertex_point_attribute |
| Define New Per Face Custom Scalar Attribute | Define Custom Face Scalar Attribute | define_custom_face_scalar_attribute |
| Define New Per Face Custom Point Attribute | Define Custom Face Point Attribute | define_custom_face_point_attribute |
Only New and Per are dropped, both redundant. Define is kept rather than converted
to the lexicon's Set — see ruling 2.
One filter removed, not renamed. Quality Mapper applier was a duplicate of Colorize by vertex Quality: identical parameters, identical categories and input/output declarations, and literally the same code path (
kFilterMapVQuality || kFilterQualityMapperin oneif). It survived as a MeshLab compatibility shim — in MeshLab it was driven by the Quality Mapper dialog, an editable transfer-function curve that QMeshLab never ported, so the filter was wired to the plain ramp and became a clone. Descriptor entry and dispatch branch deleted; no alias kept, since a consistent Python API for QMeshLab outranks pymeshlab script compatibility (ruling 5). A real transfer-function editor remains a legitimate future feature, and the name Colorize Vertices by Scalar Transfer Function is reserved for it.
The Colorize / Set split is the organising idea, and it resolves the Colorize vs
Compute confusion flagged at the end of round 1:
Colorize— colour derived from other data (a scalar field, a distance, a noise field). Reading the mesh tells you something.Set— colour assigned, carrying no information about the geometry (a fixed colour, a random one per component).Adjust/Invert/ … — colour edited, an image-processing operation on colour that is already there.
| Current | Proposed | Python |
|---|---|---|
| Colorize by vertex Quality | Colorize Vertices by Scalar | colorize_vertices_by_scalar |
| Colorize by face Quality | Colorize Faces by Scalar | colorize_faces_by_scalar |
| Perlin color | Colorize Vertices by Perlin Noise | colorize_vertices_by_perlin_noise |
| Disk Vertex Coloring | Colorize Vertices by Disk Distance | colorize_vertices_by_disk_distance |
| Voronoi Vertex Coloring | Colorize Vertices by Voronoi Regions | colorize_vertices_by_voronoi_regions |
| Per Vertex Color Function | Compute Vertex Color by Expression | compute_vertex_color_by_expression |
| Per Face Color Function | Compute Face Color by Expression | compute_face_color_by_expression |
| Vertex Color Filling | Set Vertex Color | set_vertex_color |
| Set Mesh Color | Set Mesh Color | set_mesh_color |
| Random Face Color | Set Random Face Color | set_random_face_color |
| Random Component Color | Set Random Component Color | set_random_component_color |
| PerMesh Color Scattering | Set Random Layer Color | set_random_layer_color |
| Vertex Color Brightness Contrast Gamma | Adjust Vertex Color Brightness/Contrast/Gamma | adjust_vertex_color_brightness_contrast_gamma |
| Vertex Color Levels Adjustment | Adjust Vertex Color Levels | adjust_vertex_color_levels |
| Vertex Color White Balance | Adjust Vertex Color White Balance | adjust_vertex_color_white_balance |
| Vertex Color Invert | Invert Vertex Color | invert_vertex_color |
| Vertex Color Desaturation | Desaturate Vertex Color | desaturate_vertex_color |
| Vertex Color Thresholding | Threshold Vertex Color | threshold_vertex_color |
| Vertex Color Colourisation | Tint Vertex Color | tint_vertex_color |
| Equalize Vertex Color | Equalize Vertex Color | equalize_vertex_color |
| Color noise | Add Noise to Vertex Color | add_noise_to_vertex_color |
| Smooth: Laplacian Vertex Color | Smooth Vertex Color | smooth_vertex_color |
| Smooth: Laplacian Face Color | Smooth Face Color | smooth_face_color |
| UnSharp Mask Color | Sharpen Vertex Color by Unsharp Mask | sharpen_vertex_color_by_unsharp_mask |
PerMesh Color Scattering → Set Random Layer Color applies the mesh vs layer
ruling: it iterates over document layers and gives each a distinct colour, so it is a
layer operation even though what it writes is a mesh attribute.
Smooth: Laplacian … drops the method: Laplacian is the only smoothing offered, and if a
second scheme ever lands it comes back as Smooth Vertex Color (Taubin).
Two new general verbs, plus a group of standard attribute-editing operations that are better recorded as a set than as ten separate lexicon rows.
| Verb | Means | Why the existing lexicon was not enough |
|---|---|---|
Orient |
Make normals point consistently, or toward a reference | Distinct from Compute (values are flipped, not recalculated); also the canonical form of Re-Orient/Reorient |
Sharpen |
Enhance local variation in an attribute | The four unsharp-mask filters had no verb at all; Sharpen X by Unsharp Mask matches Simplify by … |
Attribute-editing verbs (standard image/signal operations, admitted as a closed
group): Normalize, Adjust, Clamp, Invert, Equalize, Desaturate, Threshold,
Tint. Each keeps its ordinary meaning; none may be used where Compute (derive from
geometry) or Set (assign a constant) is accurate.
Define is also admitted, but narrowly: declaring a new named custom attribute, and
nothing else. It is otherwise still a rejected synonym for Set.
Run before applying round 2, prompted by finding that Quality Mapper applier was a clone. Five independent tests covered the 277 filters present on 2026-08-04:
| Test | What it looks for | Hits | True positives |
|---|---|---|---|
| A | Identical behavioural signature (parameters + domains + requirements + outputModifies + categories) |
12 groups | 0 |
| B | Two or more filter ids sharing one dispatch branch | 9 groups | 0 |
| C | Shared branch where the ids are never re-tested inside — the Quality Mapper signature | 1 | 0 |
| D | Cross-plugin name-token overlap ≥ 0.6 | 12 pairs | 0 (1 naming defect) |
| E | Duplicate id, pythonName, or display name |
0 | 0 |
Result: no further clones. Quality Mapper applier was the only one.
Test A is weak alone — filters with no parameters collide trivially (the five Platonic
solids, the four igl booleans, three no-parameter selections), as do families that differ
only in an internal constant (Subdivide by Butterfly / by Midpoint). Test C is the
sharp one, and its single hit is the APSS-vs-RIMLS surface factory in filter_mls, where
the three ids legitimately share "use APSS" and are discriminated at three later sites.
-
Create Selection Perimeter Polylinehas a wrong description. It says the polyline is "composed by the selected edges of the current mesh"; the code walks selected faces and emits each edge whose neighbour is unselected. Its siblingBuild a Polyline from Selected Edgesis the one that reads the edge selection (BuildFromFaceEdgeSel). Two genuinely different filters whose descriptions claim the same input. -
Two indistinguishable names for two different algorithms — the only real find of test D, and one to fix in the
Transferround rather than here:Name Plugin Mechanism Transfer Color: Texture to Vertex colorprocexact per-wedge UV lookup, same mesh only Transfer: Texture to Vertex Color textureclosest-point resampling in world space, across two layers, with a distance bound Neither is redundant: the first is the exact fast path, the second the only one that crosses layers. But the names differ by word order alone, which is unusable. The second needs its mechanism in the name.
-
No filter can declare "requires a selection". Both polyline filters check for a selection at runtime and fail with a message; the
inputRequirementsschema has keys for vertices, edges, faces, colour, texcoords, textures and quality, but none for selection state, so the UI cannot grey them out. A framework gap, noted not fixed.
All settled (2026-08-04).
-
Are the slashes inYes, kept. The filter really does all three.Adjust Vertex Color Brightness/Contrast/Gammaacceptable? -
DefineorSetfor custom attributes — orCreate?Define, admitted narrowly.Createwas considered and rejected: it carries the invariant a new layer appears in the layer list, which holds exactly today — every filter namedCreate *declaresoutputDomain: NewMeshes, while all four custom-attribute filters areModifyCurrentMesh. SpendingCreatehere would break a rule that makes the menu readable, for the sake of four filters.Setwas rejected because these declare a new named attribute rather than assign to an existing one. -
Saturate→Limit … Gradient?Clamp Vertex Scalar Gradient— better than the proposal, since it reuses a lexicon verb instead of addingLimitand pairs the filter withClamp Vertex Scalardirectly above it. -
Yes, proceed. Verified that the three are genuinely competing implementations of one quantity rather than different quantities sharing a word:Compute Curvature (Discrete)demotes the familiar name into a parenthesis — worth the regularity?Filter Method Writes Offers Discrete Curvatures discrete differential operators (Meyer et al.) VQMean / Gaussian / RMS / ABS Compute APSS Curvature Quality algebraic point set surfaces (MLS) VQMean / Gauss / K1 / K2 / ApproxMean Compute RIMLS Curvature Quality robust implicit MLS VQMean / Gauss / K1 / K2 Same output slot, same quantity, overlapping type enums — exactly what the parenthesised backend exists for, and the same trade already accepted across
Meshing. Search still finds "discrete" via the description.Compute curvature principal directions stays apart on merit: it is the only one writing
VA, and itsMethodenum is five further algorithms (Taubin, PCA, Normal Cycles, Quadric Fitting, Scale-Dependent Quadric Fitting). Its unique output is the directions, so it is named for them. -
Keep MeshLabNo. A consistent Python API for QMeshLab outranks pymeshlab script compatibility. Pass 2 therefore renames allpythonNames as aliases when renaming?pythonNames outright, with no alias mechanism and no deprecation window. Recorded here because it removes a prerequisite that pass 2 would otherwise have needed.
Status: APPLIED (2026-08-05). 37 names changed, 1 already conformed. Verified
mechanically against the 277-filter registry present on that date: JSON valid across
all descriptor files, no old name survived, no display name was duplicated, every
proposed name was present, the build was clean, and tests were unchanged
from baseline (FilterTests 22/1, FilterCreationTests 19/0, DocumentTests 15/6 —
all pre-existing).
Structurally the easiest root — three subcategories, each with an obvious canonical verb
(Create, Reconstruct, Sample) — but it contains the one genuinely contested
decision of the whole naming effort. See ruling 1.
| Current | Proposed | Python |
|---|---|---|
| Annulus | Create Annulus | create_annulus |
| Box/Cube | Create Box | create_box |
| Cone | Create Cone | create_cone |
| Dodecahedron | Create Dodecahedron | create_dodecahedron |
| Dodecahedron (symmetric) | Create Symmetric Dodecahedron | create_symmetric_dodecahedron |
| Icosahedron | Create Icosahedron | create_icosahedron |
| Octahedron | Create Octahedron | create_octahedron |
| Sphere | Create Sphere | create_sphere |
| Sphere Cap | Create Sphere Cap | create_sphere_cap |
| Tetrahedron | Create Tetrahedron | create_tetrahedron |
| Torus | Create Torus | create_torus |
| Points on a Sphere | Create Points on a Sphere | create_points_on_a_sphere |
| — | Create Points on a Spherical Cap | create_points_on_a_spherical_cap |
| Grid Generator | Create Grid | create_grid |
| Implicit Surface | Create Isosurface from Expression | create_isosurface_from_expression |
| Noisy Isosurface | Create Isosurface from Perlin Noise | create_isosurface_from_perlin_noise |
| Fit Plane to Selection | Create Plane from Selection | create_plane_from_selection |
| Compute Planar Section | Create Polyline from Planar Section | create_polyline_from_planar_section |
| Build a Polyline from Selected Edges | Create Polyline from Selected Edges | create_polyline_from_selected_edges |
| Create Selection Perimeter Polyline | Create Polyline from Selection Perimeter | create_polyline_from_selection_perimeter |
| Create Solid Wireframe | Create Solid Wireframe | create_solid_wireframe |
| Voronoi Scaffolding | Create Voronoi Scaffolding | create_voronoi_scaffolding |
The three polyline filters become one family, distinguished by the input at the end of the name. This is the direct fix for the confusion the duplicate sweep turned up: two of them previously read as near-identical (Build a Polyline from Selected Edges / Create Selection Perimeter Polyline) and their descriptions claimed the same input.
Compute Planar Section changes verb: it produces a new layer, so Compute — which the
lexicon defines as calculate and store an attribute — was simply the wrong word.
Fit Plane to Selection → Create Plane from Selection because Fit is a rejected
synonym for Align, and the filter's observable result is a new quad layer.
Implicit Surface / Noisy Isosurface become a visible pair: both build a scalar field on a grid and run marching cubes, differing only in where the field comes from.
Surface Reconstruction: repeats the category, so it goes; Reconstruct leads.
| Current | Proposed | Python |
|---|---|---|
| Surface Reconstruction: Screened Poisson | Reconstruct Surface by Screened Poisson | reconstruct_surface_by_screened_poisson |
| Surface Reconstruction: SSD | Reconstruct Surface by Smooth Signed Distance | reconstruct_surface_by_smooth_signed_distance |
| Surface Reconstruction: Ball Pivoting | Reconstruct Surface by Ball Pivoting (vcglib) | reconstruct_surface_by_ball_pivoting_vcglib |
| Surface Reconstruction: VCG | Reconstruct Surface by Volumetric Merging | reconstruct_surface_by_volumetric_merging |
| Alpha Wrap | Reconstruct Surface by Alpha Wrapping | reconstruct_surface_by_alpha_wrapping |
| Marching Cubes (APSS) | Reconstruct Surface by Marching Cubes (APSS) | reconstruct_surface_by_marching_cubes_apss |
| Marching Cubes (RIMLS) | Reconstruct Surface by Marching Cubes (RIMLS) | reconstruct_surface_by_marching_cubes_rimls |
| Surface Reconstruction: Surface Trimmer | Trim Surface by Scalar Isovalue | trim_surface_by_scalar_isovalue |
Surface Reconstruction: VCG was named for its library, which decision 3 forbids in a category and which tells the user nothing here either. The algorithm is volumetric merging of range maps, so that is what the name says; the backend parenthesis is dropped because nothing competes with it.
Surface Reconstruction: Surface Trimmer is not a reconstruction at all — it is the post-processing companion to Poisson. See ruling 3.
Becomes fully regular: Sample <what> by <method>.
| Current | Proposed | Python |
|---|---|---|
| Montecarlo Sampling | Sample Surface by Monte Carlo | sample_surface_by_monte_carlo |
| Poisson-disk Sampling | Sample Surface by Poisson Disk | sample_surface_by_poisson_disk |
| Stratified Triangle Sampling | Sample Surface by Stratified Triangles | sample_surface_by_stratified_triangles |
| Voronoi Sampling | Sample Surface by Voronoi Relaxation | sample_surface_by_voronoi_relaxation |
| Clustered Vertex Sampling | Sample Vertices by Clustering | sample_vertices_by_clustering |
| Mesh Element Sampling | Sample Mesh Elements | sample_mesh_elements |
| Regular Recursive Sampling | Sample Offset Surface Recursively | sample_offset_surface_recursively |
| Texel Sampling | Sample Texels | sample_texels |
| Volumetric Sampling | Sample Volume | sample_volume |
Montecarlo gains its conventional space. Nothing else here is contentious: the noun
after Sample says what is sampled, the method follows by.
One, and it is contested — see ruling 3.
| Verb | Means | Why the existing lexicon was not enough |
|---|---|---|
Trim |
Cut a surface along an isovalue of a scalar field and discard one side | Neither Remove (which deletes whole elements matching a predicate) nor Cut (which introduces a boundary but keeps both sides) describes it |
All settled (2026-08-05).
-
Does every primitive need the wordYes. Fourteen filters were bare nouns, and rule 2 (never repeat the category) plus the named-result exception both argued for leaving them.Create?Createwins because it carries an invariant — a new layer appears in the layer list — that held across the 277-filter registry audited on 2026-08-05 and was the deciding argument in round 2 ruling 2, whereCreatewas refused to the custom-attribute filters precisely to protect it. Dropping the verb from the filters that actually create layers would make the invariant invisible where it matters most. Two supporting reasons: the subcategory is not uniformly noun-able (7 of 21 are not named objects), and filters are reached by flat search and Python, where a bareSpheredoes not say what will happen. -
Spell outYes —SSD?Reconstruct Surface by Smooth Signed Distance. Long, but parallel with Screened Poisson and it keeps the no-unexplained-abbreviations rule intact for the Python name. -
Verified in code, andTrimas a new verb — and is the filter's behaviour really what the description claims?Trimis admitted.runSurfaceTrimmerImplinpoissonrecon_backend.cpp:splitPolygon(..., trimValue)splits every polygon along the isovalue of vertex quality, creating new vertices interpolated on the cut edges;- only
gtPolygons— the side above the threshold — is kept; - small disconnected islands are optionally dropped (
islandAreaRatio,removeIslands); - the result is triangulated and the mesh rebuilt in place.
So
Remove Faces by Scalar Thresholdwould be actively wrong: vertices appear that were not in the input, and no whole face is tested against a predicate.Removecannot describe this;Cutkeeps both sides.Trimis the term of art and earns its lexicon entry.The filter stays in
Creation/Reconstruction: it is meaningless except as the post-processing companion to Poisson-family reconstruction, and moving it away from the filters it exists to serve would help nobody.
An earlier draft of this section flagged Surface Reconstruction: Ball Pivoting as
inconsistent for declaring ModifyCurrentMesh where every other reconstruction declares
NewMeshes. That was wrong, and the distinction it missed is worth recording as a
rule.
| Mechanism | Filters | outputDomain |
|---|---|---|
| Approximating — fit an implicit field and extract it, or build an offset surface. Output vertices are new. | Screened Poisson, Smooth Signed Distance, Volumetric Merging, Alpha Wrapping, Marching Cubes (APSS), Marching Cubes (RIMLS) | NewMeshes — 6/6 |
| Interpolating — the input points are the output vertices; only connectivity is added. | Ball Pivoting | ModifyCurrentMesh — 1/1 |
vcg::tri::BallPivoting<VCGMesh> pivot(mesh, ...) runs on the layer's own mesh and adds
faces to the existing vertex set, so ModifyCurrentMesh is not an oversight but the
honest declaration: nothing is destroyed, the point cloud is still there and now carries
a surface. Emitting a new layer would duplicate every point for no gain.
Alpha Wrapping is approximating, despite the name. Worth stating outright, because
alpha shapes are the opposite: an alpha complex (Edelsbrunner-Mucke) is a subcomplex of
the Delaunay triangulation of the input points, so its vertices are the input points — a
textbook interpolating reconstruction. CGAL's alpha_wrap_3 is a different and much newer
algorithm (Alpha Wrapping with an Offset, Portaneri et al. 2022) that shares only the
Greek letter: it refines a 3D Delaunay triangulation with Steiner points and carves it,
and its Offset parameter must be strictly positive, so the output surface cannot
pass through the input points. Same letter, opposite side of the line.
Found while checking the above. cgalfilterplugin.cpp called only the triangle soup
overload, and buildTriangleSoup failed with "requires at least one valid triangular
face" whenever the face list was empty; the descriptor declared requireFaces: true to
match. CGAL also provides a point set overload (alpha_wrap_3(points, alpha, offset, out), routing to oracle.add_point_set) which needs no normals — unlike Poisson,
the strictly positive offset defines the envelope by itself. The face requirement was
QMeshLab's wiring, not the algorithm's.
Fixed (2026-08-05): buildTriangleSoup now tolerates an empty face list, the call
site branches to the point-set overload, requireFaces is dropped, the info message
reports "Input point set: N points", and the description explains both paths. Covered by
FilterTests::cgalAlphaWrapAcceptsPointClouds, which strips faces through the real
Remove All Faces filter and then wraps the resulting cloud.
The two paths are not equivalent, and the description now says so. They select structurally different CGAL oracles:
| Triangle soup | Point set | |
|---|---|---|
| Oracle | Triangle_soup_oracle |
Point_set_oracle |
| AABB tree over | triangles (AABB_triangle_primitive_3) |
points (AABB_primitive over vector<Point_3>) |
alpha at construction |
yes | no |
| Subdivides the input | yes — AABB_tree_oracle_splitter, Splitter_base(alpha) |
no |
CGAL states the alpha coupling outright in Triangle_soup_oracle: "the oracle will be
adapted to this particular 'alpha', and so when calling again AW3(other_alpha) the oracle
might not have performed a split that is adapted to this other alpha value."
So with faces, triangle interiors are solid to the wrap and oversized faces are split
to the alpha scale; without faces the input is isolated points and the envelope comes from
the offset around each one. Where a triangle spans a wide gap the point-set path sees only
its three corners, and the ball can roll between them and dent or hole the result. The
point-cloud path is sound only when sampling is dense relative to Alpha and Offset —
if a layer has faces, keep them.
This also settles a classification doubt raised earlier in this document: an earlier note
here suggested Alpha Wrap might be mis-filed, since "its input is a mesh, not a point
cloud" — but that was an accident of the wiring. With point clouds accepted it is
unambiguously an unstructured-input-to-surface reconstruction, so
Creation/Reconstruction is correct and there is no case for moving it next to
Repair Watertight Mesh (MeshFix). It remains approximating either way.
Ball Pivoting is currently the only interpolating reconstruction in the tree — there
is no Delaunay, convex hull, or advancing-front filter in filter_cgal, filter_igl,
filter_meshfix or filter_clean. The rule is recorded because that is exactly the
family an igl plugin would add:
Ball Pivoting is the only reconstruction that works in place, on the current mesh, optionally discarding the original face set. Every other reconstruction emits a new layer.
That is the whole of it, and it is a design choice about where the result goes, not a property of the algorithm. Two earlier drafts of this section tried to derive it from the mathematics and both were wrong:
- "interpolating ->
ModifyCurrentMesh, approximating ->NewMeshes" — MeshLab's convex hull breaks this immediately: interpolating, yet it must emit a new layer. - "preserves the entire input vertex set ->
ModifyCurrentMesh" — Ball Pivoting does not preserve it in any useful sense. It deletes nothing, but it does not connect everything either:clusteringis passed to vcglib'sminr, i.e.min_edge, a minimum edge length, so points nearer than that to an existing vertex are skipped, as are points the ball cannot reach. They survive as unreferenced vertices.
The interpolating/approximating distinction is still worth knowing (it is why Alpha
Wrapping never touches the input points, and it is the family the qhull filters belong
to), but it does not determine outputDomain. Nothing algorithmic does. A convex hull
could be computed in place too, by replacing the mesh; MeshLab simply chose not to.
The open design question, then, is a UX one rather than a classification one: should reconstruction be uniform — always a new layer, so the input survives for comparison — or is in-place Ball Pivoting worth keeping as the "give my point cloud a surface" workflow? Left as-is for now; recorded so the inconsistency is a decision rather than an accident.
MeshLab's filter_qhull provides four filters with no QMeshLab counterpart:
FP_QHULL_CONVEX_HULL (generate_convex_hull), FP_QHULL_VORONOI_FILTERING
(Amenta-Bern reconstruction), FP_QHULL_ALPHA_COMPLEX_AND_SHAPE, and
FP_QHULL_VISIBLE_POINTS. The first three are the interpolating-reconstruction family
whose absence is noted above; the fourth is a viewpoint-visibility filter and belongs with
Selection/by Visibility and ViewpointOccluder, not here.
MeshLab computes the hull with Qhull, but vcglib/vcg/complex/algorithms/convex_hull.h
already exists, so a port needs no new external dependency.
Applied 2026-08-26. 33 renamed, 5 already conformant. Python names are recorded for pass 2 and were not applied.
Ten of the twelve led with a colon prefix that restated the category.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Matrix: Freeze Current Matrix | Freeze Matrix | freeze_matrix |
| Matrix: Invert Current Matrix | Invert Matrix | invert_matrix |
| Matrix: Reset Current Matrix | Set Matrix to Identity | set_matrix_to_identity |
| Matrix: Set from translation/rotation/scale | Set Matrix from Translation/Rotation/Scale | set_matrix_from_trs |
| Matrix: Set/Copy Transformation | Set Matrix from Values or Layer | set_matrix_from_values_or_layer |
| Transform: Align to Principal Axis | Align to Principal Axes | align_to_principal_axes |
| Transform: Flip and/or swap axis | Mirror or Swap Axes | mirror_or_swap_axes |
| Transform: Rotate | Rotate | rotate |
| Transform: Rotate to Fit to a plane | Rotate to Fitted Plane | rotate_to_fitted_plane |
| Transform: Scale, Normalize | Scale | scale |
| Transform: Translate, Center, set Origin | Translate | translate |
| Normalize To Unit Box | Normalize to Unit Box | normalize_to_unit_box |
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Laplacian Smooth | Smooth Vertices by Laplacian | smooth_vertices_by_laplacian |
| Laplacian Smooth (surface preserving) | Smooth Vertices by Laplacian (Surface Preserving) | smooth_vertices_by_laplacian_surface_preserving |
| HC Laplacian Smooth | Smooth Vertices by HC Laplacian | smooth_vertices_by_hc_laplacian |
| ScaleDependent Laplacian Smooth | Smooth Vertices by Scale-Dependent Laplacian | smooth_vertices_by_scale_dependent_laplacian |
| Taubin Smooth | Smooth Vertices by Taubin | smooth_vertices_by_taubin |
| TwoStep Smooth | Smooth Vertices by Two-Step Normal Fitting | smooth_vertices_by_two_step_normal_fitting |
| Depth Smooth | Smooth Vertices along One Direction | smooth_vertices_along_one_direction |
| Directional Geometry Preservation | Project Vertices onto the Line of Sight | project_vertices_onto_line_of_sight |
| UnSharp Mask Geometry | Sharpen Vertices by Unsharp Mask | sharpen_vertices_by_unsharp_mask |
| MLS projection (APSS) | Project Vertices onto MLS Surface (APSS) | project_vertices_onto_mls_surface_apss |
| MLS projection (RIMLS) | Project Vertices onto MLS Surface (RIMLS) | project_vertices_onto_mls_surface_rimls |
| Smooth Vertices by Laplacian (TrueForm) | unchanged | |
| Smooth Vertices by Taubin (TrueForm) | unchanged |
Renaming the two vcg smoothers completes two competing sets whose TrueForm halves were
already in final form, under the convention the archive has settled into: the incumbent
stays unsuffixed, the challenger carries the backend (Remove Duplicate Vertices +
… (TrueForm), Simplify by Quadric Edge Collapse + … (QSlim)).
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Displace Vertices Randomly | unchanged | |
| Displace by Fractal Brownian Motion | Displace Vertices by Fractal Brownian Motion | displace_vertices_by_fractal_brownian_motion |
| Displace by Heterogeneous Multifractal Noise | Displace Vertices by Heterogeneous Multifractal Noise | displace_vertices_by_heterogeneous_multifractal_noise |
| Displace by Hybrid Multifractal Noise | Displace Vertices by Hybrid Multifractal Noise | displace_vertices_by_hybrid_multifractal_noise |
| Displace by Ridged Multifractal Noise | Displace Vertices by Ridged Multifractal Noise | displace_vertices_by_ridged_multifractal_noise |
| Displace by Standard Multifractal Noise | Displace Vertices by Standard Multifractal Noise | displace_vertices_by_standard_multifractal_noise |
| Per Vertex Geometric Function | Compute Vertex Coordinates by Expression | compute_vertex_coordinates_by_expression |
| Vertex Linear Morphing | Displace Vertices toward Target Mesh | displace_vertices_toward_target_mesh |
Compute … by Expression matches the six names ratified in round 2.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Align by Bounding Box | unchanged | |
| Align to Corresponding Points | unchanged | |
| Align by ICP (TrueForm) | unchanged | |
| ICP Between Meshes | Align by ICP | align_by_icp |
| Global Align Meshes | Align Meshes Globally | align_meshes_globally |
| Verb | Means | Why the existing lexicon was not enough |
|---|---|---|
Mirror |
Negate one or more axes | Flip was given a single meaning in round 1 — reconnect by flipping edges, vertices do not move — which is the opposite of this |
Project |
Move vertices onto existing geometry, or onto a line | Admitted narrowly, for geometric projection only. It remains a rejected synonym for Transfer wherever attributes move between domains |
Swap is used as an ordinary word inside Mirror or Swap Axes, not as a lexicon verb.
All settled (2026-08-26).
No —Flipfor axis mirroring?MirrorandSwapare better and leaveFlipto edges.How much of a variant list belongs in a name?Trim to the bare verb. TheTransform/Matrixgroup is expected to be split later into pureTranslate,RotateandScaleplus a targeted canonicalizing filter, and the bare verbs are the foundation that split needs.Both kept, case fixed. It already centres and uniformly scales, so it is the seed of the future "normalize / make canonical" filter; the split movesNormalize To Unit BoxoverlapsScale'sunitFlag.unitFlagout ofScaleinto it.Reading the implementation settled it: withDirectional Geometry Preservation— what is it?othe stored position,pthe current one anddthe unit vector from the viewpoint too, it computeso + d·((p−o)·d). That is an orthogonal projection of the vertex onto its own sight line, keeping the along-view part of a previous smoothing and discarding the lateral part. Project Vertices onto the Line of Sight. The old description was wrong twice over — it said "blend" (there is no mixing factor) and "preservation" (every vertex moves); it has been rewritten.Admitted, narrowly — see the lexicon table.Projector route around it?No.Morphas a verb?Displacecovers it, and "morphing" stays in the description so search still finds it.Meshes.MeshesorLayers?Drift:Remesh by Edge Flipping (TrueForm), folded into this round. It was the only filter added after its own round that broke rule 1.Improve Triangulation (TrueForm)?
Project Vertices onto the Line of Sight— description rewritten (see ruling 4).- Both
viewPointparameters infilter_unsharpgained"point3fDefaultPreset": "cameraEye". Both filters are defined by the viewer's position and neither offered the preset. - Progress labels in
filter_icp,filter_mlsandfilter_trueformfollowed their filters' new names; they are user-visible.
docs/design/filter_classification.md and filter_organization.md still carry the old
names. Both are dated snapshots of an audit, like the "Current" column above, and are left
alone on purpose. docs/api/filters.md is generated from the descriptors.
Applied 2026-08-27. 12 renamed, 18 already conformant.
All five kept. Dilate Selection, Erode Selection and Invert Selection were already
right; the round's work here was admitting the two verbs (below) so the guard accepts
them.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Conditional Face Selection | Select Faces by Expression | select_faces_by_expression |
| Conditional Vertex Selection | Select Vertices by Expression | select_vertices_by_expression |
| Select Faces with Edges Longer Than... | Select Faces by Edge Length | select_faces_by_edge_length |
| Select by Face Quality | Select Faces by Scalar | select_faces_by_scalar |
| Select by Vertex Quality | Select Vertices by Scalar | select_vertices_by_scalar |
| Select Vertices Inside Mesh | Select Vertices Inside Mesh (TrueForm) | select_vertices_inside_mesh_trueform |
| Select Faces by Color · by View Angle · Select Outliers | unchanged |
by Expression matches the six Compute … by Expression names ratified in round 2 —
the same muparser machinery, and the descriptor ids already said
select_faces_by_condition. The trailing ... was a MeshLab dialog marker.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Select non Manifold Edges | Select Non-Manifold Edges (vcglib) | select_non_manifold_edges_vcglib |
| Select non Manifold Vertices | Select Non-Manifold Vertices | select_non_manifold_vertices |
| Select Self Intersecting Faces | Select Self-Intersecting Faces | select_self_intersecting_faces |
| Select small disconnected component | Select Small Disconnected Components | select_small_disconnected_components |
| Select 'Problematic' Faces | Select Ill-Shaped Faces | select_ill_shaped_faces |
| the other 8 | unchanged |
Select non Manifold Edges was the last bare incumbent in the archive — it escaped the
(vcglib) sweep only because its casing differed from its (TrueForm) counterpart.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Select by Rectangle (Screen) | Select by Screen Rectangle | select_by_screen_rectangle |
| Select Visible Faces · Select Visible Vertices | unchanged |
(Screen) is neither a backend nor an algorithm, so under rule 3 it does not belong in
parentheses: it says what the filter is, and belongs in the name.
| Verb | Means | Why the existing lexicon was not enough |
|---|---|---|
Dilate, Erode |
Grow or shrink the selection by one ring of adjacent elements | The standard morphological pair. Select names the act of selecting, not these two operations on a selection that already exists |
All settled (2026-08-27).
AdmitAdmit both. The three existing names were already good; renaming them to avoid two lexicon rows would have been the wrong trade.Dilate/Erode, or rename toExpand/Shrink Selection?Suffix added.Select Vertices Inside Meshcarried no backend suffix, alone among the TrueForm filters.→ Select Ill-Shaped Faces. The scare quotes were doing the work of a definition; the filter selects elongated, flipped or folded faces. Select Degenerate Faces was rejected as confusable with theSelect 'Problematic' FacesRepair/Degeneratesubcategory, which means zero-area.
Selection was added to appliedRoots in displayNamesLeadWithALexiconVerb, so the
round is now checked on every build.
Applied 2026-09-01. 14 renamed, 10 already conformant. All 24 carry Repair/* as
their primary category, so nothing here was owned by another round.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Remove Isolated Pieces (wrt Diameter) | Remove Isolated Components by Diameter | remove_isolated_components_by_diameter |
| Remove Isolated Pieces (wrt Face Num.) | Remove Isolated Components by Face Count | remove_isolated_components_by_face_count |
| Remove Vertices wrt Quality | Remove Vertices by Scalar | remove_vertices_by_scalar |
| Remove Zero Area Faces | Remove Zero-Area Faces | unchanged |
wrt is an abbreviation the vocabulary does not carry, and rule 3 reserves parentheses
for the backend, so both (wrt …) become by connectors. Pieces → Components
follows round 5's Select Small Disconnected Components; Quality → Scalar is §4;
Zero-Area hyphenates as a compound adjective, like round 5's Non-Manifold.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Merge Wedge Texture Coord | Merge Close Wedge UVs | merge_close_wedge_uvs |
| Vertex Attribute Seam | Split Vertices by Attribute Seam | split_vertices_by_attribute_seam |
| the other 5 | unchanged |
§4 makes UV the user-facing term and Coord was a bare abbreviation; the description
says "merge very close per-wedge texture coordinates", so restoring Close also puts the
name beside its sibling Merge Close Vertices.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Close Holes | unchanged — Close admitted to the lexicon |
unchanged |
| Snap Mismatched Borders | Repair Mismatched Borders | repair_mismatched_borders |
The snap filter splits faces so two borders coincide and only welds vertices when
unify_vertices is set, so Merge would have over-promised. Snap stays in the
description.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Extract Outer Shell | Extract Outer Shell (TrueForm) | extract_outer_shell_trueform |
| Invert Faces Orientation | Invert Face Orientation | invert_face_orientation |
| Orient Faces Coherently (TrueForm) | Orient Faces Consistently (TrueForm) | orient_faces_consistently_trueform |
| Re-Orient all faces coherently | Orient Faces Consistently (vcglib) | orient_faces_consistently_vcglib |
| Repair Self-Intersections | Repair Self-Intersections (TrueForm) | repair_self_intersections_trueform |
| Repair non Manifold Edges | Repair Non-Manifold Edges | unchanged |
| Repair non Manifold Vertices by Splitting | Repair Non-Manifold Vertices by Splitting | repair_non_manifold_vertices_by_splitting |
| the other 4 | unchanged |
Re-Orient is struck by §5 by name and listed as a rejected synonym in §3.
Coherently → Consistently is §3's own wording for Orient, and the vcglib filter's
shortDescription already said "Orient faces consistently" — only the display name
lagged. Neither non-manifold repair takes a suffix: nothing competes with them.
| Verb | Means | Why the existing lexicon was not enough |
|---|---|---|
Close |
Fill a boundary loop with new faces | Close a hole is the term of art; Repair is too coarse to separate it from the four other Repair/* filters |
Split was widened, not added: it was scoped to layer-structure operations, and
Cut's row claimed it outright for them. It now also covers separating mesh elements
that were sharing storage, which is what Split Vertices by Attribute Seam does.
All settled (2026-09-01).
AdmitClose Holes: admitClose, or rename toRepair Holes?Close.Split Vertices by Attribute Seam, withVertex Attribute Seamleads with no verb at all.Splitwidened as above. Convert to Seam-Independent Vertices would have needed no lexicon change but read worse.Do the two unsuffixed TrueForm filters here takeEvery TrueForm filter takes it — see below.(TrueForm)?
Ruling 3 came back wider than the round: 19 of 31 TrueForm filters were suffixed and 12
were not, so the suffix had stopped meaning "this is TrueForm" and started meaning "this
one happened to have a sibling". The other 10 were renamed with this round, across
already-applied roots (Attribute, Creation, Geometry, Meshing) and one pending
one (Measurement, whose names are still open to its own round):
Compute Signed Distance to Mesh · Create Polyline from Self-Intersections · Create Polyline from Mesh Intersection · Create Polyline from Scalar Isocontour · Create Tube from Polyline · Align by Bounding Box · Align to Corresponding Points · Measure Chamfer Distance · Mesh CSG Expression · Cut Along Scalar Isocontour
§6 records the amendment: the omission clause still governs a backend that is not a named family the user chooses between.
- Two descriptions cross-referenced filters this round renamed (Orient Faces Outward pointed at Orient Faces Coherently, Repair Self-Intersections at Extract Outer Shell); both now name the current filter, suffix included.
beginFilterProgresslabels in the TrueForm plugin repeat the display name verbatim, so all 12 were updated with it; likewise the undo label and failure message of Split Vertices by Attribute Seam in the meshing plugin.- Description vocabulary: five
Delete …openings →Remove(§3 rejectsDeleteoutright), twoquality→scalar, threetexture coord(inate)s→UVs, two Britishneighbouring→neighboring, onenon Manifold→non-manifold, and the typotrhesholdin Close Holes.
Repair was added to appliedRoots in displayNamesLeadWithALexiconVerb. Verified to
discriminate: restoring Snap Mismatched Borders fails the guard with
leading word 'Snap' is not in the lexicon.
Applied 2026-09-01. 18 renamed, 4 already conformant. All 22 carry Document/* as
their primary category. 17 of the 22 led with a word outside the lexicon — by some margin
the least conformant root, because none of it had been touched since the MeshLab import.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Export active rasters cameras to file | Export Cameras from Visible Rasters | export_cameras_from_visible_rasters |
| Import cameras for active rasters from file | Import Cameras to Visible Rasters | import_cameras_to_visible_rasters |
| Generate Camera from Direction | Set Camera from Direction | unchanged |
| Generate Camera to View Selection | Set Camera to View Selection | unchanged |
| Transform camera extrinsics | Transform Camera Extrinsics | transform_camera_extrinsics |
| Transform: Rotate Camera or set of cameras | Rotate Cameras | rotate_cameras |
| Transform: Scale Camera or set of cameras | Scale Cameras | scale_cameras |
| Transform: Translate Camera or set of cameras | Translate Cameras | translate_cameras |
| Set Mesh Camera · Set Raster Camera | unchanged |
Generate is a rejected synonym for Create, but neither verb applied: these assign the
current view rather than producing a layer, and the ids already said set_camera_…. The
three Transform: names carried the colon form the guard rejects outright;
Geometry/Transform ships bare Rotate/Scale/Translate, so the camera set needs only
the object its own category does not supply.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Delete Current Mesh | Remove Current Mesh Layer | remove_current_mesh_layer |
| Delete Current Raster | Remove Current Raster | remove_current_raster |
| Delete all non visible Mesh Layers | Remove Hidden Mesh Layers | remove_hidden_mesh_layers |
| Delete all Non Selected Rasters | Remove Hidden Rasters | remove_hidden_rasters |
| Duplicate Current layer | Duplicate Current Layer | unchanged |
| Flatten Visible Layers | Merge Visible Layers | merge_visible_layers |
| Move selected faces to another layer | Extract Selected Faces | extract_selected_faces |
| Move selected vertices to another layer | Extract Selected Vertices | extract_selected_vertices |
| Rename Current Mesh | Rename Current Mesh Layer | rename_mesh_layer |
| Rename Current Raster | unchanged | |
| Split in Connected Components | Split into Connected Components | split_into_connected_components |
Delete → Remove is settled by §3. "Mesh Layer" appears only where a raster
counterpart exists, so each pair disambiguates itself; Duplicate, Merge and Split
have no raster twin and stay plain "Layer".
Flatten is a rejected synonym (for Transfer and Parametrize) and the operation is a
merge. MergeVisible defaults to true, so Merge Visible Layers names the default
honestly while the parameter widens it; flatten stays in the description.
Move is rejected in favour of Translate and would read as moving geometry through
space. These produce a new layer from part of an existing one, which is Extract — and
because Extract implies the new layer, "to another layer" drops out entirely. The
DeleteOriginal parameter still chooses move-versus-copy.
Render from Render-State JSON — unchanged; Render admitted below.
| Verb | Means | Why the existing lexicon was not enough |
|---|---|---|
Import, Export |
Read or write data in a file outside the document | Nothing covered file I/O. Transfer is defined over domains and layers; Load/Save are the File menu's document-level operations, and a filter writing a camera rig — or a multiresolution build — beside the mesh is not saving the document |
Rename |
Change the label of a layer or raster | Set Mesh Name would have needed no new verb and matched the ids, but rename is the word a user searches for |
Render |
Produce an image from the scene | Create produces a layer, not an image; nothing else was close |
All settled (2026-09-01).
File I/O has no verb:Export/Import, orSave/Load?Export/Import, which also covers the planned Nexus generation filters.AdmitRename: admit it, or useSet … Name?Rename.Admitted narrowly.Render.
- Delete all Non Selected Rasters did not delete unselected rasters: it tests
!doc.raster(i).visible, so it removes hidden ones. The name, the description and both of its runtime messages all repeated the same wrong claim; all four corrected. - Nine descriptions still opened with
Delete …or described the old verb (move or duplicate, flatten); rewritten to match the filters they document, keeping flatten as a searchable synonym. - Two prose references in code — a comment in
Document::runFilterOnVisibleMeshesand one in the layer-index invariant test — named filters this round renamed.
Document was added to appliedRoots. Verified to discriminate: restoring
Flatten Visible Layers fails the guard with
leading word 'Flatten' is not in the lexicon.
Applied 2026-09-01. All 17 renamed — the only round in which nothing was already
conformant. Two further filters carry Parametrization as a secondary category
(Select Vertex Texture Seams, Pack Texture Images) and belong to their own rounds.
The round's shape is one decision: Parametrize owns UV creation outright. The verb
had been in the lexicon since the ontology was written and had no users at all — thirteen
filters produced UVs under five different leading words. All thirteen now lead with it.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Compute As-Rigid-As-Possible Parametrization (libigl) | Parametrize by As-Rigid-As-Possible (libigl) | parametrize_by_as_rigid_as_possible_libigl |
| Compute Locally Injective Parametrization (SLIM) | Parametrize by SLIM (libigl) | parametrize_by_slim_libigl |
| Harmonic Parametrization | Parametrize by Harmonic Map (libigl) | parametrize_by_harmonic_map_libigl |
| Least Squares Conformal Maps Parametrization | Parametrize by Least Squares Conformal Maps (libigl) | parametrize_by_least_squares_conformal_maps_libigl |
| Geometric Cylindrical Unwrapping | Parametrize by Cylindrical Projection | parametrize_by_cylindrical_projection |
| Parameterization from registered rasters | Parametrize from Registered Rasters | parametrize_from_registered_rasters |
| Parameterization + texturing from registered rasters | Parametrize from Registered Rasters with Texture | parametrize_from_registered_rasters_with_texture |
| Parametrization: Flat Plane | Parametrize by Flat Plane | parametrize_by_flat_plane |
| Parametrization: Trivial Per-Triangle | Parametrize by Trivial Per-Triangle Layout | parametrize_by_trivial_per_triangle_layout |
| Parametrization: Voronoi Atlas | Parametrize by Voronoi Atlas (vcglib) | parametrize_by_voronoi_atlas_vcglib |
| Parametrization: xatlas | Parametrize by Atlas (xatlas) | parametrize_by_atlas_xatlas |
| Per Vertex Texture Function | Parametrize per Vertex by Expression | parametrize_per_vertex_by_expression |
| Per Wedge Texture Function | Parametrize per Wedge by Expression | parametrize_per_wedge_by_expression |
Four Parametrization: colon names, one rejected Unwrap, two rejected Parameterization
spellings (§4), two verbless noun phrases, and four leading with Compute — legal in
itself, but not when a verb exists for exactly this attribute.
Voronoi Atlas takes (vcglib): it is vcg/…/parametrization/voronoi_atlas.h, and it
competes with xatlas as a route to an atlas, which is when rule 3 says both name
themselves.
Method names follow the literature rather than a house style: As-Rigid-As-Possible and Least Squares Conformal Maps read naturally expanded, SLIM does not — nobody writes Scalable Locally Injective Mappings in full.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Convert PerVertex UV into PerWedge UV | Convert Per-Vertex UV to Per-Wedge UV | convert_per_vertex_uv_to_per_wedge_uv |
| Convert PerWedge UV into PerVertex UV | Convert Per-Wedge UV to Per-Vertex UV | convert_per_wedge_uv_to_per_vertex_uv |
PerVertex is the irregular internal capital §5 names by example; per-vertex is the §4
qualifier. into → to, the ratified connector.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Small Islands Remover | Merge Small Texture Islands | merge_small_texture_islands |
| Texture Map Defragmentation | Defragment Texture Atlas | defragment_texture_atlas |
Both were verbless noun phrases. Small Islands Remover removes nothing: its own description says it merges small islands into neighbours that share a seam.
| Verb | Means | Why the existing lexicon was not enough |
|---|---|---|
Defragment |
Reduce the fragmentation of a texture atlas by merging compatible charts and resampling | The term of the paper the filter implements (Maggiordomo et al. 2021, already cited in the descriptor). Merge covers only half of it — it also resamples — and Pack means arranging charts, not merging them |
Parametrize was not added: it was already ratified and simply unused.
with joins by, from and to, for an additional output a filter also produces:
Parametrize from Registered Rasters with Texture beside Parametrize from Registered
Rasters. §6 records that it is not a fifth way of saying by — it is for the case where
the alternative is a second verb.
All settled (2026-09-01).
DoesParametrizeown UV creation, or mayComputekeep the four libigl filters?Parametrizeowns it outright, the two expression filters included.AdmitYes.Defragment?Parametrize and Transfer Texture from Registered Rasters or … with Texture?with Texture, which is what promotedwithto a connector.
- Two libigl messages had the display name substituted into them where the method name was the correct noun ("Parametrize by Least Squares Conformal Maps (libigl) failed."); reworded to name the method.
- Progress labels in the libigl, texture, xatlas and defragmentation plugins repeat the display name verbatim and were updated with it.
plugins/filter_texture_defragmentation/upstream/is a vendored third-party tree and was deliberately left untouched, though it contains six occurrences of the old name.
Parametrization was added to appliedRoots. Verified to discriminate: restoring
Small Islands Remover fails the guard with
leading word 'Small' is not in the lexicon.
Applied 2026-09-01. 13 renamed, 1 already conformant. One further filter carries
Measurement as a secondary category (Create Plane from Selection) and belongs to
its own round.
The round turns on a distinction §3 already draws and nothing had enforced: Measure
reports values without modifying, Compute calculates and stores an attribute.
outputModifies settles it filter by filter, and exactly one of the fourteen stores
anything.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Compute Area/Perimeter of Selection | Measure Selection Area and Perimeter | measure_selection_area_and_perimeter |
| Compute Geometric Measures | Measure Geometric Properties | measure_geometric_properties |
| Hausdorff Distance | Measure Hausdorff Distance | measure_hausdorff_distance |
| Overlapping Meshes | Measure Layer Overlap | measure_layer_overlap |
| Distance from Reference Mesh | Compute Distance from Reference Mesh | compute_distance_from_reference_mesh |
| Measure Chamfer Distance (TrueForm) | unchanged |
Distance from Reference Mesh is the one Compute: it declares outputModifies: ["VQ"]
and writes the distance into vertex quality. Hausdorff Distance reports statistics and
produces sample layers only as an optional by-product, so it stays Measure.
The / in Area/Perimeter was never a connector, and rewriting that name removed the
last real use of of in any shipped name — the only other occurrence sits inside the
fixed phrase line of sight. No new connector was needed for it.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Estimate radius from density | Estimate Radius from Density | estimate_radius_from_density |
| Per Face Quality Histogram | Measure Face Scalar Histogram | measure_face_scalar_histogram |
| Per Face Quality Stat | Measure Face Scalar Statistics | measure_face_scalar_statistics |
| Per Vertex Quality Histogram | Measure Vertex Scalar Histogram | measure_vertex_scalar_histogram |
| Per Vertex Quality Stat | Measure Vertex Scalar Statistics | measure_vertex_scalar_statistics |
Quality → Scalar is §4; Stat is an abbreviation §5 does not carry.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Compute Topological Measures | Measure Topological Properties | measure_topological_properties |
| Compute Topological Measures for Quad Meshes | Measure Topological Properties for Quad Mesh | measure_topological_properties_for_quad_mesh |
| Current Mesh Info | Measure Mesh Summary | measure_mesh_summary |
Info is a rejected synonym of Measure. "Measures" could not survive beside the verb,
so the pair became "Properties"; keeping the two parallel is what promoted for to a
connector rather than splitting them into differently shaped names.
The vocabulary permitted Estimate and the guard rejected it. §3 admitted it in the
footnote below the table — "permitted only when the result is explicitly statistical or
approximate" — and that footnote's own worked example is Estimate Radius from Density,
this exact filter. But the ratified set is defined as everything above that footnote,
which is where round 6's guard truncates, so the parser saw 55 verbs and Estimate was
not one of them.
Doc and enforcement had disagreed since the truncation landed, and nothing caught it
because no shipped filter led with Estimate. It now has a table row of its own, above
the footnote; the footnote stays, because it is also the marker the guard truncates on.
for names the class of input a specialised variant is built for, distinguishing it from
the general filter: Measure Topological Properties for Quad Mesh beside Measure
Topological Properties. Like with in round 8, it arrived with exactly one user.
All settled (2026-09-01).
"Properties" or "Geometry"/"Topology" as the object?Properties.The quad variant breaks the parallel with its sibling.for Quad Mesh, which admittedfor.
Measurement was added to appliedRoots. Verified to discriminate: restoring
Current Mesh Info fails the guard with
leading word 'Current' is not in the lexicon.
Applied 2026-09-01. All 13 renamed, and the three subcategories renamed and
remembered with them. Nine names carried the Verb: colon form the guard rejects
outright, three led with Project, and one was a verbless noun phrase.
Every name collapses onto one shape: Transfer <attribute> from <source> to <target>.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Project Current Raster Color to Current Mesh | Transfer Color from Current Raster to Vertex | transfer_color_from_current_raster_to_vertex |
| Project Active Rasters Color to Current Mesh | Transfer Color from Visible Rasters to Vertex | transfer_color_from_visible_rasters_to_vertex |
| Project Active Rasters Color to Current Mesh Texture | Transfer Color from Visible Rasters to Texture | transfer_color_from_visible_rasters_to_texture |
No ruling was needed: §3 settles it outright — "Transfer, not Project. Project
described only the raster sub-case." Project was later readmitted, but only for moving
vertices onto geometry, and these move colour. "Active" → "Visible" follows the
descriptions, which already said "all visible rasters".
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Transfer Color: Vertex to Face | Transfer Color from Vertex to Face | transfer_color_from_vertex_to_face |
| Transfer Color: Mesh to Face | Transfer Color from Mesh to Face | transfer_color_from_mesh_to_face |
| Transfer Color: Face to Vertex | Transfer Color from Face to Vertex | transfer_color_from_face_to_vertex |
| Transfer Color: Texture to Vertex | Transfer Color from Texture to Vertex | transfer_color_from_texture_to_vertex |
| Transfer Quality: Vertex to Face | Transfer Scalar from Vertex to Face | transfer_scalar_from_vertex_to_face |
| Transfer Quality: Face to Vertex | Transfer Scalar from Face to Vertex | transfer_scalar_from_face_to_vertex |
| Transfer: Vertex Color to Texture | Transfer Color from Vertex to Texture | transfer_color_from_vertex_to_texture |
Quality → Scalar is §4; the ids already said compute_scalar_transfer_…, so only the
label had lagged.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Vertex Attribute Transfer | Transfer Vertex Attributes by Closest Point | transfer_vertex_attributes_by_closest_point |
| Transfer: Texture to Vertex Color | Transfer Color from Texture to Vertex by Closest Point | transfer_color_from_texture_to_vertex_by_closest_point |
| Transfer: Vertex Attributes to Texture | Transfer Vertex Attributes to Texture by Closest Point | transfer_vertex_attributes_to_texture_by_closest_point |
The old split mixed axes — Mesh to Mesh beside Attribute to Texture — and six of the
seven filters under Mesh to Mesh never went mesh to mesh at all: they moved an attribute
between elements of a single mesh. The distinction that actually matters is whether a
correspondence has to be constructed:
| Subcategory | What supplies the correspondence |
|---|---|
Within a Mesh |
The mesh's own incidence. A face already knows its vertices; nothing is chosen and nothing can be wrong |
Between Layers |
A computed match between two layers — closest point on the source surface, bounded by upperBound |
From Rasters |
The raster's camera |
Membership follows the parameter signature, not the name: exactly three filters carry
sourceMesh/targetMesh/upperBound, and those three are Between Layers. Two of them
had been filed under Attribute to Texture, and Transfer: Vertex Color to Texture — same
mesh, no source/target pair — had been filed with them; all three moved.
by Closest Point names that mechanism on the three cross-layer filters. It is the
standard term for what they do, it explains what upperBound bounds, and it avoids
overloading Project, which §3 keeps for moving vertices onto geometry.
"Domain" was considered and rejected for the subcategory names: §2 already rules that
inputDomain/outputDomain mean document scope, so the word is spoken for. "Between
Elements" fails too, because a texture is not one of §4's elements.
All settled (2026-09-01).
How to mark the cross-layer three?by Closest Point, overfrom Another Layer: less short, more true, and it names the mechanism rather than the arity.Subcategories renamed toMesh to Meshmisdescribes its contents.Within a Mesh/Between Layers/From Rasters, and three filters re-homed.Same Meshwas the runner-up toWithin a Mesh; the prepositional form pairs withBetween Layersas an inside/outside pair, whichSame Meshdoes not.
Transfer was added to appliedRoots. Verified to discriminate: restoring
Vertex Attribute Transfer fails the guard with
leading word 'Vertex' is not in the lexicon.
Applied 2026-09-01. 1 renamed, 2 already conformant. The last round of pass 1.
Eight further filters carry a bare Texture as a secondary category — the raster
transfers, the defragmentation pair, Pack UV Charts, Simplify by Quadric Edge Collapse
with Texture (vcglib) — and were renamed by their own rounds.
| Current | Proposed | Python (pass 2) |
|---|---|---|
| Convert: Object-Space Normal Map to Tangent-Space | Convert Object-Space Normal Map to Tangent Space | convert_object_space_normal_map_to_tangent_space |
The last Verb: colon name in the archive. Convert was already the right verb and to
already the right connector; only the colon had to go. The trailing hyphen goes with it:
object-space is a compound adjective adjacent to the noun it modifies and keeps its
hyphen, while to tangent space is a prepositional phrase and never needed one.
Set Texture and Pack Texture Images were already conformant.
Pack was defined as "Arrange UV charts in an atlas", but Pack Texture Images arranges
whole images, not charts — the entry under-described its own shipped user, and had done
since it was written. It now reads "Arrange UV charts, or whole texture images, into an
atlas". The two filters now form a pair distinguished only by their object, which is what
the grammar is for: Pack UV Charts beside Pack Texture Images.
Texture was added to appliedRoots, completing the set: all eleven roots are now
checked on every build. Verified to discriminate: restoring
Convert: Object-Space Normal Map to Tangent-Space fails the guard with
repeats the category with a colon — the colon branch of the check, which no earlier
round had exercised on its own.
All 328 filters across 11 roots carry a display name that leads with a ratified verb and
follows Verb Object [(Backend)]. The lexicon closed at 56 verbs and 5 connectors
(by, from, to, with, for).
Ids and pythonNames are untouched by design and are pass 2: 250 of 328 pythonNames
and 280 of 328 ids no longer match their display name, plus parameter ids on top.