Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion .zenodo.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
}

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -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": "",
Expand All @@ -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),
Expand All @@ -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",
]
Original file line number Diff line number Diff line change
@@ -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",
]
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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}")
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Original file line number Diff line number Diff line change
@@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)


Expand All @@ -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":
Expand All @@ -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

Expand Down
2 changes: 1 addition & 1 deletion traitblender/core/config/templates/meshes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
2 changes: 1 addition & 1 deletion traitblender/core/datasets/traitblender_dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion traitblender/core/helpers/orientation_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion traitblender/core/morphospaces/list_morphospaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand Down
2 changes: 1 addition & 1 deletion traitblender/core/morphospaces/trait_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion traitblender/docs/api/scene-assets.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ This reference focuses on the user-facing operators and properties you will use
<hr>

<p><code>bpy.context.scene.traitblender_config.meshes.orient_before_export</code></p>
<p>If <code>True</code> (default), apply Default orientation before pipeline mesh export (backward compatible). If <code>False</code>, export the generated mesh as-is—recommended for ATLAS PCA-aligned meshes. Does not affect imaging orientations used for renders. See <a href="../configuration/config-files.md#mesh-export-in-the-imaging-pipeline">Configuration Files</a>.</p>
<p>If <code>True</code> (default), apply Default orientation before pipeline mesh export (backward compatible). If <code>False</code>, export the generated mesh as-is—recommended for MorphoWeave PCA-aligned meshes. Does not affect imaging orientations used for renders. See <a href="../configuration/config-files.md#mesh-export-in-the-imaging-pipeline">Configuration Files</a>.</p>

<hr>

Expand Down
6 changes: 3 additions & 3 deletions traitblender/docs/configuration/config-files.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ TraitBlender config files are **YAML** files that store the settings you see in
<p><strong>meshes</strong></p>
<ul>
<li><code>save_meshes</code>: boolean</li>
<li><code>orient_before_export</code>: boolean (default <code>true</code>). When saving meshes in the imaging pipeline, apply the morphospace <strong>Default</strong> orientation before export. Set to <code>false</code> 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.</li>
<li><code>orient_before_export</code>: boolean (default <code>true</code>). When saving meshes in the imaging pipeline, apply the morphospace <strong>Default</strong> orientation before export. Set to <code>false</code> 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.</li>
<li><code>file_export_type</code>: <code>obj</code> or <code>ply</code></li>
</ul>

Expand Down Expand Up @@ -275,7 +275,7 @@ imaging:
meshes:
file_export_type: obj
save_meshes: true
# ATLAS: keep SSM/PCA frame (skip Default table placement before export)
# MorphoWeave: keep SSM/PCA frame (skip Default table placement before export)
# orient_before_export: false
```

Expand Down Expand Up @@ -450,7 +450,7 @@ When `meshes.save_meshes` is `true`, the simulation pipeline writes one mesh per
| Value | Mesh file contents |
|-------|--------------------|
| `true` (default) | Morphospace **Default** applied first (table placement). Same behavior as earlier TraitBlender releases. |
| `false` | Export the mesh as generated, with no Default/table orientation. Prefer this for **ATLAS**, where the SSM already lives in a shared PCA-aligned frame. Imaging orientations still apply afterward for rendered images only. |
| `false` | Export the mesh as generated, with no Default/table orientation. Prefer this for **MorphoWeave**, where the SSM already lives in a shared PCA-aligned frame. Imaging orientations still apply afterward for rendered images only. |

```yaml
meshes:
Expand Down
6 changes: 3 additions & 3 deletions traitblender/docs/getting-started/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ Before starting, ensure you have:

- **Shell (Default)** — parametric mollusc shells ([Contreras-Figueroa & Aragón](https://www.mdpi.com/1424-2818/15/3/431); thickness from [Okabe & Yoshimura](https://doi.org/10.1038/srep42445))
- **Circle Grid** — simple test morphospace (cube with a 4×4 grid of circles)
- **ATLAS** — deform a template mesh from an [ATLAS DATABASE](https://github.com/agporto/ATLAS) export using PCA + local RBF ([details](../morphospaces/atlas.md))
- **MorphoWeave** — deform a template mesh from a [MorphoWeave](https://github.com/agporto/SlicerMorphoWeave) export using PCA + local RBF ([details](../morphospaces/morphoweave.md))

Select the morphospace that matches your dataset.

For **ATLAS**, set **`database_dir`** under Configuration → morphospace hyperparameters before generating samples.
For **MorphoWeave**, set **`database_dir`** under Configuration → morphospace hyperparameters before generating samples.

??? note "Step 4: Import and Manage Datasets"
Expand the **"4 Datasets"** panel.
Expand Down Expand Up @@ -155,7 +155,7 @@ Before starting, ensure you have:

1. Choose the mesh export type.
2. Enable **Save Meshes** if you want the simulation pipeline to export meshes.
3. Optionally disable **Orient before export** to save meshes in the morphospace/SSM frame (recommended for ATLAS); leave it on (default) to apply Default table placement first.
3. Optionally disable **Orient before export** to save meshes in the morphospace/SSM frame (recommended for MorphoWeave); leave it on (default) to apply Default table placement first.
4. You can also use **Export Mesh** to save the current sample manually.

??? note "Step 8: Imaging"
Expand Down
Binary file added traitblender/docs/images/morphoweave-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading