diff --git a/.zenodo.json b/.zenodo.json index 3c2ee45..579ba97 100644 --- a/.zenodo.json +++ b/.zenodo.json @@ -52,6 +52,33 @@ { "id": "021nxhr62::2118240" } + ], + "references": [ + "Blender Development Team (2026). Blender (Version 5.1) [Computer software]. https://www.blender.org", + "Contreras-Figueroa, G. & Aragón, J. L. (2023). A mathematical model for mollusc shells based on parametric surfaces and the construction of theoretical morphospaces. Diversity 15, 431. https://doi.org/10.3390/d15030431", + "Okabe, T. & Yoshimura, J. (2017). Optimal designs of mollusk shells from bivalves to snails. Scientific Reports 7, 42445. https://doi.org/10.1038/srep42445", + "Porto, A. MorphoWeave: Atlas-based 3D landmark transfer and statistical shape modeling. GitHub repository. https://github.com/agporto/SlicerMorphoWeave" + ], + "related_identifiers": [ + { + "identifier": "https://www.blender.org", + "relation": "requires", + "resource_type": "software" + }, + { + "identifier": "10.3390/d15030431", + "relation": "cites", + "resource_type": "publication-article" + }, + { + "identifier": "10.1038/srep42445", + "relation": "cites", + "resource_type": "publication-article" + }, + { + "identifier": "https://github.com/agporto/SlicerMorphoWeave", + "relation": "cites", + "resource_type": "software" + } ] } - diff --git a/README.md b/README.md index 9ac0a65..fecf86d 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ TraitBlender is a Blender add-on for generating museum-style images and 3D meshe - **Shell (Default)** — A 3D shell morphospace that combines the parametric coiling model of [Contreras-Figueroa & Aragón (2023)](https://doi.org/10.3390/d15030431) with thickness allometry from [Okabe & Yoshimura (2017)](https://doi.org/10.1038/srep42445). Traits control whorl expansion, aperture shape, ribbing, and scaling; hyperparameters tune mesh resolution and options. - **Circle Grid** — A simple morphospace (e.g. for testing): a gridded plane of circles with configurable traits. -- **ATLAS** — Deforms a template mesh from an [ATLAS DATABASE](https://github.com/agporto/ATLAS) export using PCA on dense correspondences and local RBF interpolation (same workflow as the ATLAS DATABASE explorer). +- **MorphoWeave** — Deforms a template mesh from a [MorphoWeave](https://github.com/agporto/SlicerMorphoWeave) export using PCA on dense correspondences and local RBF interpolation (same workflow as the MorphoWeave Model Library / SSM Explorer). **Dataset import, editing, and saving** — Specimen trait values are read from CSV, TSV, or Excel. The built-in dataset editor supports filtering, sorting, pagination, and saving changes back to file, so you can curate and tweak trait tables without leaving Blender. diff --git a/traitblender/assets/morphospace_modules/ATLAS/morphospace/__init__.py b/traitblender/assets/morphospace_modules/ATLAS/morphospace/__init__.py deleted file mode 100644 index 9e41742..0000000 --- a/traitblender/assets/morphospace_modules/ATLAS/morphospace/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -from .atlas_morphospace import ( - generate_atlas_sample, - pc_values_from_trait_kwargs, - resolve_n_modes_for_hyperparams, -) -from .atlas_morphospace_sample import AtlasMorphospaceSample - -__all__ = [ - "generate_atlas_sample", - "AtlasMorphospaceSample", - "pc_values_from_trait_kwargs", - "resolve_n_modes_for_hyperparams", -] diff --git a/traitblender/assets/morphospace_modules/ATLAS/__init__.py b/traitblender/assets/morphospace_modules/MorphoWeave/__init__.py similarity index 69% rename from traitblender/assets/morphospace_modules/ATLAS/__init__.py rename to traitblender/assets/morphospace_modules/MorphoWeave/__init__.py index 402a612..17b9446 100644 --- a/traitblender/assets/morphospace_modules/ATLAS/__init__.py +++ b/traitblender/assets/morphospace_modules/MorphoWeave/__init__.py @@ -1,20 +1,21 @@ """ -ATLAS morphospace: template-centered PCA on dense correspondences + Local RBF warp. +MorphoWeave morphospace: template-centered PCA on dense correspondences + Local RBF warp. -Traits ``pc1`` … ``pcK`` are σ-multiples (ATLAS DATABASE sliders). Reconstruction: -``target = template_dense + Σ z_i √(λ_i) mode_i``, then Local RBF mesh warp (LPS/RAS aware). +Traits ``pc1`` … ``pcK`` are σ-multiples (MorphoWeave Model Library / SSM Explorer sliders). +Reconstruction: ``target = template_dense + Σ z_i √(λ_i) mode_i``, then Local RBF mesh warp +(LPS/RAS aware). """ from __future__ import annotations from .morphospace import ( - generate_atlas_sample, + generate_morphoweave_sample, pc_values_from_trait_kwargs, resolve_n_modes_for_hyperparams, ) from .orientations import ORIENTATIONS -NAME = "ATLAS" +NAME = "MorphoWeave" HYPERPARAMETERS = { "database_dir": "", @@ -33,9 +34,9 @@ def get_trait_parameters_with_defaults(merged_hyperparameters): return {f"pc{i}": 0.0 for i in range(1, n + 1)} -def sample(name="ATLAS", hyperparameters=None, **traits): +def sample(name="MorphoWeave", hyperparameters=None, **traits): """Generate a specimen; PC coefficients are passed as pc1, pc2, … keyword traits.""" - return generate_atlas_sample( + return generate_morphoweave_sample( name, hyperparameters or {}, pc_values_from_trait_kwargs(traits), @@ -48,6 +49,6 @@ def sample(name="ATLAS", hyperparameters=None, **traits): "HYPERPARAMETERS", "ORIENTATIONS", "get_trait_parameters_with_defaults", - "generate_atlas_sample", + "generate_morphoweave_sample", "resolve_n_modes_for_hyperparams", ] diff --git a/traitblender/assets/morphospace_modules/MorphoWeave/morphospace/__init__.py b/traitblender/assets/morphospace_modules/MorphoWeave/morphospace/__init__.py new file mode 100644 index 0000000..a0b97c1 --- /dev/null +++ b/traitblender/assets/morphospace_modules/MorphoWeave/morphospace/__init__.py @@ -0,0 +1,13 @@ +from .morphoweave_morphospace import ( + generate_morphoweave_sample, + pc_values_from_trait_kwargs, + resolve_n_modes_for_hyperparams, +) +from .morphoweave_morphospace_sample import MorphoWeaveMorphospaceSample + +__all__ = [ + "generate_morphoweave_sample", + "MorphoWeaveMorphospaceSample", + "pc_values_from_trait_kwargs", + "resolve_n_modes_for_hyperparams", +] diff --git a/traitblender/assets/morphospace_modules/ATLAS/morphospace/atlas_core.py b/traitblender/assets/morphospace_modules/MorphoWeave/morphospace/morphoweave_core.py similarity index 98% rename from traitblender/assets/morphospace_modules/ATLAS/morphospace/atlas_core.py rename to traitblender/assets/morphospace_modules/MorphoWeave/morphospace/morphoweave_core.py index 3a60ff4..deb2bac 100644 --- a/traitblender/assets/morphospace_modules/ATLAS/morphospace/atlas_core.py +++ b/traitblender/assets/morphospace_modules/MorphoWeave/morphospace/morphoweave_core.py @@ -1,7 +1,7 @@ """ -ATLAS DATABASE core (PCA + Local RBF), matching agporto/ATLAS DATABASE explorer. +MorphoWeave Model Library core (PCA + Local RBF), matching agporto/SlicerMorphoWeave. -Ported from the working ATLASViewer reference implementation: +Ported from the MorphoWeave / former ATLASViewer reference implementation: - Template-centered PCA: target = template_dense + sum_i (z_i * sqrt(lambda_i) * mode_i) - Local RBF: k=min(32, n_landmarks), Gaussian weights, h = 75th percentile k-th NN distance - LPS/RAS: dense landmarks and SSM in RAS; template PLY typically LPS on disk @@ -21,7 +21,7 @@ def resolve_database_paths(database_dir: str | Path) -> dict[str, Path]: - """Resolve model, dense, sparse (optional), and ssm paths in an ATLAS DATABASE folder.""" + """Resolve model, dense, sparse (optional), and ssm paths in a MorphoWeave Model Library / DATABASE folder.""" root = Path(database_dir).expanduser().resolve() if not root.is_dir(): raise FileNotFoundError(f"Database folder not found: {root}") @@ -254,7 +254,7 @@ def ssm_std_devs(eigenvalues: np.ndarray) -> np.ndarray: return np.sqrt(np.maximum(np.asarray(eigenvalues, dtype=np.float64).ravel(), 0.0)) -def atlas_pc_coefficients( +def morphoweave_pc_coefficients( sigma_weights: np.ndarray | Sequence[float], eigenvalues: np.ndarray, ) -> np.ndarray: @@ -287,7 +287,7 @@ def reconstruct_dense_target( if k == 0 or np.allclose(z[:k], 0.0): return template.copy() - coeff = atlas_pc_coefficients(z[:k], ev[:k]) + coeff = morphoweave_pc_coefficients(z[:k], ev[:k]) agg = np.tensordot(modes_arr[..., :k], coeff, axes=(2, 0)) return template + agg diff --git a/traitblender/assets/morphospace_modules/ATLAS/morphospace/atlas_morphospace.py b/traitblender/assets/morphospace_modules/MorphoWeave/morphospace/morphoweave_morphospace.py similarity index 82% rename from traitblender/assets/morphospace_modules/ATLAS/morphospace/atlas_morphospace.py rename to traitblender/assets/morphospace_modules/MorphoWeave/morphospace/morphoweave_morphospace.py index a18b6e6..bbf5b7e 100644 --- a/traitblender/assets/morphospace_modules/ATLAS/morphospace/atlas_morphospace.py +++ b/traitblender/assets/morphospace_modules/MorphoWeave/morphospace/morphoweave_morphospace.py @@ -1,5 +1,5 @@ """ -ATLAS morphospace sample generation (TraitBlender wrapper around atlas_core). +MorphoWeave morphospace sample generation (TraitBlender wrapper around morphoweave_core). """ from __future__ import annotations @@ -9,8 +9,8 @@ import bpy import numpy as np -from .atlas_core import build_deformed_mesh_arrays, load_ssm, resolve_database_paths -from .atlas_morphospace_sample import AtlasMorphospaceSample +from .morphoweave_core import build_deformed_mesh_arrays, load_ssm, resolve_database_paths +from .morphoweave_morphospace_sample import MorphoWeaveMorphospaceSample def resolve_n_modes_for_hyperparams(hyperparameters: dict | None) -> int | None: @@ -49,30 +49,31 @@ def pc_values_from_trait_kwargs(traits: dict) -> list[float]: def _log(msg: str) -> None: - print(f"[ATLAS] {msg}") + print(f"[MorphoWeave] {msg}") -def generate_atlas_sample( +def generate_morphoweave_sample( name: str, hyperparameters: dict | None, pc_values: list[float], -) -> AtlasMorphospaceSample: +) -> MorphoWeaveMorphospaceSample: hp = dict(hyperparameters or {}) db_raw = (hp.get("database_dir") or hp.get("database_path") or "").strip() if not db_raw: raise ValueError( - "ATLAS morphospace: set hyperparameter 'database_dir' to your DATABASE folder " + "MorphoWeave morphospace: set hyperparameter 'database_dir' to your " + "MorphoWeave Model Library / DATABASE folder " "(manifest + template model + dense correspondences + ssm_model.npz)." ) db_root = Path(bpy.path.abspath(db_raw)) if not db_root.is_dir(): - raise ValueError(f"ATLAS database_dir is not a directory: {db_root}") + raise ValueError(f"MorphoWeave database_dir is not a directory: {db_root}") n_comp_cfg = max(0, int(hp.get("n_components", 10))) scale = float(hp.get("scale", 1.0)) if scale <= 0.0: - raise ValueError("ATLAS hyperparameter 'scale' must be positive") + raise ValueError("MorphoWeave hyperparameter 'scale' must be positive") paths = resolve_database_paths(db_root) ssm = load_ssm(paths) @@ -117,4 +118,4 @@ def generate_atlas_sample( _log(f" mesh: {vertices.shape[0]} verts, {len(faces)} faces") verts = [tuple(map(float, row)) for row in vertices] - return AtlasMorphospaceSample(name, verts, faces) + return MorphoWeaveMorphospaceSample(name, verts, faces) diff --git a/traitblender/assets/morphospace_modules/ATLAS/morphospace/atlas_morphospace_sample.py b/traitblender/assets/morphospace_modules/MorphoWeave/morphospace/morphoweave_morphospace_sample.py similarity index 84% rename from traitblender/assets/morphospace_modules/ATLAS/morphospace/atlas_morphospace_sample.py rename to traitblender/assets/morphospace_modules/MorphoWeave/morphospace/morphoweave_morphospace_sample.py index 29ed045..d48e07c 100644 --- a/traitblender/assets/morphospace_modules/ATLAS/morphospace/atlas_morphospace_sample.py +++ b/traitblender/assets/morphospace_modules/MorphoWeave/morphospace/morphoweave_morphospace_sample.py @@ -1,9 +1,9 @@ -"""Blender mesh creation for an ATLAS warped template.""" +"""Blender mesh creation for a MorphoWeave warped template.""" import bpy -class AtlasMorphospaceSample: +class MorphoWeaveMorphospaceSample: def __init__(self, name: str, vertices: list, faces: list): self.name = name self.vertices = vertices diff --git a/traitblender/assets/morphospace_modules/ATLAS/orientations/__init__.py b/traitblender/assets/morphospace_modules/MorphoWeave/orientations/__init__.py similarity index 78% rename from traitblender/assets/morphospace_modules/ATLAS/orientations/__init__.py rename to traitblender/assets/morphospace_modules/MorphoWeave/orientations/__init__.py index debe2ec..1ea0583 100644 --- a/traitblender/assets/morphospace_modules/ATLAS/orientations/__init__.py +++ b/traitblender/assets/morphospace_modules/MorphoWeave/orientations/__init__.py @@ -1,5 +1,5 @@ """ -ATLAS specimen orientations. +MorphoWeave specimen orientations. Default: table placement only (tb_location). Other views: rotate one axis by n·π/4 (n = 1..7), origin to bounds, tb_location reset — then apply_orientation bakes rotation. @@ -13,7 +13,7 @@ import bpy -def _orient_atlas_default(sample_obj): +def _orient_morphoweave_default(sample_obj): sample_obj.tb_location = (0.0, 0.0, 0.0) @@ -28,13 +28,13 @@ def _pi_multiplier_label(n_quarters: int) -> str: return f"{num}/{den}" -def _make_atlas_axis_rotation(axis: str, n_quarters: int): +def _make_morphoweave_axis_rotation(axis: str, n_quarters: int): angle = (math.pi / 4.0) * n_quarters label = _pi_multiplier_label(n_quarters) name = f"{axis} {label} π Rotation" def orient(sample_obj): - _orient_atlas_default(sample_obj) + _orient_morphoweave_default(sample_obj) if axis == "X": sample_obj.tb_rotation = (angle, 0.0, 0.0) elif axis == "Y": @@ -48,15 +48,15 @@ def orient(sample_obj): sample_obj.tb_location = (0.0, 0.0, 0.0) bpy.context.view_layer.update() - orient.__name__ = f"_orient_atlas_{axis}_{n_quarters}q" + orient.__name__ = f"_orient_morphoweave_{axis}_{n_quarters}q" return name, orient def _build_orientations() -> dict: - out: dict = {"Default": _orient_atlas_default} + out: dict = {"Default": _orient_morphoweave_default} for axis in ("X", "Y", "Z"): for n in range(1, 8): - key, fn = _make_atlas_axis_rotation(axis, n) + key, fn = _make_morphoweave_axis_rotation(axis, n) out[key] = fn return out diff --git a/traitblender/core/config/templates/meshes.py b/traitblender/core/config/templates/meshes.py index 08565ea..6a4abe1 100644 --- a/traitblender/core/config/templates/meshes.py +++ b/traitblender/core/config/templates/meshes.py @@ -36,7 +36,7 @@ class MeshesConfig(TraitBlenderConfig): description=( "If enabled (default), apply the morphospace Default orientation before exporting " "meshes (table placement). Disable to export the generated mesh as-is—useful for " - "ATLAS, where the SSM already provides a consistent PCA-aligned frame" + "MorphoWeave, where the SSM already provides a consistent PCA-aligned frame" ), default=True, ) diff --git a/traitblender/core/datasets/traitblender_dataset.py b/traitblender/core/datasets/traitblender_dataset.py index cc8a35a..4a054ab 100644 --- a/traitblender/core/datasets/traitblender_dataset.py +++ b/traitblender/core/datasets/traitblender_dataset.py @@ -323,7 +323,7 @@ def _get_default_dataframe(self): def _ensure_virtual_dataset_traits_synced(self) -> None: """ No filepath: in-memory CSV. Morphospaces with ``get_trait_parameters_with_defaults`` - (e.g. ATLAS ``n_components``) may change expected trait columns — rewrite CSV to match + (e.g. MorphoWeave ``n_components``) may change expected trait columns — rewrite CSV to match so the editor and ``loc()`` stay consistent. """ if self.filepath: diff --git a/traitblender/core/helpers/orientation_helpers.py b/traitblender/core/helpers/orientation_helpers.py index 599c31e..2b6f3d5 100644 --- a/traitblender/core/helpers/orientation_helpers.py +++ b/traitblender/core/helpers/orientation_helpers.py @@ -113,7 +113,7 @@ def make_euler_orientation(rx, ry, rz, default_fn=None): before bake), then origin at geometry bounds and table center. Local composition uses ``R_obj @ R_local`` so axes follow the specimen after Default - (works for Shell aperture alignment, ATLAS table Default, etc.). + (works for Shell aperture alignment, MorphoWeave table Default, etc.). Args: rx, ry, rz: Local Euler rotation in radians (object rotation_euler order). diff --git a/traitblender/core/morphospaces/list_morphospaces.py b/traitblender/core/morphospaces/list_morphospaces.py index 53776fa..e11316e 100644 --- a/traitblender/core/morphospaces/list_morphospaces.py +++ b/traitblender/core/morphospaces/list_morphospaces.py @@ -6,7 +6,7 @@ from .get_orientations import get_orientations_for_morphospace # GUI dropdown order (folder names under morphospace_modules/) -_MORPHOSPACE_ORDER = ("Shell", "CircleGrid", "ATLAS") +_MORPHOSPACE_ORDER = ("Shell", "CircleGrid", "MorphoWeave") def list_morphospaces(): diff --git a/traitblender/core/morphospaces/trait_params.py b/traitblender/core/morphospaces/trait_params.py index 6903b47..3fde175 100644 --- a/traitblender/core/morphospaces/trait_params.py +++ b/traitblender/core/morphospaces/trait_params.py @@ -39,7 +39,7 @@ def get_trait_parameters_with_defaults_for_morphospace(morphospace_name, context Get trait parameters and their default values for a morphospace. If the module defines ``get_trait_parameters_with_defaults(merged_hyperparameters)``, that - return value is used (e.g. ATLAS: PC1..PCn from ``n_components``, capped by SSM mode count). + return value is used (e.g. MorphoWeave: PC1..PCn from ``n_components``, capped by SSM mode count). Otherwise parameters are inferred from ``sample()``'s signature. diff --git a/traitblender/docs/api/scene-assets.md b/traitblender/docs/api/scene-assets.md index 2d13609..b4279a8 100644 --- a/traitblender/docs/api/scene-assets.md +++ b/traitblender/docs/api/scene-assets.md @@ -151,7 +151,7 @@ This reference focuses on the user-facing operators and properties you will use
bpy.context.scene.traitblender_config.meshes.orient_before_export
If True (default), apply Default orientation before pipeline mesh export (backward compatible). If False, export the generated mesh as-is—recommended for ATLAS PCA-aligned meshes. Does not affect imaging orientations used for renders. See Configuration Files.
If True (default), apply Default orientation before pipeline mesh export (backward compatible). If False, export the generated mesh as-is—recommended for MorphoWeave PCA-aligned meshes. Does not affect imaging orientations used for renders. See Configuration Files.
meshes
save_meshes: booleanorient_before_export: boolean (default true). When saving meshes in the imaging pipeline, apply the morphospace Default orientation before export. Set to false to export the generated mesh as-is (recommended for ATLAS, where the SSM already provides a consistent PCA-aligned frame). Imaging orientations are still applied afterward for renders.orient_before_export: boolean (default true). When saving meshes in the imaging pipeline, apply the morphospace Default orientation before export. Set to false to export the generated mesh as-is (recommended for MorphoWeave, where the SSM already provides a consistent PCA-aligned frame). Imaging orientations are still applied afterward for renders.file_export_type: obj or ply
+
-
-
+
+
+