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
1 change: 1 addition & 0 deletions cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"*.pyc",
"*.pyd",
"*.so",
"*.svg",
".vscode/*.json",
"**/__pycache__/**",
".git",
Expand Down
1 change: 1 addition & 0 deletions docs/pro-features/raw-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ The list shows every raw control, plus a **Rest Pose** row at the top called `de
- **Non-Zero** — hide-zero-value controls. (This can be useful to find which controls are activated.)
- **Value Sort** — sort by value so you can see which controls have the highest values.
- **Side** — a side filter (Left / Right / Center). Helpful if you only want to work on one side of the face.
- **Freeze** — the snowflake button locks the current list order and filtering in place so rows do not reshuffle while you scrub the raw control values.

Select a control and click **Edit** to open an editing session for that expression's pose.

Expand Down
2 changes: 2 additions & 0 deletions docs/pro-features/shape-key-editor.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ The list shows every shape key channel. Filter by:
- **Non-Zero** — hide-zero-value shape keys. (This can be useful to find which shape keys are activated.)
- **Value Sort** — sort by value so you can see which shape keys have the highest values.
- **Side** — a side filter (Left / Right / Center). Helpful if you only want to work on one side of the face.
- **Deltas** — the ghost button is a filter to hide shape keys that do not actually contain vertex changes (a sculpted offset different from the basis). This so you can quickly spot which shape keys hold edits. This is handy when sculpting a custom character where most shape keys have been zeroed out from the beginning.
- **Freeze** — the snowflake button locks the current list order and filtering in place so rows do not reshuffle while you scrub shape key values.

## Editing with Dependencies

Expand Down
4 changes: 1 addition & 3 deletions docs/terminology.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ Toggle each of these outputs on your [Rig Instance](#rig-instance) to see its in

Under the hood, RigLogic runs each pose through a few stages. You don't need to know the math to use the addon, but understanding the vocabulary helps:

* **PSDs (Pose Space Deformations)** — corrective combinations. When several expressions are active at once, PSDs turn on additional "combined" expressions so overlapping poses blend correctly instead of stacking. This is why editing one shape can be affected by others (see the [Shape Key Editor](./pro-features/shape-key-editor.md)).
* **Linear outputs** — most joints and blend shapes are driven linearly by the active raw control values.
* **Conditionals** — a small share of outputs (mostly the wrinkle mask multipliers) ramp on across ranges of an input value.
* **PSDs (Pose Space Deformations)** — are expressions that represent complex poses and how they are corrected. They define how multiple different expressions should be combined together. This is why editing one shape can affect the final result of others (see the [Shape Key Editor](./pro-features/shape-key-editor.md)).

### LODs

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ keywords = ["blender", "metahuman", "dna", "riglogic", "animation", "3d"]
name = "character-dna-addon"
readme = "README.md"
requires-python = ">=3.11,<3.14"
version = "0.10.3"
version = "0.11.2"

[project.optional-dependencies]
dev = [
Expand Down
2 changes: 1 addition & 1 deletion src/addons/character_dna/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
bl_info = {
"name": "Character DNA",
"author": "Poly Hammer",
"version": (0, 10, 3),
"version": (0, 11, 2),
"blender": (4, 5, 0),
"location": "File > Import > MetaHuman DNA",
"description": (
Expand Down
2 changes: 1 addition & 1 deletion src/addons/character_dna/blender_manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ schema_version = "1.0.0"
id = "character_dna"
name = "Character DNA"
tagline = "Customize MetaHuman DNA files with RigLogic evaluation support"
version = "0.10.3"
version = "0.11.2"
type = "add-on"

# =============================================================================
Expand Down
2 changes: 1 addition & 1 deletion src/addons/character_dna/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ class PanelOrder(IntEnum):
CONVERTER = 50
MESH_EDITOR = 55
RAW_CONTROL_EDITOR = 60
CORRECTIVES_VIEWER = 65
SHAPE_KEY_EDITOR = 70
RBF_EDITOR = 80
BEHAVIOR_VIEWER = 85
BACKUP_MANAGER = 90
OUTPUT = 100
MIGRATE_LEGACY_DATA = 110
Expand Down
39 changes: 25 additions & 14 deletions src/addons/character_dna/dna_io/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,33 @@ def get_dna_reader(
stream = dna.FileStream.create(
path=str(file_path), accessMode=dna.AccessMode_Read, openMode=mode, memRes=memory_resource
)

# Explicitly enforce the coordinate frame our importer assumes (Maya Y-up:
# x=left, y=up, z=front) instead of trusting whatever system the incoming DNA
# was authored in. With the Transform policy the reader converts any source
# system to this frame at load time (a no-op when the data is already Maya
# Y-up), so the downstream manual +90deg X rotation and scale handling stay
# valid even for DNAs exported with a different coordinate system. Units are
# not touched here (the Configuration transform does not convert cm<->m or
# degrees<->radians); those are still adapted from getTranslationUnit /
# getRotationUnit by the importer.
coordinate_system = dna.CoordinateSystem()
coordinate_system.x = dna.Direction_left
coordinate_system.y = dna.Direction_up
coordinate_system.z = dna.Direction_front

config = dna.Configuration()
config.layer = getattr(dna, f"DataLayer_{data_layer}")
config.unknownLayerPolicy = dna.UnknownLayerPolicy_Preserve
config.coordinateSystemTransformPolicy = dna.CoordinateSystemTransformPolicy_Transform
config.coordinateSystem = coordinate_system

if file_format.lower() == "json":
reader = dna.JSONStreamReader.create(
stream,
getattr(dna, f"DataLayer_{data_layer}"),
dna.UnknownLayerPolicy_Preserve,
0, # Provide appropriate int value
None, # Assuming MemoryResource is None
)
# The JSON reader has no Configuration overload, so it cannot enforce the
# coordinate system on load; JSON DNAs are expected to already be Maya Y-up.
reader = dna.JSONStreamReader.create(stream, memory_resource)
elif file_format.lower() == "binary":
reader = dna.BinaryStreamReader.create(
stream,
getattr(dna, f"DataLayer_{data_layer}"),
dna.UnknownLayerPolicy_Preserve,
0, # Provide appropriate int value
None, # Assuming MemoryResource is None
)
reader = dna.BinaryStreamReader.create(stream, config, memory_resource)
else:
raise ValueError(f"Invalid file format '{file_format}'. Must be 'binary' or 'json'.")

Expand Down
2 changes: 1 addition & 1 deletion src/addons/character_dna/editors
13 changes: 9 additions & 4 deletions src/addons/character_dna/release_notes.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
## Major Changes

* Initial implementation of the Behavior Viewer

## Minor Changes

* Added Blender `5.2` support
* Added Freeze option to Raw Editor and Shape Key Editor list filter
* Added Ghost indicator for shape keys that do not contain deltas. Also added toggle to filter them from the view.
* Added option to turn off dependency chain isolation in Shape Key Editor

## Patch Changes

* Added experimental option to turn off batched evaluations in addon preferences
* Fixed center eye control baking bug [#308](https://github.com/poly-hammer/character-dna-addon/issues/308)
* Fixed bug with eye aim control when eyes follow head is false [#309](https://github.com/poly-hammer/character-dna-addon/issues/309)
* Fixed incomplete/opaque eye and saliva materials on Blender 4.x [#346](https://github.com/poly-hammer/character-dna-addon/issues/346)
* New Coordinate System policy is enforced on the DNA Reader to properly convert DNA's saved in other coordinate systems.

## Tests Passing On

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1411,23 +1411,6 @@
1.0
]
},
"CTRL_C_jaw": {
"location": [
1.8851503469363706e-08,
0.0624239444732666,
0.0
],
"rotation": [
0.0,
-0.0,
0.0
],
"scale": [
1.0,
1.0,
1.0
]
},
"CTRL_C_jaw_fwdBack": {
"location": [
0.0,
Expand Down
5 changes: 5 additions & 0 deletions src/addons/character_dna/rig_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -854,6 +854,10 @@ def sync_shape_key_list(self) -> None:
self.head_shape_key_blocks # noqa: B018
shape_key_block_names = self.data.get(self.cache_key("head", "shape_key_block_names"), [])

# The has-deltas map is derived from the live block coords; drop it so it
# recomputes lazily against the freshly synced blocks.
self.data.pop(self.cache_key("head", "shape_key_has_deltas"), None)

shape_key_editor.shape_key_list.clear()
for shape_key_block_name in shape_key_block_names:
shape_key_item = shape_key_editor.shape_key_list.add()
Expand Down Expand Up @@ -1359,6 +1363,7 @@ def destroy_references(self):
("head", "shape_key"),
("head", "shape_key_blocks"),
("head", "shape_key_apply_plan"),
("head", "shape_key_has_deltas"),
("head", "body_constraints"),
("head", "rig_evaluated"),
("body", "rig_evaluated"),
Expand Down
6 changes: 3 additions & 3 deletions src/addons/character_dna/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
BackupManagerProperties,
DnaBackupEntry,
)
from .editors.behavior_viewer.properties import BehaviorViewerProperties, PsdCorrectiveListItem # noqa: TC004
from .editors.converter.properties import ( # noqa: TC004
ConverterExtraMeshItem,
ConverterProperties,
)
from .editors.correctives_viewer.properties import CorrectivesViewerProperties, PsdCorrectiveListItem # noqa: TC004
from .editors.mesh_editor.properties import ( # noqa: TC004
MeshDataTransferItem,
MeshEditorProperties,
Expand Down Expand Up @@ -100,7 +100,7 @@ class RigInstance(_RigInstanceBase):
backup_manager: BackupManagerProperties
rbf_editor: RBFEditorProperties
raw_control_editor: RawControlEditorProperties
correctives_viewer: CorrectivesViewerProperties
behavior_viewer: BehaviorViewerProperties
shape_key_editor: ShapeKeyEditorProperties
output: CharacterOutputProperties
view_options: CharacterViewOptionsProperties
Expand Down Expand Up @@ -181,6 +181,7 @@ class Context(bpy.types.Context):
__all__ = [
"BakeAnimationBase",
"BakeAnimationBase",
"BehaviorViewerProperties",
"BinaryStreamReader",
"BinaryStreamWriter",
"CharacterAddonPreferences",
Expand All @@ -198,7 +199,6 @@ class Context(bpy.types.Context):
"Context",
"ConverterExtraMeshItem",
"ConverterProperties",
"CorrectivesViewerProperties",
"DnaBackupEntry",
"DuplicateRigInstance",
"MeshDataTransferItem",
Expand Down
9 changes: 2 additions & 7 deletions src/addons/character_dna/ui/callbacks.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,7 @@ def get_body_texture_logic_node(material: bpy.types.Material) -> bpy.types.Shade
for node in material.node_tree.nodes:
# Check if this is the right group node by checking one input name
# We don't check all to avoid performance issues
if (
node.type == "GROUP"
and node.inputs.get("Color_MAIN")
and node.inputs.get("Normal_MAIN")
and node.inputs.get("Cavity_MAIN")
):
if node.type == "GROUP" and node.inputs.get("Color_MAIN") and node.inputs.get("Normal_MAIN"):
return node # type: ignore[return-value]
return None

Expand Down Expand Up @@ -697,7 +692,7 @@ def set_active_material_preview(self: "CharacterViewOptionsProperties", value: i

for node_group in [head_node_group, body_node_group]:
if not node_group or not node_group.node_tree:
return
continue

# combined
if value == 0:
Expand Down
11 changes: 8 additions & 3 deletions src/addons/character_dna/utilities/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,14 @@ def create_new_material(
name: str, color: tuple[float, float, float, float] | None = None, alpha: float | None = None
) -> bpy.types.Material:
material = bpy.data.materials.new(name=name)
# `Material.use_nodes` is deprecated (removed in Blender 6.0); every material
# already has a node tree by default on the addon's supported Blender range
# (4.5+), so no explicit opt-in is needed.
# Blender 4.x does not build the node tree for a freshly created material
# until node usage is enabled, so the material renders as incomplete. The
# `use_nodes` property is deprecated in Blender 5.0+ (removed in 6.0), where
# materials always use their node tree, so only set it on older versions to
# avoid emitting a deprecation warning.
if bpy.app.version < (5, 0, 0) and hasattr(material, "use_nodes"):
material.use_nodes = True

if not material.node_tree:
logger.error(f"Material {name} has no node tree.")
return material
Expand Down
Loading