Update
This commit is contained in:
Jeffreytsai1004 2025-01-07 00:38:13 +08:00
parent bbfd4a64da
commit 8929a7e59c
37 changed files with 0 additions and 3229 deletions

View File

@ -1,253 +0,0 @@
## DNA 库
DNA 库作为 DNACalib 的依赖项之一捆绑在本仓库中。
它提供了读取和写入 MetaHuman DNA 文件的核心功能。
它使用户能够查询和更改其中包含的信息。
DNACalib 提供了一组用于编辑 MetaHuman DNA 文件的实用命令。在底层,它使用 DNA 库。一些命令只是封装了对 DNA 库的几个调用,而其他命令则包含额外的逻辑。
虽然用户可以仅使用 DNA 库自己完成所有这些操作,但这些命令旨在让他们的工作更轻松。
## MetaHuman DNA
MetaHuman DNA 是一种文件格式,旨在存储 3D 对象的骨骼和几何体的完整描述。
仅依靠 MetaHuman DNA 文件,就可以重建对象的完整网格并使其完全装配好骨骼,准备好进行动画制作。在实践中,MetaHuman DNA 文件仅用于存储人物角色的面部。
### 层级
MetaHuman DNA 文件中的数据分为几个逻辑层。这些层通过松散的层级结构连接在一起,其中 MetaHuman DNA 文件中的每个后续层都依赖于其上层中存储的数据。
![MetaHuman DNA 层级](img/layers.svg "MetaHuman DNA 层级")
可以选择性地只加载 MetaHuman DNA 到指定层。如层级组织图所示,行为层和几何层彼此不依赖。这种独立性对于只需要使用 MetaHuman DNA 文件来驱动骨骼(使用行为层进行运行时评估)而不需要访问几何数据的用例至关重要。
#### 描述符
描述符层包含关于骨骼的基本元数据,例如:
- 角色名称
- 年龄
- 面部原型
- 以键/值对形式的任意字符串元数据
- 所需的兼容性参数(与高级系统相关,例如用于混合 MetaHuman DNA 文件)
#### 定义
定义层包含骨骼的静态数据,例如:
- 控制器、关节、变形、动画贴图和网格的名称
- 关节、变形、动画贴图和网格到各个 LOD 的映射
- 关节层级
- 绑定姿态(如 T 姿态)中的关节变换
该层包含基于所选 LOD 在后续层中进行过滤所需的信息。
#### 行为
行为层包含骨骼的动态数据,用于:
- 将 GUI 控制器映射到原始控制值
- 计算修正表情
- 计算关节变换
- 计算变形通道权重
- 计算动画贴图权重
#### 几何
几何层包含重建角色网格所需的所有数据,以及其蒙皮权重和变形目标增量。网格信息本身的结构类似于 OBJ 格式。
### API 概述
在使用 MetaHuman DNA 文件时,使用的两个主要接口是:
- [`BinaryStreamReader`](/dnacalib/DNACalib/include/dna/BinaryStreamReader.h)
- [`BinaryStreamWriter`](/dnacalib/DNACalib/include/dna/BinaryStreamWriter.h)
它们用于从二进制流读取数据或将数据写入二进制流。例如,使用 [`FileStream`](/dnacalib/DNACalib/include/trio/streams/FileStream.h) 处理文件时。
这里我们将展示一些展示库基本用法的代码片段。
DNA 库的一般 API 概述可以在[此处](/docs/dna_api.md)找到。
与 DNACalib 一样,DNA 库是用 C++ 编写的,但有一个 Python 包装器,因此可以从 C++ 和 Python 中使用它。
#### 创建读取器/写入器
##### 读取器
从文件读取二进制 DNA 的函数示例:
```
def load_dna(path):
stream = FileStream(path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
reader = BinaryStreamReader(stream, DataLayer_All)
reader.read()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error loading DNA: {status.message}")
return reader
```
创建读取器时,除了流参数外,还可以指定要加载的数据层。在此示例中,将加载所有层,因为使用了 ```DataLayer_All```,但您可以指定以下任何一项:
```
DataLayer_Descriptor
DataLayer_Definition - 包括描述符和定义
DataLayer_Behavior - 包括描述符、定义和行为
DataLayer_Geometry - 包括描述符、定义和几何
DataLayer_GeometryWithoutBlendShapes - 包括描述符、定义和不含变形的几何
DataLayer_AllWithoutBlendShapes - 包括除几何中的变形之外的所有内容
DataLayer_All
```
例如,如果您只想加载行为层(包括定义和描述符),请使用:
```
stream = FileStream(path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
reader = BinaryStreamReader(stream, DataLayer_Behavior)
reader.read()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error loading DNA: {status.message}")
```
##### 写入器
从文件写入二进制 DNA 的函数示例:
```
def save_dna(reader, path):
stream = FileStream(path, FileStream.AccessMode_Write, FileStream.OpenMode_Binary)
writer = BinaryStreamWriter(stream)
# 使用所有数据层基于读取器创建写入器(如果没有传递参数给 setFrom(),DataLayer_All 是默认值)
writer.setFrom(reader)
# 例如,要仅使用几何层(包括定义和描述符)创建写入器,请使用:
# writer.setFrom(reader, DataLayer_Geometry)
writer.write()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error saving DNA: {status.message}")
```
除了在创建读取器时指定层外,还可以在创建写入器时指定要使用的层(作为 ```setFrom()``` 方法的参数)。
```load_dna``` 和 ```save_dna``` 函数在大多数[`示例`](/examples/)中都有使用。
**注意**: 还有 [`JSONStreamReader`](/dnacalib/DNACalib/include/dna/JSONStreamReader.h) 和 [`JSONStreamWriter`](/dnacalib/DNACalib/include/dna/JSONStreamWriter.h),用于处理 JSON 格式而不是二进制格式的 MetaHuman DNA 文件。但是应该注意,JSON 变体仅用于调试等目的。与二进制读取器和写入器不同,它们的 JSON 对应项不能执行过滤,并且通常会生成更大的文件。
存储 DNA 文件的推荐格式是二进制格式。
**已知问题**: 目前读取 JSON MetaHuman DNA 文件会失败。此问题将在未来版本中解决。
#### 示例
以下是使用该库的一些示例片段。
##### 示例 1: 读取指定网格的中性顶点位置
```
dna = load_dna(input_path)
if dna.getMeshCount() == 0:
print("No meshes found in DNA.")
return
mesh_index = 0
xs = dna.getVertexPositionXs(mesh_index)
ys = dna.getVertexPositionYs(mesh_index)
zs = dna.getVertexPositionZs(mesh_index)
```
##### 示例 2: 读取中性关节坐标和关节方向值
```
dna = load_dna(input_path)
# 读取关节坐标
neutral_joint_translation_xs = dna.getNeutralJointTranslationXs()
neutral_joint_translation_ys = dna.getNeutralJointTranslationYs()
neutral_joint_translation_zs = dna.getNeutralJointTranslationZs()
# 读取关节方向
neutral_joint_orient_xs = dna.getNeutralJointRotationXs()
neutral_joint_orient_ys = dna.getNeutralJointRotationYs()
neutral_joint_orient_zs = dna.getNeutralJointRotationZs()
```
##### 示例 3: 读取表情的变形目标增量并更改它们
```
def read_blend_shape_target_deltas(reader, mesh_index, blend_shape_target_index):
"""
读取变形目标增量和相应的顶点索引。
"""
vertex_indices = reader.getBlendShapeTargetVertexIndices(
mesh_index, blend_shape_target_index
)
blend_shape_target_delta_count = reader.getBlendShapeTargetDeltaCount(
mesh_index, blend_shape_target_index
)
deltas = []
for delta_index in range(blend_shape_target_delta_count):
x, y, z = reader.getBlendShapeTargetDelta(
mesh_index, blend_shape_target_index, delta_index
)
deltas.append([x, y, z])
return vertex_indices, deltas
# 读取然后更改表情 "jaw_open" 的变形目标增量,网格为 "head_lod0_mesh"
input_dna = load_dna(input_path)
mesh_name = "head_lod0_mesh"
mesh_count = input_dna.getMeshCount()
head_mesh_index = 0
for mesh_index in range(mesh_count):
if input_dna.getMeshName(mesh_index) == mesh_name:
head_mesh_index = mesh_index
break
bs_target_count = input_dna.getBlendShapeTargetCount(head_mesh_index)
expr_name = "jaw_open"
# 获取指定表情的变形目标索引
for i in range(bs_target_count):
bs_channel_index = input_dna.getBlendShapeChannelIndex(head_mesh_index, i)
bs_name = input_dna.getBlendShapeChannelName(bs_channel_index)
if bs_name == expr_name:
bs_target_index = i
break
vertex_indices, deltas = read_blend_shape_target_deltas(input_dna, head_mesh_index, bs_target_index)
# 修改增量(在本例中,只是给每个增量加 1.0)
for i in range(len(deltas)):
deltas[i][0] += 1.0
deltas[i][1] += 1.0
deltas[i][2] += 1.0
# 从输入 DNA 创建写入器 DNA
output_stream = dna.FileStream(outputPath, dna.FileStream.AccessMode_Write, dna.FileStream.OpenMode_Binary)
# 在此示例中,为了调试目的,以 JSON 格式写入 DNA,以快速查看变形增量是否已更改
output_dna = dna.JSONStreamWriter(output_stream)
output_dna.setFrom(input_dna)
# 为表情写入新的变形增量值
output_dna.setBlendShapeTargetDeltas(mesh_index, bs_target_index, deltas)
# 如果您以删除或添加某些增量的方式修改了增量,
# 那么您还必须设置与新增量对应的新顶点索引:
# output_dna.setBlendShapeTargetVertexIndices(mesh_index, bs_target_index, new_vertex_indices)
# 写入具有修改值的 DNA
output_dna.write()
if not dna.Status.isOk():
status = dna.Status.get()
raise RuntimeError("Error saving DNA: {}".format(status.message))
```
### 使用
有一些简短的[`示例`](/examples)涵盖了用户在使用 MetaHuman DNA 时可能遇到的一些情况。
其中包括:
- [`将二进制 DNA 文件内容写入 JSON 格式以供检查`](/examples/dna_binary_to_json_demo.py)
- [`从 DNA 中清除所有变形数据`](/examples/dnacalib_clear_blend_shapes.py)
- [`从 DNA 中删除某些 LOD`](/examples/dnacalib_lod_demo.py)

View File

@ -1,564 +0,0 @@
# DNA API 概述
以下是用于读取和写入 DNA 文件的主要方法概述。
以下文档适用于 C++。目前尚无 Python 文档。
如[此处](/docs/dna.md#api-overview)所述,有一些类用于从流中读取 DNA 或将其写入流。这些类包括:
- [BinaryStreamReader](/dnacalib/DNACalib/include/dna/BinaryStreamReader.h)
- [BinaryStreamWriter](/dnacalib/DNACalib/include/dna/BinaryStreamWriter.h)
- [JSONStreamReader](/dnacalib/DNACalib/include/dna/JSONStreamReader.h)
- [JSONStreamWriter](/dnacalib/DNACalib/include/dna/JSONStreamWriter.h)
创建读取器后,可以用它来查询 DNA 中包含的不同信息。
创建写入器后,可以用它来在 DNA 中设置新值。
这是通过本页列出的方法完成的。这些方法根据 DNA 文件中的[层级](/docs/dna.md#layers)进行分组。
**注意**: 本页并未列出所有可用方法。有关此处列出的方法的更多详细信息以及所有可用方法的列表,请参阅相应的读取器和/或写入器[文档](/dnacalib/DNACalib/include/dna/layers)。
## BinaryStreamReader
包含用于创建和销毁 [BinaryStreamReader](/dnacalib/DNACalib/include/dna/BinaryStreamReader.h) 的方法。
创建 BinaryStreamReader 时,用户可以通过指定要加载的 LOD 来过滤 DNA 文件中的数据。如[此处](/docs/dna.md#reader)所述,还可以通过指定要加载的数据层来进行过滤。
- `create(stream, layer = DataLayer::All, maxLOD = 0u, memRes = nullptr)`
BinaryStreamReader 的工厂方法。
参数:
`stream` - 将从中读取数据的源流。
`layer` - 指定需要加载的数据层。
`maxLOD` - 要加载的最大细节级别。值为零表示加载所有 LOD。
`memRes` - 用于分配的内存资源。如果未提供内存资源,将使用默认分配机制。用户负责通过调用 destroy 释放返回的指针。
- `create(stream, layer, maxLOD, minLOD, memRes = nullptr)`
BinaryStreamReader 的工厂方法。
参数:
`stream` - 将从中读取数据的源流。
`layer` - 指定需要加载的数据层。
`maxLOD` - 要加载的最大细节级别。
`minLOD` - 要加载的最小细节级别。maxLOD / minLOD 的范围为 [0, LOD 总数 - 1] 表示加载所有 LOD。
`memRes` - 用于分配的内存资源。如果未提供内存资源,将使用默认分配机制。用户负责通过调用 destroy 释放返回的指针。
- `create(stream, layer, lods, lodCount, memRes = nullptr)`
BinaryStreamReader 的工厂方法。
参数:
`stream` - 将从中读取数据的源流。
`layer` - 指定需要加载的数据层。
`lods` - 指定要加载的具体 LOD 的数组。
`lodCount` - lods 数组中的元素数量。
`memRes` - 用于分配的内存资源。如果未提供内存资源,将使用默认分配机制。用户负责通过调用 destroy 释放返回的指针。
- `destroy(instance)`
用于释放 BinaryStreamReader 实例的方法。
参数:
`instance` - 要释放的 BinaryStreamReader 实例。
## BinaryStreamWriter
- `create(stream, memRes = nullptr)`
BinaryStreamWriter 的工厂方法。
参数:
`stream` - 将向其写入数据的流。
`memRes` - 用于分配的内存资源。如果未提供内存资源,将使用默认分配机制。用户负责通过调用 destroy 释放返回的指针。
- `destroy(BinaryStreamWriter* instance)`
用于释放 BinaryStreamWriter 实例的方法。
参数:
`instance` - 要释放的 BinaryStreamWriter 实例。
## JSONStreamReader
- `create(stream, memRes = nullptr)`
JSONStreamReader 的工厂方法。
参数:
`stream` - 将从中读取数据的源流。
`memRes` - 用于分配的内存资源。如果未提供内存资源,将使用默认分配机制。用户负责通过调用 destroy 释放返回的指针。
- `destroy(instance)`
用于释放 JSONStreamReader 实例的方法。
参数:
`instance` - 要释放的 JSONStreamReader 实例。
## JSONStreamWriter
- `create(stream, indentWidth = 4u, memRes = nullptr)`
JSONStreamWriter 的工厂方法。
参数:
`stream` - 将向其写入数据的流。
`indentWidth` - 用于缩进的空格数。
`memRes` - 用于分配的内存资源。如果未提供内存资源,将使用默认分配机制。用户负责通过调用 destroy 释放返回的指针。
- `destroy(instance)`
用于释放 JSONStreamWriter 实例的方法。
参数:
`instance` - 要释放的 JSONStreamWriter 实例。
## setFrom() 方法
除了创建写入器的方法外,BinaryStreamReader 和 JSONStreamReader 都有一个从 [Writer](/dnacalib/DNACalib/include/dna/Writer.h) 继承的 setFrom() 方法,用于使用读取器中的数据初始化写入器。
使用此方法时,用于初始化写入器的数据可以按数据层进行过滤。
- `setFrom(source, layer = DataLayer::All, memRes = nullptr)`
从给定的读取器初始化写入器。
此函数通过调用读取器的每个 getter 函数并将返回值传递给写入器中的匹配 setter 函数,将所有数据从给定的读取器复制到写入器实例中。
参数:
`source` - 需要从中复制数据的源 DNA 读取器。
`layer` - 限制应从给定源读取器接管哪些层。
`memRes` - 用于复制期间临时分配的可选内存资源。
## 读取器方法
### DescriptorReader
包含用于访问角色和骨骼各种元数据的只读访问器。
- `getName()`
角色名称。
- `getArchetype()`
角色原型。
- `getGender()`
角色性别。
- `getAge()`
角色年龄。
- `getTranslationUnit()`
使用的平移单位(厘米或米)。
- `getRotationUnit()`
使用的旋转单位(度或弧度)。
- `getCoordinateSystem()`
使用的坐标系统(x、y 和 z 轴的方向)。
- `getLODCount()`
可用的细节级别(例如 6 表示以下级别可用:[0,1,2,3,4,5],其中 0 是细节最高的 LOD,5 是细节最低的 LOD)。
- `getDBMaxLOD()`
此角色的 DNA 数据中存储的最大细节级别。该值相对于数据库中的 LOD-0。
- `getDBComplexity()`
用于驱动此角色骨骼的输入控制接口的名称。此参数表示角色的输入控制复杂度。
- `getDBName()`
角色源自的数据库的名称。来自同一数据库的所有角色必须具有相同的定义,但可能具有不同的复杂度或 LOD。
### DefinitionReader
包含用于表示骨骼静态数据的 DNA 属性的只读访问器。
- `getGUIControlCount()`
GUI 控制器的数量。
- `getGUIControlName(index)`
请求的 GUI 控制器的名称。
- `getRawControlCount()`
原始控制器的数量。
- `getRawControlName(index)`
请求的原始控制器的名称。
- `getJointCount()`
关节的数量。
- `getJointName(index)`
请求的关节的名称。
- `getJointIndicesForLOD(lod)`
指定 LOD 的关节索引列表。
- `getJointParentIndex(index)`
请求的关节父级的索引。
可以使用此函数遍历和重建关节层级。示例:
关节名称: [A, B, C, D, E, F, G, H, I]
层级结构: [0, 0, 0, 1, 1, 4, 2, 6, 2]
描述了以下层级结构:
```
A
├── B
│ ├── D
│ └── E
│ └── F
└── C
├── G
│ └── H
└── I
```
请求关节 5(关节名称:F)的父级索引将返回 4(关节名称:E)。
请求根关节的父级索引:0(关节名称:A)将返回相同的索引 0。
- `getBlendShapeChannelCount()`
变形通道的数量。
- `getBlendShapeChannelName(index)`
请求的变形通道的名称。
- `getBlendShapeChannelIndicesForLOD(lod)`
指定 LOD 的变形通道索引列表。
- `getAnimatedMapCount()`
动画贴图的数量。
- `getAnimatedMapName(index)`
请求的动画贴图的名称。
- `getAnimatedMapIndicesForLOD(lod)`
指定 LOD 的动画贴图索引列表。
- `getMeshCount()`
网格的数量。
- `getMeshName(index)`
请求的网格的名称。
- `getMeshIndicesForLOD(lod)`
指定 LOD 的网格索引列表。
- `getMeshBlendShapeChannelMappingCount()`
网格-变形通道映射项的数量。
- `getMeshBlendShapeChannelMapping(index)`
保存指定映射索引的网格索引和相关变形通道索引的结构。
- `getMeshBlendShapeChannelMappingIndicesForLOD(lod)`
指定 LOD 的网格-变形通道映射索引列表。
- `getNeutralJointTranslation(index)`
绑定姿态下关节的平移值(x, y, z)。
- `getNeutralJointTranslationXs()`
绑定姿态下所有关节的 X 轴平移值列表。
- `getNeutralJointTranslationYs()`
绑定姿态下所有关节的 Y 轴平移值列表。
- `getNeutralJointTranslationZs()`
绑定姿态下所有关节的 Z 轴平移值列表。
- `getNeutralJointRotation(index)`
绑定姿态下关节的旋转值(x, y, z)。
- `getNeutralJointRotationXs()`
绑定姿态下所有关节的 X 轴旋转值列表。
- `getNeutralJointRotationYs()`
绑定姿态下所有关节的 Y 轴旋转值列表。
- `getNeutralJointRotationZs()`
绑定姿态下所有关节的 Z 轴旋转值列表。
### BehaviorReader
包含用于定义骨骼评估的 DNA 属性的只读访问器。
- `getGUIToRawInputIndices()`
用于将 GUI 映射到原始控制器的输入索引。
- `getGUIToRawOutputIndices()`
用于将 GUI 映射到原始控制器的输出索引。
- `getGUIToRawFromValues()`
在 GUI 到原始控制器映射期间用于决定是否应评估特定条目的过滤值(下限)。
- `getGUIToRawToValues()`
在 GUI 到原始控制器映射期间用于决定是否应评估特定条目的过滤值(上限)。
- `getGUIToRawSlopeValues()`
在 GUI 到原始控制器映射期间用于计算输出值的计算值(斜率/梯度)。
- `getGUIToRawCutValues()`
在 GUI 到原始控制器映射期间用于计算输出值的计算值(垂直截距)。
- `getPSDCount()`
不同 PSD 表情的数量。
- `getPSDRowIndices()`
PSD(输入)索引。
- `getPSDColumnIndices()`
控制器(输入)索引。
- `getPSDValues()`
与每个 PSD 行和列对相关联的权重。
- `getJointRowCount()`
完整未压缩关节矩阵中的行数。
- `getJointColumnCount()`
完整未压缩关节矩阵中的列数。
- `getJointVariableAttributeIndices(lod)`
请求的 LOD 的关节属性索引(输出索引)。
- `getJointGroupCount()`
整个关节矩阵中存在的关节组数量。
- `getJointGroupLODs(jointGroupIndex)`
请求的关节组中每个细节级别的行数。
每个元素的位置代表级别本身,而值表示属于该级别的关节组内的行数。例如:
```
[12, 9, 3]
│ │ └── LOD-2 包含前 3 行
│ └── LOD-1 包含前 9 行
└── LOD-0 包含前 12 行
```
- `getJointGroupInputIndices(jointGroupIndex)`
请求的关节组包含的列索引。这些列索引指向完整的未压缩关节矩阵。
- `getJointGroupOutputIndices(jointGroupIndex)`
请求的关节组包含的行索引。这些行索引指向完整的未压缩关节矩阵。
- `getJointGroupValues(jointGroupIndex)`
请求的关节组包含的值。
- `getJointGroupJointIndices(jointGroupIndex)`
请求的关节组包含的关节索引。
- `getBlendShapeChannelLODs()`
变形通道每个细节级别的输入索引数量。
每个元素的位置代表级别本身(例如 [0,1,2,3,4,5] 值 0 是最高细节的 LOD,值 5 是最低细节的 LOD),而值表示属于该级别的输入索引数量。
- `getBlendShapeChannelInputIndices()`
用于索引输入向量的输入索引。
- `getBlendShapeChannelOutputIndices()`
指定变形通道输出值位置的输出索引。
- `getAnimatedMapLODs()`
动画贴图每个细节级别的行数。
每个元素的位置代表级别本身(例如 [0,1,2,3,4,5] 值 0 是最高细节的 LOD,值 5 是最低细节的 LOD),而值表示属于该级别的行数(在条件表中)。
- `getAnimatedMapInputIndices()`
用于索引输入值数组的输入索引。
- `getAnimatedMapOutputIndices()`
指定计算输出值位置的输出索引。
- `getAnimatedMapFromValues()`
用于决定是否应评估特定条目的过滤值(下限)。
- `getAnimatedMapToValues()`
用于决定是否应评估特定条目的过滤值(上限)。
- `getAnimatedMapSlopeValues()`
用于计算输出值的计算值(斜率/梯度)。
- `getAnimatedMapCutValues()`
用于计算输出值的计算值(垂直截距)。
### GeometryReader
包含与骨骼相关的几何数据的只读访问器。
- `getVertexPositionCount(meshIndex)`
整个网格中顶点位置的数量。
- `getVertexPosition(meshIndex, vertexIndex)`
指定网格中指定顶点的位置。顶点按顶点 ID 排序。
- `getVertexPositionXs(meshIndex)`
引用网格的所有顶点位置 X 值列表。
- `getVertexPositionYs(meshIndex)`
引用网格的所有顶点位置 Y 值列表。
- `getVertexPositionZs(meshIndex)`
引用网格的所有顶点位置 Z 值列表。
- `getVertexTextureCoordinateCount(meshIndex)`
整个网格中纹理坐标的数量。
- `getVertexTextureCoordinate(meshIndex, textureCoordinateIndex)`
指定网格中指定索引的纹理坐标。
- `getVertexTextureCoordinateUs(meshIndex)`
引用网格的所有纹理坐标 U 值列表。
- `getVertexTextureCoordinateVs(meshIndex)`
引用网格的所有纹理坐标 V 值列表。
- `getVertexNormalCount(meshIndex)`
整个网格中顶点法线的数量。
- `getVertexNormal(meshIndex, normalIndex)`
指定网格中指定索引的顶点法线。
- `getVertexNormalXs(meshIndex)`
引用网格的所有法线 X 值列表。
- `getVertexNormalYs(meshIndex)`
引用网格的所有法线 Y 值列表。
- `getVertexNormalZs(meshIndex)`
引用网格的所有法线 Z 值列表。
- `getVertexLayoutCount(meshIndex)`
整个网格中顶点布局的数量。顶点布局是顶点属性的集合。
- `getVertexLayout(meshIndex, layoutIndex)`
顶点布局仅包含可用于查询与顶点关联的实际属性(如位置、纹理坐标和法线)的属性索引。布局中的索引可与上述定义的 API 一起使用。
- `getVertexLayoutPositionIndices(meshIndex)`
引用网格中每个顶点的位置索引。
- `getVertexLayoutTextureCoordinateIndices(meshIndex)`
引用网格中每个顶点的纹理坐标索引。
- `getVertexLayoutNormalIndices(meshIndex)`
引用网格中每个顶点的法线索引。
- `getFaceCount(meshIndex)`
指定网格所包含的面的数量。
- `getFaceVertexLayoutIndices(meshIndex, faceIndex)`
属于指定网格上某个面的顶点布局索引列表。
- `getMaximumInfluencePerVertex(meshIndex)`
可能影响任何单个顶点的最大关节数量。
- `getSkinWeightsCount(meshIndex)`
与指定网格关联的蒙皮权重数量。
- `getSkinWeightsValues(meshIndex, vertexIndex)`
影响请求顶点的蒙皮权重列表。
- `getSkinWeightsJointIndices(meshIndex, vertexIndex)`
与指定顶点的每个蒙皮权重关联的关节索引列表。关节索引的存储顺序与其关联的权重相同。
- `getBlendShapeTargetCount(meshIndex)`
属于指定网格的变形目标数量。
- `getBlendShapeChannelIndex(meshIndex, blendShapeTargetIndex)`
请求的变形目标对应的变形通道索引。
- `getBlendShapeTargetDeltaCount(meshIndex, blendShapeTargetIndex)`
属于指定变形目标的增量数量。
- `getBlendShapeTargetDelta(meshIndex, blendShapeTargetIndex, deltaIndex)`
每个受影响顶点的增量列表。
- `getBlendShapeTargetDeltaXs(meshIndex, blendShapeTargetIndex)`
引用变形目标的所有增量 X 值列表。
- `getBlendShapeTargetDeltaYs(meshIndex, blendShapeTargetIndex)`
引用变形目标的所有增量 Y 值列表。
- `getBlendShapeTargetDeltaZs(meshIndex, blendShapeTargetIndex)`
引用变形目标的所有增量 Z 值列表。
- `getBlendShapeTargetVertexIndices(meshIndex, blendShapeTargetIndex)`
受引用变形目标影响的顶点位置索引。顶点位置索引的存储顺序与其关联的增量相同。这些索引可以通过 getVertexPosition 用于查询关联的顶点本身。
## Writer methods
### DescriptorWriter
包含用于访问角色和骨骼各种元数据的只写访问器。
- `setName(name)`
Sets character name.
- `setArchetype(archetype)`
Sets character archetype.
- `setGender(gender)`
Sets character gender.
- `setAge(age)`
Sets character age.
- `setTranslationUnit(unit)`
Sets translation unit (cm or m).
- `setRotationUnit(unit)`
Sets rotation unit (degrees or radians).
- `setCoordinateSystem(system)`
Sets coordinate system (directions of the axes).
- `setLODCount(lodCount)`
Sets available levels of detail (e.g. 6 which means the following levels are available: [0,1,2,3,4,5], where 0 is the LOD with the highest details, and 5 is the LOD with lowest details).
- `setDBMaxLOD(lod)`
Sets the maximum level of detail stored in the DNA data for this character.
- `setDBComplexity(name)`
Sets name of the input control interface used to drive this character rig.
- `setDBName(name)`
Sets name of the database from which the character originates.
### DefinitionWriter
包含用于表示骨骼静态数据的 DNA 属性的只写访问器。
- `clearGUIControlNames()`
删除所有存储的 GUI 控制器名称。
- `setGUIControlName(index, name)`
设置指定 GUI 控制器的名称。
- `clearRawControlNames()`
删除所有存储的原始控制器名称。
- `setRawControlName(index, name)`
设置指定原始控制器的名称。
- `clearJointNames()`
删除所有存储的关节名称。
- `setJointName(index, name)`
设置指定关节的名称。
- `clearJointIndices()`
删除所有存储的关节索引。
- `setJointIndices(index, jointIndices, count)`
将关节索引列表存储到指定索引。该索引表示整个关节索引列表的位置,而不是其单个元素的位置,即关节索引 2D 矩阵中的行索引。
- `clearLODJointMappings()`
删除所有存储的 LOD 到关节列表索引的映射条目。
- `setLODJointMapping(lod, index)`
设置哪些关节属于哪个细节级别。
- `clearBlendShapeChannelNames()`
删除所有存储的变形通道名称。
- `setBlendShapeChannelName(index, name)`
设置指定变形通道的名称。
- `clearBlendShapeChannelIndices()`
删除所有存储的变形通道索引。
- `setBlendShapeChannelIndices(index, blendShapeChannelIndices, count)`
将变形通道名称索引列表存储到指定索引。该索引表示整个变形通道索引列表的位置,而不是其单个元素的位置,即变形通道索引 2D 矩阵中的行索引。
- `clearLODBlendShapeChannelMappings()`
删除所有存储的 LOD 到变形通道列表索引的映射条目。
- `setLODBlendShapeChannelMapping(lod, index)`
设置哪些变形通道属于哪个细节级别。
- `clearAnimatedMapNames()`
删除所有存储的动画贴图名称。
- `setAnimatedMapName(index, name)`
设置指定动画贴图的名称。
- `clearAnimatedMapIndices()`
删除所有存储的动画贴图索引。
- `setAnimatedMapIndices(index, animatedMapIndices, count)`
将动画贴图名称索引列表存储到指定索引。该索引表示整个动画贴图索引列表的位置,而不是其单个元素的位置,即动画贴图索引 2D 矩阵中的行索引。
- `clearLODAnimatedMapMappings()`
删除所有存储的 LOD 到动画贴图列表索引的映射条目。
- `setLODAnimatedMapMapping(lod, index)`
设置哪些动画贴图属于哪个细节级别。
- `clearMeshNames()`
删除所有存储的网格名称。
- `setMeshName(index, name)`
设置指定网格的名称。
- `clearMeshIndices()`
删除所有存储的网格索引。
- `setMeshIndices(index, meshIndices, count)`
将网格名称索引列表存储到指定索引。该索引表示整个网格索引列表的位置,而不是其单个元素的位置,即网格索引 2D 矩阵中的行索引。
- `clearLODMeshMappings()`
删除所有存储的 LOD 到网格列表索引的映射条目。
- `setLODMeshMapping(lod, index)`
设置哪些网格属于哪个细节级别。
- `clearMeshBlendShapeChannelMappings()`
删除所有存储的网格到变形通道的映射条目。
- `setMeshBlendShapeChannelMapping(index, meshIndex, blendShapeChannelIndex)`
将变形通道与其网格关联。
- `setJointHierarchy(jointIndices, count)`
设置描述关节之间父子关系的简单数组。
示例:
关节名称: [A, B, C, D, E, F, G, H]
层级结构: [0, 0, 0, 1, 1, 4, 2, 2]
描述了以下层级结构:
```
A
├── B
│ ├── D
│ └── E
│ └── F
└── C
├── G
└── H
```
- `setNeutralJointTranslations(translations, count)`
设置绑定姿态下关节的平移值。
- `setNeutralJointRotations(rotations, count)`
设置绑定姿态下关节的旋转值。
### BehaviorWriter
包含用于定义骨骼评估的 DNA 属性的只写访问器。
- `setGUIToRawInputIndices(inputIndices, count)`
设置用于将 GUI 映射到原始控制器的输入索引。
- `setGUIToRawOutputIndices(outputIndices, count)`
设置用于将 GUI 映射到原始控制器的输出索引。
- `setGUIToRawFromValues(fromValues, count)`
设置在 GUI 到原始控制器映射期间用于决定是否应评估特定条目的过滤值(下限)。
- `setGUIToRawToValues(toValues, count)`
设置在 GUI 到原始控制器映射期间用于决定是否应评估特定条目的过滤值(上限)。
- `setGUIToRawSlopeValues(slopeValues, count)`
设置在 GUI 到原始控制器映射期间用于计算输出值的计算值(斜率/梯度)。
- `setGUIToRawCutValues(cutValues, count)`
设置在 GUI 到原始控制器映射期间用于计算输出值的计算值(垂直截距)。
- `setPSDCount(count)`
设置不同 PSD 表情的数量。
- `setPSDRowIndices(rowIndices, count)`
设置将成为 PSD 矩阵行的 PSD(输入)索引。
- `setPSDColumnIndices(columnIndices, count)`
设置将成为 PSD 矩阵列的控制器(输入)索引。
- `setPSDValues(weights, count)`
设置与每个 PSD 行和列对相关联的权重。
- `setJointRowCount(rowCount)`
设置完整未压缩关节矩阵中的行数。
- `setJointColumnCount(columnCount)`
设置完整未压缩关节矩阵中的列数。
- `clearJointGroups()`
删除所有关节组。
- `deleteJointGroup(jointGroupIndex)`
删除指定的关节组。
- `setJointGroupLODs(jointGroupIndex, lods, count)`
设置指定关节组中每个细节级别的行数。
每个元素的位置代表级别本身,而值表示属于该级别的关节组内的行数。例如:
```
[12, 9, 3]
│ │ └── LOD-2 包含前 3 行
│ └── LOD-1 包含前 9 行
└── LOD-0 包含前 12 行
```
- `setJointGroupInputIndices(jointGroupIndex, inputIndices, count)`
设置指定关节组包含的列索引。这些列索引指向完整的未压缩关节矩阵。
- `setJointGroupOutputIndices(jointGroupIndex, outputIndices, count)`
设置指定关节组包含的行索引。这些行索引指向完整的未压缩关节矩阵。
- `setJointGroupValues(jointGroupIndex, values, count)`
设置指定关节组包含的值。
- `setJointGroupJointIndices(jointGroupIndex, jointIndices, count)`
设置指定关节组包含的关节索引。
- `setBlendShapeChannelLODs(lods, count)`
设置变形通道每个细节级别的输入索引数量。
每个元素的位置代表级别本身(例如 [0,1,2,3,4,5] 值 0 是最高细节的 LOD,值 5 是最低细节的 LOD),而值表示属于该级别的输入索引数量。
- `setBlendShapeChannelInputIndices(inputIndices, count)`
设置用于索引输入向量的输入索引。
- `setBlendShapeChannelOutputIndices(outputIndices, count)`
设置指定变形通道输出值位置的输出索引。
- `setAnimatedMapLODs(lods, count)`
设置动画贴图每个细节级别的行数。
每个元素的位置代表级别本身(例如 [0,1,2,3,4,5] 值 0 是最高细节的 LOD,值 5 是最低细节的 LOD),而值表示属于该级别的行数(在条件表中)。
- `setAnimatedMapInputIndices(inputIndices, count)`
设置用于索引输入值数组的输入索引。
- `setAnimatedMapOutputIndices(outputIndices, count)`
设置指定计算输出值位置的输出索引。
- `setAnimatedMapFromValues(fromValues, count)`
设置用于决定是否应评估特定条目的过滤值(下限)。
- `setAnimatedMapToValues(toValues, count)`
设置用于决定是否应评估特定条目的过滤值(上限)。
- `setAnimatedMapSlopeValues(slopeValues, count)`
设置用于计算输出值的计算值(斜率/梯度)。
- `setAnimatedMapCutValues(cutValues, count)`
设置用于计算输出值的计算值(垂直截距)。
### GeometryWriter
包含与骨骼相关的几何数据的只写访问器。
- `clearMeshes()`
删除所有网格。
- `deleteMesh(meshIndex)`
删除指定的网格。
- `setVertexPositions(meshIndex, positions, count)`
设置顶点位置。
- `setVertexTextureCoordinates(meshIndex, textureCoordinates, count)`
设置顶点纹理坐标。
- `setVertexNormals(meshIndex, normals, count)`
设置顶点法线。
- `setVertexLayouts(meshIndex, layouts, count)`
设置属于指定网格的顶点布局。
- `clearFaceVertexLayoutIndices(meshIndex)`
删除指定网格的所有顶点布局索引列表。
- `setFaceVertexLayoutIndices(meshIndex, faceIndex, layoutIndices, count)`
设置属于指定面的顶点布局索引。布局索引指向通过 setVertexLayouts() 设置的数组。
- `setMaximumInfluencePerVertex(meshIndex, maxInfluenceCount)`
设置可能影响任何单个顶点的最大关节数量。
- `clearSkinWeights(meshIndex)`
删除指定网格的所有蒙皮权重。
- `setSkinWeightsValues(meshIndex, vertexIndex, weights, count)`
设置影响引用顶点的蒙皮权重。权重总和必须等于 1。
- `setSkinWeightsJointIndices(meshIndex, vertexIndex, jointIndices, count)`
设置与指定顶点的每个蒙皮权重关联的关节索引。关节索引必须按与其关联的权重相同的顺序存储。
- `clearBlendShapeTargets(meshIndex)`
删除指定网格的所有变形目标。
- `setBlendShapeChannelIndex(meshIndex, blendShapeTargetIndex, blendShapeChannelIndex)`
设置指定变形目标对应的变形通道索引。将网格本地变形目标索引与定义层中的绝对变形通道索引关联。
- `setBlendShapeTargetDeltas(meshIndex, blendShapeTargetIndex, deltas, count)`
设置每个受影响顶点的增量。
- `setBlendShapeTargetVertexIndices(meshIndex, blendShapeTargetIndex, vertexIndices, count)`
设置受指定变形目标影响的顶点位置索引。顶点位置索引必须按与其关联的增量相同的顺序存储。

View File

@ -1,43 +0,0 @@
# DNAViewer
[`dna_viewer`](/dna_viewer) 包含从 DNA 文件读取和在 Maya 中创建功能性骨骼所需的所有类。
它的组织方式使每个选项都可配置,因此您可以轻松获得想要的确切结果。
## 示例
- [生成骨骼](/examples/dna_viewer_build_rig.py)
- [按 LOD 导出 FBX](/examples/dna_viewer_export_fbx.py)
- [将 Maya 场景的更改传播到 DNA](/examples/dna_viewer_grab_changes_from_scene_and_propagate_to_dna.py)
- [简单 UI](/examples/dna_viewer_run_in_maya.py)
## 从代码使用
有两个 [API](dna_viewer_api.md):
- [build_meshes](dna_viewer_api_build_meshes.md)
- [build_rig](dna_viewer_api_build_rig.md)
## 在 Maya 中使用
在 Maya 中的使用说明位于[此处](/docs/dna_viewer_maya.md)
## 文件夹结构
- [builder](/dna_viewer/builder) - 包含构建器类,用于轻松添加配置选项并构建场景、配置、网格等。
- [dnalib](/dna_viewer/dnalib) - 包含用于更好地访问 DNA 文件的 API 类。
- [ui](/dna_viewer/ui) - 包含 Maya UI 所需的类。
## 工作原理
一般流程如下:
![image](img/flow_general.png)
场景构建过程的流程如下:
![image](img/flow_scene_build.png)
骨骼构建过程的流程如下:
![image](img/flow_character_build.png)
图例:
- <span style="color:blue">蓝色: 构建器相关</span>
- <span style="color:green">绿色: 配置相关</span>
- <span style="color:brown">棕色: 模型相关</span>
- <span style="color:purple">紫色: 读取器相关</span>

View File

@ -1,54 +0,0 @@
# 环境设置
为了能够从 dna_viewer 导入,需要设置环境。这可以通过在下面提到的任何示例开头添加以下代码来完成:
```
from sys import path as syspath, platform
from os import environ, path as ospath
ROOT_DIR = fr"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/") # 如果使用 Maya,请使用绝对路径
ROOT_LIB_DIR = fr"{ROOT_DIR}/lib"
if platform == "win32":
LIB_DIR = f"{ROOT_LIB_DIR}/windows"
elif platform == "linux":
LIB_DIR = f"{ROOT_LIB_DIR}/linux"
else:
raise OSError("不支持的操作系统,请编译依赖项并添加 LIB_DIR 值")
if "MAYA_PLUG_IN_PATH" in environ:
separator = ":" if platform == "linux" else ";"
environ["MAYA_PLUG_IN_PATH"] = separator.join([environ["MAYA_PLUG_IN_PATH"], LIB_DIR])
else:
environ["MAYA_PLUG_IN_PATH"] = LIB_DIR
syspath.append(ROOT_DIR)
syspath.append(LIB_DIR)
```
从 Maya 运行时,应将 `ROOT_DIR` 设置为仓库根目录的绝对路径。
# DNA
## 加载 DNA
加载 DNA 并返回一个 [`DNA`](../dna_viewer/dnalib/dnalib.py#L13) 对象。
```
from dna_viewer import DNA
dna_ada = DNA(DNA_PATH_ADA)
dna_taro = DNA(DNA_PATH_TARO)
```
这使用以下参数:
- `dna_path: str` - 要使用的 DNA 文件的路径。
- `layers: Optional[List[Layer]]` - 要加载的 DNA 部分列表。如果未传递任何内容,将加载整个 DNA。等同于传递 Layer.all。
## 构建网格
构建网格 API 说明位于[此处](/docs/dna_viewer_api_build_meshes.md)。
## 构建骨骼
构建骨骼 API 说明位于[此处](/docs/dna_viewer_api_build_rig.md)。

View File

@ -1,106 +0,0 @@
# 网格工具
以下方法的目的是提供:
- 从给定 DNA 文件路径构建网格的简单机制
- 返回和打印 DNA 文件中包含的网格信息
## 导入
```
from dna_viewer import DNA, Config, build_meshes
```
```
DNA_PATH_ADA = f"{ROOT_DIR}/data/dna_files/Ada.dna"
DNA_PATH_TARO = f"{ROOT_DIR}/data/dna_files/Taro.dna"
```
## 创建 Config 实例([`Config`](../dna_viewer/builder/config.py#35))
创建一个将在网格构建过程中使用的配置对象。
```
config = Config(
add_joints=True,
add_blend_shapes=True,
add_skin_cluster=True,
add_ctrl_attributes_on_root_joint=True,
add_animated_map_attributes_on_root_joint=True,
lod_filter=[0, 1],
mesh_filter=["head"],
)
```
以下是 `Config` 类的一些属性:
- `add_joints: bool` - 表示是否应添加关节的标志,默认为 `True`
- `add_blend_shapes: bool` - 表示是否应添加变形的标志,默认为 `True`
- `add_skin_cluster: bool` - 表示是否应添加蒙皮簇的标志,默认为 `True`
- `add_ctrl_attributes_on_root_joint: bool` - 表示是否应将控制属性作为属性添加到根关节的标志,默认为 `False`。它们在引擎中用作 Rig Logic 输入的动画曲线。
- `add_animated_map_attributes_on_root_joint: bool` - 表示是否应将动画贴图属性添加到根关节作为属性的标志,默认为 `True`。它们在引擎中用作动画贴图的动画曲线。
**重要**: 某些标志值的组合可能会导致骨骼不可用或禁用某些功能!
## 构建网格 ([`build_meshes`](../dna_viewer/api.py#L26))
用于构建骨骼元素(关节、网格、变形和蒙皮簇),不包含 Rig Logic。
它返回已添加到场景中的网格的长名称。
```
config = Config(
add_joints=True,
add_blend_shapes=True,
add_skin_cluster=True,
add_ctrl_attributes_on_root_joint=True,
add_animated_map_attributes_on_root_joint=True,
lod_filter=[0, 1],
mesh_filter=["head"],
)
mesh_names = build_meshes(
dna=dna_ada,
config=config
)
```
这使用以下参数:
- `dna: DNA` - 通过 `DNA` 获得的 DNA 实例。
- `config: Config` - 配置实例。
```
mesh_names = build_meshes(dna=dna_ada)
```
这默认添加 DNA 文件中的所有网格。
### 示例
**重要**: 运行此示例之前需要执行上述[环境设置](dna_viewer_api.md#environment-setup)。
```
from dna_viewer import DNA, Config, build_meshes
# if you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
# Sets DNA file path
DNA_PATH_ADA = f"{ROOT_DIR}/data/dna_files/Ada.dna"
dna_ada = DNA(DNA_PATH_ADA)
# Starts the mesh build process with all the default values
build_meshes(dna=dna_ada)
# Creates the options to be passed in `build_meshes`
config = Config(
add_joints=True,
add_blend_shapes=True,
add_skin_cluster=True,
add_ctrl_attributes_on_root_joint=True,
add_animated_map_attributes_on_root_joint=True,
lod_filter=[0, 1],
mesh_filter=["head"],
)
# Starts the mesh building process with the provided parameters
# In this case it will create every mesh contained in LODs 0 and 1 with 'head` in it's name,
build_meshes(
dna=dna_ada,
config=config,
)
```

View File

@ -1,42 +0,0 @@
# 构建骨骼 ([`build_rig`](../dna_viewer/api.py#L9))
构建骨骼 API 用于从给定的 DNA 文件路径在 Maya 场景中轻松组装角色骨骼。
## 创建 RigConfig 实例([`RigConfig`](../dna_viewer/builder/config.py#35))
创建一个将在骨骼构建过程中使用的配置对象。
```
from dna_viewer import RigConfig
```
以下是 `RigConfig` 类的一些属性:
- `gui_path: str` - GUI 文件路径。
- `analog_gui_path: str` - 模拟 GUI 文件路径。
- `aas_path: str` - 附加组装脚本路径。
- `aas_method: str` - 应从附加组装脚本调用的方法名称。
- `add_ctrl_attributes_on_root_joint: bool` - 表示是否应在根关节上添加属性的标志,默认为 `True`
- `add_key_frames: bool` - 表示是否应添加关键帧的标志,默认为 `True`
## 示例
**重要**: 运行此示例之前需要执行上述[环境设置](dna_viewer_api.md#environment-setup)。
```
from dna_viewer import DNA, RigConfig, build_rig
# if you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
# Sets the values that will used
DNA_PATH_ADA = f"{ROOT_DIR}/data/dna_files/Ada.dna"
dna_ada = DNA(DNA_PATH_ADA)
config = RigConfig(
gui_path=f"{ROOT_DIR}/data/gui.ma",
analog_gui_path=f"{ROOT_DIR}/data/analog_gui.ma",
aas_path=f"{ROOT_DIR}/data/additional_assemble_script.py",
)
# Creates the rig
build_rig(dna=dna_ada, config=config)
```

View File

@ -1,13 +0,0 @@
# 在 Maya 中使用
还有一个 [Maya UI 窗口](/examples/dna_viewer_run_in_maya.py),可用于以非编程方式创建包含功能性骨骼的场景。
![image](img/ui.png)
![image](img/ui_extra_options.png)
在场景生成期间,RigLogic4 插件将自动加载,您可能会看到以下消息:
![image](img/maya_warn.png)
点击 `允许` 加载插件。如果启用 **应用于此位置的所有插件** 选项,Maya 将不再显示此通知。

View File

@ -1,100 +0,0 @@
# DNACalib
这个库用于对 DNA 文件进行修改。
它使用 C++ 编写,并且也有 Python 包装器。[SWIG](https://www.swig.org/) 库用于生成 Python 的绑定。DNACalib 可以在命令行或 Maya 中使用。
提供了 Windows 和 Linux 的二进制文件。**如果您使用不同的架构和/或平台,您必须自行构建 DNACalib。**
## DNACalib 文件夹结构
- [`DNACalib`](/dnacalib/DNACalib) - 包含 DNACalib 及其依赖项的 C++ 源代码。其中有一个用于读写 DNA 文件的库,以及一些其他实用库。
- [`PyDNACalib`](/dnacalib/PyDNACalib) - 包含用于生成 DNACalib Python 包装器的源代码。
- [`PyDNA`](/dnacalib/PyDNA) - 包含用于生成 DNA 库 Python 包装器的源代码,该库位于包含 C++ 源代码的 DNACalib 文件夹下。
- [`SPyUS`](/dnacalib/SPyUS) - 包含 PyDNACalib 和 PyDNA 都使用的一些通用 SWIG 接口文件。
- [`CMakeModulesExtra`](/dnacalib/CMakeModulesExtra) - 包含在整个项目中使用的一些通用 CMake 函数,包括 C++ 和 Python 包装器。
## 使用
例如,要更改中性关节的旋转值,使用
[`SetNeutralJointRotationsCommand`](/dnacalib/DNACalib/include/dnacalib/commands/SetNeutralJointRotationsCommand.h)。
下面是一个示例,它读取 DNA,将所有中性关节的旋转值更改为 `{1, 2, 3}`,并用这些新值覆写 DNA 文件。
```
// 创建 DNA 读取器
auto inOutStream = dnac::makeScoped<dnac::FileStream>("example.dna",
dnac::FileStream::AccessMode::ReadWrite,
dnac::FileStream::OpenMode::Binary);
auto reader = dnac::makeScoped<dnac::BinaryStreamReader>(inOutStream.get());
reader->read();
// 检查读取 DNA 文件时是否发生错误
if (!dnac::Status::isOk()) {
// 处理读取器错误
}
// 创建 DNACalib 读取器以编辑 DNA
auto dnaReader = dnac::makeScoped<dnac::DNACalibDNAReader>(reader.get());
std::vector<dnac::Vector3> rotations{dnaReader->getJointCount(), {1.0f, 2.0f, 3.0f}};
// 创建命令实例
dnac::SetNeutralJointRotationsCommand cmd{dnac::ConstArrayView<dnac::Vector3>{rotations}};
// 执行命令
cmd.run(dnaReader.get());
// 写入 DNA 文件
auto writer = dnac::makeScoped<dnac::BinaryStreamWriter>(inOutStream.get());
writer->setFrom(dnaReader.get());
writer->write();
// 检查写入 DNA 文件时是否发生错误
if (!dnac::Status::isOk()) {
// 处理写入器错误
}
```
## 示例
### C++
C++ 库使用示例可以在[此处](/dnacalib/DNACalib/examples)找到。
这些包括:
- [链接多个命令](/dnacalib/DNACalib/examples/CommandSequence.cpp)
- [重命名变形](/dnacalib/DNACalib/examples/SingleCommand.cpp)
### Python
从 Python 使用库的示例在[此处](/examples)。
这些包括:
- [展示几个命令](/examples/dnacalib_demo.py)
- [重命名关节](/examples/dnacalib_rename_joint_demo.py)
- [从头开始创建一个小型 DNA](/examples/dna_demo.py)
- [通过提取特定 LOD 从现有 DNA 创建新 DNA](/examples/dnacalib_lod_demo.py)
- [读取二进制 DNA 并以人类可读格式写入](/examples/dna_binary_to_json_demo.py)
- [删除关节](/examples/dnacalib_remove_joint.py)
- [清除变形数据](/examples/dnacalib_clear_blend_shapes.py)
- [从中性网格中减去值](/examples/dnacalib_neutral_mesh_subtract.py)
## 构建
提供了 64 位 Windows 和 Linux 的[预构建二进制文件](/lib)。
如果您使用不同的架构和/或平台,您必须构建 DNACalib。
先决条件:
- [CMake](https://cmake.org/download/) 至少 3.14 版本
- [SWIG](https://www.swig.org/download.html) 至少 4.0.0 版本
- [Python](https://www.python.org/downloads/) 要指定要使用的 python3 的确切版本,请设置 CMake 变量
`PYTHON3_EXACT_VERSION`。例如,要在 Maya 2022 中使用该库,请使用 3.7 版本。对于 Maya 2023,使用 3.9 版本。
使用 CMake 生成构建所需的构建脚本,例如通过从
[`MetaHuman-DNA-Calibration/dnacalib/`](/dnacalib) 目录执行以下命令:
```
mkdir build
cd build
cmake ..
```
然后,要开始构建过程:
```
cmake --build
```

View File

@ -1,78 +0,0 @@
# API 概述
DNA 修改是通过可用的命令完成的。每个命令都实现了 `run(DNACalibDNAReader* output)` 方法,
该方法修改通过其参数指定的 DNA。要配置在 `run()` 中发生的修改,可以通过构造函数或特定的 setter 方法传递参数。
以下文档适用于 C++。目前尚无 Python 文档。
所有可用命令列表:
## 用于删除 DNA 特定部分的命令:
- [`RemoveJointAnimationCommand`](/dnacalib/DNACalib/include/dnacalib/commands/RemoveJointAnimationCommand.h) 从 DNA 中删除
给定索引的关节动画。
- [`RemoveJointCommand`](/dnacalib/DNACalib/include/dnacalib/commands/RemoveJointCommand.h) 从 DNA 中删除给定
索引的关节。
- [`RemoveMeshCommand`](/dnacalib/DNACalib/include/dnacalib/commands/RemoveMeshCommand.h) 从 DNA 中删除给定索引
的网格。
- [`ClearBlendShapesCommand`](/dnacalib/DNACalib/include/dnacalib/commands/ClearBlendShapesCommand.h) 从 DNA 中清除所有变形数据。
## 用于重命名 DNA 特定部分的命令:
- [`RenameAnimatedMapCommand`](/dnacalib/DNACalib/include/dnacalib/commands/RenameAnimatedMapCommand.h) 重命名给定
索引或之前名称的动画贴图。
- [`RenameBlendShapeCommand`](/dnacalib/DNACalib/include/dnacalib/commands/RenameBlendShapeCommand.h) 重命名给定
索引或之前名称的变形。
- [`RenameJointCommand`](/dnacalib/DNACalib/include/dnacalib/commands/RenameJointCommand.h) 重命名给定
索引或之前名称的关节。
- [`RenameMeshCommand`](/dnacalib/DNACalib/include/dnacalib/commands/RenameMeshCommand.h) 重命名给定
索引或之前名称的网格。
## 用于变换 DNA 的命令:
- [`RotateCommand`](/dnacalib/DNACalib/include/dnacalib/commands/RotateCommand.h) 围绕给定原点旋转中性关节和顶点
位置。
- [`ScaleCommand`](/dnacalib/DNACalib/include/dnacalib/commands/ScaleCommand.h) 按因子缩放中性关节、顶点位置
和关节及变形增量。对于中性关节和关节增量,只缩放平移属性。
- [`TranslateCommand`](/dnacalib/DNACalib/include/dnacalib/commands/TranslateCommand.h) 平移中性关节和
顶点位置。
## 用于修改变形的命令:
- [`SetBlendShapeTargetDeltasCommand`](/dnacalib/DNACalib/include/dnacalib/commands/SetBlendShapeTargetDeltasCommand.h)
更改变形目标增量。
- [`PruneBlendShapeTargetsCommand`](/dnacalib/DNACalib/include/dnacalib/commands/PruneBlendShapeTargetsCommand.h)
修剪小于或等于指定阈值的变形目标增量。
## 用于更改绑定姿态的命令:
- [`SetNeutralJointRotationsCommand`](/dnacalib/DNACalib/include/dnacalib/commands/SetNeutralJointRotationsCommand.h)
为中性关节设置新的旋转值。
- [`SetNeutralJointTranslationsCommand`](/dnacalib/DNACalib/include/dnacalib/commands/SetNeutralJointTranslationsCommand.h)
为中性关节设置新的平移值。
- [`SetVertexPositionsCommand`](/dnacalib/DNACalib/include/dnacalib/commands/SetVertexPositionsCommand.h) 更改
顶点位置值。
## 执行有用计算或提供附加功能的命令:
- [`SetLODsCommand`](/dnacalib/DNACalib/include/dnacalib/commands/SetLODsCommand.h) 过滤 DNA,使其只包含
指定 LOD 的数据。
- [`CalculateMeshLowerLODsCommand`](/dnacalib/DNACalib/include/dnacalib/commands/CalculateMeshLowerLODsCommand.h)
重新计算指定网格的较低 LOD 网格的顶点位置。
- [`CommandSequence`](/dnacalib/DNACalib/include/dnacalib/commands/CommandSequence.h) 在指定的 DNA 上运行
一系列命令。
每个可用命令及其方法的更详细描述可以在
[`DNACalib/include/dnacalib/commands`](/dnacalib/DNACalib/include/dnacalib/commands/)中找到。

View File

@ -1,35 +0,0 @@
# 常见问题解答 (FAQ)
## 修复 "RuntimeError: Error loading DNA: DNA signature mismatched, expected DNA, got ver?"
要修复此问题,您应该安装 [git-lfs](https://git-lfs.github.com/),然后重新克隆仓库。
这样 DNA 文件就会正确下载。
如果您无法安装 git-lfs,可以手动下载 DNA 文件。
## 如何分发 Maya 场景?
如果您包含以下内容,您的场景应该可以直接使用:
- 场景文件 (`.mb` 文件)
- DNA (`.dna` 文件)
- 工作空间 (`workspace.mel` 文件)
所有这些文件需要一起分发。如果这些文件没有打包在一起,并且您在 Maya 中遇到骨骼问题,
请尝试以下步骤:
### 如何共享生成的文件?
如果您想将生成的 Maya 场景分发给其他用户,必须将 `.dna` 文件和 `workspace.mel` 与场景一起分发。
### 如何打开生成的场景?
在加载生成的场景之前,请按照以下步骤操作:
- 从主菜单中,转到 文件 > 设置项目。
- 选择 `workspace.mel`
- 设置包含文件夹(包含生成的 maya 场景、`.dna` 文件和 `workspace.mel`)。
### 如何在 Maya 场景中更改 DNA 路径?
如果您想更改场景中的 DNA 路径:
- 在 `大纲视图` 中,取消选择 **仅 DAG 对象**:
- ![image](img/change_path_outliner_settings.png)
- 仍在大纲视图中,搜索 `rl4`。您会看到一个名称以 `rl4Embedded_` 开头的文件。点击此文件以选择它。
- ![image](img/change_path_outliner.png)
- 在 `属性编辑器` 中,您可以通过 `Dna 文件路径` 更改路径:
- ![image](img/change_path_node_path.png)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 517 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 209 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,62 +0,0 @@
# 仓库组织结构
本仓库包含两个独立的组件:
1. **dnacalib C++ 库** - 用于操作 DNA 文件
2. **dna_viewer python 代码** - 用于在 Autodesk Maya 中可视化 DNA
# 文件夹结构
- [dnacalib](/dnacalib) - DNACalib 源代码
- [dna_viewer](/dna_viewer) - dna_viewer 源代码
- [examples](/examples) - 多个 Python 脚本,展示 dna_viewer 和 DNACalib Python 封装的基本用法
- [lib](/lib) - DNACalib、PyDNACalib 和 PyDNA 的预编译二进制文件
- [data](/data) - 必需的 DNA 文件和 Maya 场景文件
- [docs](/docs) - 文档
## DNACalib
文档位于[此处](dnacalib.md)
## DNAViewer
文档位于[此处](dna_viewer.md)
## 示例
要运行 [DNAViewer 示例](/docs/dna_viewer.md#examples),您必须安装 Maya 2022。
要运行 [DNACalib 示例](/docs/dnacalib.md#python),您需要 Python3。
## Lib
[Lib 文件夹](/lib)包含 Windows 和 Linux 平台的 DNACalib 库预编译二进制文件。此外,还提供了 Maya 的 RL4 插件。
### Linux 路径
您需要为[lib](lib/Maya2022/linux)中的所有 **.so** 文件复制或创建符号链接:
```shell
sudo ln -s ~/MetaHuman-DNA-Calibration/lib/Maya2022/linux/_py3dna.so /usr/lib/_py3dna.so
sudo ln -s ~/MetaHuman-DNA-Calibration/lib/Maya2022/linux/libdnacalib.so /usr/lib/libdnacalib.so
sudo ln -s ~/MetaHuman-DNA-Calibration/lib/Maya2022/linux/libdnacalib.so.6 /usr/lib/libdnacalib.so.6
sudo ln -s ~/MetaHuman-DNA-Calibration/lib/Maya2022/linux/libembeddedRL4.so /usr/lib/embeddedRL4.mll
sudo ln -s ~/MetaHuman-DNA-Calibration/lib/Maya2022/linux/MayaUERBFPlugin.mll /usr/lib/MayaUERBFPlugin.mll
```
注意:请将路径 `~/MetaHuman-DNA-Calibration` 更改为您的 `MetaHuman-DNA-Calibration` 实际所在位置。
## Data
[`data 文件夹`](/data)包含示例 DNA 文件。我们提供了两个 MetaHuman DNA 文件(我们的首个预设 Ada 和 Taro)。
| Ada | Taro |
|---|---|
|![image](img/metahuman_008.png)| ![image](img/metahuman_010.png) |
此外,我们添加了在 Maya 场景组装过程中使用的[`gui`](/data/gui.ma)和[`analog_gui`](/data/analog_gui.ma) Maya 场景文件。
另外,[`additional_assemble_script.py`](/data/additional_assemble_script.py)用于组织场景中的对象并连接控制器。理想的设置如下所示:
![image](img/aas.png)
MHC 2023 春季版本对骨骼定义进行了更改(增加了关节数量和表情数量)。
为适应这些更改,我们在 `/data/mh4` 文件夹中添加了几个文件:新的[gui 场景](/data/mh4/gui.ma)、更新的[组装脚本](/data/mh4/additional_assemble_script.py)和 Ada 的[DNA 文件](data/mh4/dna_files/Ada.dna)示例。
此外,在 lib 文件夹中我们添加了用于控制颈部表情的 Maya RBF 插件。最近对颈部设置进行了改进,添加 RBF 插件以及新的 gui 场景来使用它,我们可以获得更好的颈部变形效果。

View File

@ -1,83 +0,0 @@
"""
This example demonstrates reading a DNA file in binary format and writing it in a human readable JSON format.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
- usage in command line:
python dna_binary_to_json_demo.py
mayapy dna_binary_to_json_demo.py
- usage in Maya:
1. copy whole content of this file to Maya Script Editor
2. change value of ROOT_DIR to absolute path of dna_calibration, e.g. `c:/dna_calibration` in Windows or `/home/user/dna_calibration`. Important:
Use `/` (forward slash), because Maya uses forward slashes in path.
- customization:
- change CHARACTER_NAME to Taro, or the name of a custom DNA file placed in /data/dna_files
Expected: Script will generate Ada_output.json in OUTPUT_DIR from original Ada.dna.
NOTE: If OUTPUT_DIR does not exist, it will be created.
"""
from os import makedirs
from os import path as ospath
# If you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
OUTPUT_DIR = f"{ROOT_DIR}/output"
CHARACTER_NAME = "Ada"
DATA_DIR = f"{ROOT_DIR}/data"
CHARACTER_DNA = f"{DATA_DIR}/dna_files/{CHARACTER_NAME}.dna"
OUTPUT_DNA = f"{OUTPUT_DIR}/{CHARACTER_NAME}_output.json"
from dna import DataLayer_All, FileStream, Status, BinaryStreamReader, JSONStreamWriter
def load_dna(path):
stream = FileStream(path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
reader = BinaryStreamReader(stream, DataLayer_All)
reader.read()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error loading DNA: {status.message}")
return reader
def save_dna(reader, path):
stream = FileStream(path, FileStream.AccessMode_Write, FileStream.OpenMode_Binary)
writer = JSONStreamWriter(stream)
# Create a writer based on the reader using all data layers (if no argument is passed, DataLayer_All is the default value)
writer.setFrom(reader)
# Alternatively, a writer can be created using only a subset of layers,
# e.g. to write only Behavior layer (Descriptor and Definition included with it), use:
# writer.setFrom(reader, DataLayer_Behavior)
#
# Available layer options and their approximate sizes for this example (Ada.dna, JSON format):
# DataLayer_Descriptor - ~ 3 KB
# DataLayer_Definition - includes Descriptor, ~ 131 KB
# DataLayer_Behavior - includes Descriptor and Definition, ~ 10 MB
# DataLayer_Geometry - includes Descriptor and Definition, ~ 191 MB
# DataLayer_GeometryWithoutBlendShapes - includes Descriptor and Definition, ~ 22 MB
# DataLayer_AllWithoutBlendShapes - includes everything except blend shapes from Geometry, ~ 32 MB
# DataLayer_All - ~ 201 MB
#
# If using one of the other layer options, be sure to add it to the import list.
#
# Beside specifying layers when creating a writer, layers to use can be specified when
# creating a reader as well.
writer.write()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error saving DNA: {status.message}")
def create_json_dna(input_path, output_path):
dna_reader = load_dna(input_path)
save_dna(dna_reader, output_path)
print('Done.')
if __name__ == "__main__":
makedirs(OUTPUT_DIR, exist_ok=True)
create_json_dna(CHARACTER_DNA, OUTPUT_DNA)

View File

@ -1,101 +0,0 @@
"""
This example demonstrates creating DNA from scratch.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
- usage in command line:
python dna_demo.py
mayapy dna_demo.py
- usage in Maya:
1. copy whole content of this file to Maya Script Editor
2. change value of ROOT_DIR to absolute path of dna_calibration, e.g. `c:/dna_calibration` in Windows or `/home/user/dna_calibration`. Important:
Use `/` (forward slash), because Maya uses forward slashes in path.
- customization:
- change CHARACTER_NAME to Taro, or the name of a custom DNA file placed in /data/dna_files
Expected: Script will generate Ada_output.dna in OUTPUT_DIR from original Ada.dna.
NOTE: If OUTPUT_DIR does not exist, it will be created.
"""
from os import makedirs
from os import path as ospath
# If you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
OUTPUT_DIR = f"{ROOT_DIR}/output"
CHARACTER_NAME = "Ada"
DATA_DIR = f"{ROOT_DIR}/data"
CHARACTER_DNA = f"{DATA_DIR}/dna_files/{CHARACTER_NAME}.dna"
OUTPUT_DNA = f"{OUTPUT_DIR}/{CHARACTER_NAME}_output.dna"
from dna import DataLayer_All, FileStream, Status, BinaryStreamReader, BinaryStreamWriter
def create_dna(path):
stream = FileStream(path, FileStream.AccessMode_Write, FileStream.OpenMode_Binary)
writer = BinaryStreamWriter(stream)
# Sets a couple of parameters about in the dna that is about to be created and written to
writer.setName("rig name")
writer.setLODCount(4)
writer.setJointName(0, "spine")
writer.setJointName(1, "neck")
writer.setMeshName(0, "head")
writer.setVertexPositions(0, [[0.0, 0.5, 0.3], [1.0, 3.0, -8.0]])
writer.setVertexTextureCoordinates(0, [[0.25, 0.55], [1.5, 3.6]])
# Creates the DNA
writer.write()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error saving DNA: {status.message}")
def load_dna(path):
stream = FileStream(path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
reader = BinaryStreamReader(stream, DataLayer_All)
reader.read()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error loading DNA: {status.message}")
return reader
def print_dna_summary(dna_reader):
print(f"Name: {dna_reader.getName()}")
print(f"Joint count: {dna_reader.getJointCount()}")
joint_names = ", ".join(
dna_reader.getJointName(i) for i in range(dna_reader.getJointCount())
)
print(f"Joint names: {joint_names}")
for mesh_idx in range(dna_reader.getMeshCount()):
# Get vertices one by one
for vtx_id in range(dna_reader.getVertexPositionCount(mesh_idx)):
vtx = dna_reader.getVertexPosition(mesh_idx, vtx_id)
print(f"Mesh {mesh_idx} - Vertex {vtx_id} : {vtx}")
# Get all X / Y / Z coordinates
print(dna_reader.getVertexPositionXs(mesh_idx))
print(dna_reader.getVertexPositionYs(mesh_idx))
print(dna_reader.getVertexPositionZs(mesh_idx))
for tc_idx in range(dna_reader.getVertexTextureCoordinateCount(mesh_idx)):
tex_coord = dna_reader.getVertexTextureCoordinate(mesh_idx, tc_idx)
print(f"Mesh {mesh_idx} - Texture coordinate {tc_idx} : {tex_coord}")
def create_new_dna(dna_path):
create_dna(dna_path)
dna_reader = load_dna(dna_path)
print_dna_summary(dna_reader)
print('Done.')
if __name__ == "__main__":
makedirs(OUTPUT_DIR, exist_ok=True)
create_new_dna(OUTPUT_DNA)

View File

@ -1,74 +0,0 @@
"""
This example demonstrates generating functional rig in maya scene and exporting fbx per lod.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
- usage in command line:
python dna_viewer_build_rig.py
mayapy dna_viewer_build_rig.py
NOTE: Script cannot be called with Python, it must be called with mayapy.
- usage in Maya:
1. copy whole content of this file to Maya Script Editor
2. change value of ROOT_DIR to absolute path of dna_calibration, e.g. `c:/dna_calibration` in Windows or `/home/user/dna_calibration`. Important:
Use `/` (forward slash), because Maya uses forward slashes in path.
- customization:
- change CHARACTER_NAME to Taro, or the name of a custom DNA file placed in /data/dna_files
Expected:
- script will generate maya scene Ada.mb in OUTPUT_DIR
- script will generate workspace.mel in OUTPUT_DIR
- script will copy original Ada.dna file to OUTPUT_DIR
Expected: script will generate <PATH TO NEW DNA FILE>.
NOTE: If OUTPUT_DIR does not exist, it will be created.
"""
from os import makedirs
from os import path as ospath
from shutil import copyfile
# if you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
OUTPUT_DIR = f"{ROOT_DIR}/output"
EXAMPLES_DIR = f"{ROOT_DIR}/examples"
ROOT_LIB_DIR = f"{ROOT_DIR}/lib"
CHARACTER_NAME = "Ada"
DATA_DIR = f"{ROOT_DIR}/data"
CHARACTER_DNA = f"{DATA_DIR}/dna_files/{CHARACTER_NAME}.dna"
ANALOG_GUI = f"{DATA_DIR}/analog_gui.ma"
GUI = f"{DATA_DIR}/gui.ma"
ADDITIONAL_ASSEMBLE_SCRIPT = f"{DATA_DIR}/additional_assemble_script.py"
from maya import cmds, mel
from dna_viewer import DNA, RigConfig, build_rig
if __name__ == "__main__":
dna = DNA(CHARACTER_DNA)
makedirs(OUTPUT_DIR, exist_ok=True)
# This fixes warning when calling this script with headless maya Warning: line 1: Unknown object type: HIKCharacterNode
mel.eval(f"HIKCharacterControlsTool;")
# Generate workspace.mel
mel.eval(f'setProject "{OUTPUT_DIR}";')
config = RigConfig(
gui_path=GUI,
analog_gui_path=ANALOG_GUI,
aas_path=ADDITIONAL_ASSEMBLE_SCRIPT,
)
# Creates the rig
build_rig(dna=dna, config=config)
# Renames and saves the scene
cmds.file(rename=f"{OUTPUT_DIR}/{CHARACTER_NAME}.mb")
cmds.file(save=True)
# Copy dna file and workspace file alongside generated scene
copyfile(CHARACTER_DNA, f"{OUTPUT_DIR}/{CHARACTER_NAME}.dna")

View File

@ -1,355 +0,0 @@
"""
This example demonstrates generating functional rig based on DNA file in Maya scene with applied textures.
Maps added in data folder belong to Ada preset character. User should use source data downloaded from Quixel Bridge.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
- usage in command line:
mayapy dna_viewer_build_rig_with_textures.py
NOTE: Script cannot be called with Python, it must be called with mayapy.
- usage in Maya:
1. copy whole content of this file to Maya Script Editor
2. change value of ROOT_DIR to absolute path of dna_calibration, e.g. `c:/dna_calibration` in Windows or `/home/user/dna_calibration`. Important:
Use `/` (forward slash), because Maya uses forward slashes in path.
"""
import time
import os
# if you use Maya, use absolute path
ROOT_DIR = f"{os.path.dirname(os.path.abspath(__file__))}/..".replace("\\", "/")
DATA_DIR = f"{ROOT_DIR}/data"
from maya import cmds, mel
from dna_viewer import (
DNA,
RigConfig,
build_rig
)
ORIENT_Y = [0.0, 0.0, 0.0]
COMMON_MAP_INFOS = [
("dx11_diffuseIrradiance", 1),
("dx11_jitter", 1),
("dx11_skinLUT", 1),
("dx11_specularIrradiance", 1),
]
MAP_INFOS = [
("head_color", 1),
("head_cm1_color", 0),
("head_cm2_color", 0),
("head_cm3_color", 0),
("head_normal", 1),
("head_wm1_normal", 0),
("head_wm2_normal", 0),
("head_wm3_normal", 0),
("head_specular", 1),
("head_specular_16Bits", 1),
("head_occlusion", 1),
("head_occlusion_16Bits", 1),
("head_cavity", 1),
("head_cavity_16Bits", 1),
("head_transmission", 1),
("head_transmission_16Bits", 1),
("head_curvature", 1),
("head_curvature_16Bits", 1),
("head_position", 1),
("head_position_16Bits", 1),
("head_worldspace", 1),
("head_worldspace_16Bits", 1),
("head_bentNormal", 1),
("head_bentNormal_16Bits", 1),
("teeth_color", 1),
("teeth_normal", 1),
("eyes_color", 1),
("eyeLeft_color", 1),
("eyeRight_color", 1),
("eyeLeft_normal", 1),
("eyeRight_normal", 1),
("eyes_color_16Bits", 1),
("eyes_normal", 1),
("eyes_normal_16Bits", 1),
("eyelashes_color", 1),
]
MASKS = [
"head_wm1_blink_L",
"head_wm1_blink_R",
"head_wm1_browsRaiseInner_L",
"head_wm1_browsRaiseInner_R",
"head_wm1_browsRaiseOuter_L",
"head_wm1_browsRaiseOuter_R",
"head_wm1_chinRaise_L",
"head_wm1_chinRaise_R",
"head_wm1_jawOpen",
"head_wm1_purse_DL",
"head_wm1_purse_DR",
"head_wm1_purse_UL",
"head_wm1_purse_UR",
"head_wm1_squintInner_L",
"head_wm1_squintInner_R",
"head_wm2_browsDown_L",
"head_wm2_browsDown_R",
"head_wm2_browsLateral_L",
"head_wm2_browsLateral_R",
"head_wm2_mouthStretch_L",
"head_wm2_mouthStretch_R",
"head_wm2_neckStretch_L",
"head_wm2_neckStretch_R",
"head_wm2_noseWrinkler_L",
"head_wm2_noseWrinkler_R",
"head_wm3_cheekRaiseInner_L",
"head_wm3_cheekRaiseInner_R",
"head_wm3_cheekRaiseOuter_L",
"head_wm3_cheekRaiseOuter_R",
"head_wm3_cheekRaiseUpper_L",
"head_wm3_cheekRaiseUpper_R",
"head_wm3_smile_L",
"head_wm3_smile_R",
"head_wm13_lips_DL",
"head_wm13_lips_DR",
"head_wm13_lips_UL",
"head_wm13_lips_UR",
]
MESH_SHADER_MAPPING = {
"head_lod": "head_shader",
"teeth_lod": "teeth_shader",
"saliva_lod": "saliva_shader",
"eyeLeft_lod": "eyeLeft_shader",
"eyeRight_lod": "eyeRight_shader",
"eyeshell_lod": "eyeshell_shader",
"eyelashes_lod": "eyelashes_shader",
"eyelashesShadow_lod": "eyelashesShadow_shader",
"eyeEdge_lod": "eyeEdge_shader",
"cartilage_lod": "eyeEdge_shader",
}
SHADERS = ["head_shader", "teeth_shader", "eyeLeft_shader", "eyeRight_shader"]
SHADER_ATTRIBUTES_MAPPING = {
"FRM_WMmultipliers.head_cm2_color_head_wm2_browsDown_L": "shader_head_shader.maskWeight_00",
"FRM_WMmultipliers.head_wm2_normal_head_wm2_browsDown_L": "shader_head_shader.maskWeight_01",
"FRM_WMmultipliers.head_cm2_color_head_wm2_browsDown_R": "shader_head_shader.maskWeight_02",
"FRM_WMmultipliers.head_wm2_normal_head_wm2_browsDown_R": "shader_head_shader.maskWeight_03",
"FRM_WMmultipliers.head_cm2_color_head_wm2_browsLateral_L": "shader_head_shader.maskWeight_04",
"FRM_WMmultipliers.head_wm2_normal_head_wm2_browsLateral_L": "shader_head_shader.maskWeight_05",
"FRM_WMmultipliers.head_cm2_color_head_wm2_browsLateral_R": "shader_head_shader.maskWeight_06",
"FRM_WMmultipliers.head_wm2_normal_head_wm2_browsLateral_R": "shader_head_shader.maskWeight_07",
"FRM_WMmultipliers.head_cm1_color_head_wm1_browsRaiseInner_L": "shader_head_shader.maskWeight_08",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_browsRaiseInner_L": "shader_head_shader.maskWeight_09",
"FRM_WMmultipliers.head_cm1_color_head_wm1_browsRaiseInner_R": "shader_head_shader.maskWeight_10",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_browsRaiseInner_R": "shader_head_shader.maskWeight_11",
"FRM_WMmultipliers.head_cm1_color_head_wm1_browsRaiseOuter_L": "shader_head_shader.maskWeight_12",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_browsRaiseOuter_L": "shader_head_shader.maskWeight_13",
"FRM_WMmultipliers.head_cm1_color_head_wm1_browsRaiseOuter_R": "shader_head_shader.maskWeight_14",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_browsRaiseOuter_R": "shader_head_shader.maskWeight_15",
"FRM_WMmultipliers.head_cm1_color_head_wm1_blink_L": "shader_head_shader.maskWeight_16",
"FRM_WMmultipliers.head_cm1_color_head_wm1_squintInner_L": "shader_head_shader.maskWeight_17",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_blink_L": "shader_head_shader.maskWeight_18",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_squintInner_L": "shader_head_shader.maskWeight_19",
"FRM_WMmultipliers.head_cm1_color_head_wm1_blink_R": "shader_head_shader.maskWeight_20",
"FRM_WMmultipliers.head_cm1_color_head_wm1_squintInner_R": "shader_head_shader.maskWeight_21",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_blink_R": "shader_head_shader.maskWeight_22",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_squintInner_R": "shader_head_shader.maskWeight_23",
"FRM_WMmultipliers.head_cm3_color_head_wm3_cheekRaiseInner_L": "shader_head_shader.maskWeight_24",
"FRM_WMmultipliers.head_cm3_color_head_wm3_cheekRaiseOuter_L": "shader_head_shader.maskWeight_25",
"FRM_WMmultipliers.head_cm3_color_head_wm3_cheekRaiseUpper_L": "shader_head_shader.maskWeight_26",
"FRM_WMmultipliers.head_wm3_normal_head_wm3_cheekRaiseInner_L": "shader_head_shader.maskWeight_27",
"FRM_WMmultipliers.head_wm3_normal_head_wm3_cheekRaiseOuter_L": "shader_head_shader.maskWeight_28",
"FRM_WMmultipliers.head_wm3_normal_head_wm3_cheekRaiseUpper_L": "shader_head_shader.maskWeight_29",
"FRM_WMmultipliers.head_cm3_color_head_wm3_cheekRaiseInner_R": "shader_head_shader.maskWeight_30",
"FRM_WMmultipliers.head_cm3_color_head_wm3_cheekRaiseOuter_R": "shader_head_shader.maskWeight_31",
"FRM_WMmultipliers.head_cm3_color_head_wm3_cheekRaiseUpper_R": "shader_head_shader.maskWeight_32",
"FRM_WMmultipliers.head_wm3_normal_head_wm3_cheekRaiseInner_R": "shader_head_shader.maskWeight_33",
"FRM_WMmultipliers.head_wm3_normal_head_wm3_cheekRaiseOuter_R": "shader_head_shader.maskWeight_34",
"FRM_WMmultipliers.head_wm3_normal_head_wm3_cheekRaiseUpper_R": "shader_head_shader.maskWeight_35",
"FRM_WMmultipliers.head_cm2_color_head_wm2_noseWrinkler_L": "shader_head_shader.maskWeight_36",
"FRM_WMmultipliers.head_wm2_normal_head_wm2_noseWrinkler_L": "shader_head_shader.maskWeight_37",
"FRM_WMmultipliers.head_cm2_color_head_wm2_noseWrinkler_R": "shader_head_shader.maskWeight_38",
"FRM_WMmultipliers.head_wm2_normal_head_wm2_noseWrinkler_R": "shader_head_shader.maskWeight_39",
"FRM_WMmultipliers.head_cm3_color_head_wm3_smile_L": "shader_head_shader.maskWeight_40",
"FRM_WMmultipliers.head_wm3_normal_head_wm3_smile_L": "shader_head_shader.maskWeight_41",
"FRM_WMmultipliers.head_cm1_color_head_wm13_lips_UL": "shader_head_shader.maskWeight_42",
"FRM_WMmultipliers.head_cm1_color_head_wm13_lips_UR": "shader_head_shader.maskWeight_43",
"FRM_WMmultipliers.head_cm1_color_head_wm13_lips_DL": "shader_head_shader.maskWeight_44",
"FRM_WMmultipliers.head_cm1_color_head_wm13_lips_DR": "shader_head_shader.maskWeight_45",
"FRM_WMmultipliers.head_wm1_normal_head_wm13_lips_UL": "shader_head_shader.maskWeight_46",
"FRM_WMmultipliers.head_wm1_normal_head_wm13_lips_UR": "shader_head_shader.maskWeight_47",
"FRM_WMmultipliers.head_wm1_normal_head_wm13_lips_DL": "shader_head_shader.maskWeight_48",
"FRM_WMmultipliers.head_wm1_normal_head_wm13_lips_DR": "shader_head_shader.maskWeight_49",
"FRM_WMmultipliers.head_cm3_color_head_wm3_smile_R": "shader_head_shader.maskWeight_50",
"FRM_WMmultipliers.head_wm3_normal_head_wm3_smile_R": "shader_head_shader.maskWeight_51",
"FRM_WMmultipliers.head_cm3_color_head_wm13_lips_UL": "shader_head_shader.maskWeight_52",
"FRM_WMmultipliers.head_cm3_color_head_wm13_lips_DL": "shader_head_shader.maskWeight_53",
"FRM_WMmultipliers.head_wm3_normal_head_wm13_lips_UL": "shader_head_shader.maskWeight_54",
"FRM_WMmultipliers.head_wm3_normal_head_wm13_lips_DL": "shader_head_shader.maskWeight_55",
"FRM_WMmultipliers.head_cm3_color_head_wm13_lips_UR": "shader_head_shader.maskWeight_56",
"FRM_WMmultipliers.head_cm3_color_head_wm13_lips_DR": "shader_head_shader.maskWeight_57",
"FRM_WMmultipliers.head_wm3_normal_head_wm13_lips_UR": "shader_head_shader.maskWeight_58",
"FRM_WMmultipliers.head_wm3_normal_head_wm13_lips_DR": "shader_head_shader.maskWeight_59",
"FRM_WMmultipliers.head_cm2_color_head_wm2_mouthStretch_L": "shader_head_shader.maskWeight_60",
"FRM_WMmultipliers.head_wm2_normal_head_wm2_mouthStretch_L": "shader_head_shader.maskWeight_61",
"FRM_WMmultipliers.head_cm2_color_head_wm2_mouthStretch_R": "shader_head_shader.maskWeight_62",
"FRM_WMmultipliers.head_wm2_normal_head_wm2_mouthStretch_R": "shader_head_shader.maskWeight_63",
"FRM_WMmultipliers.head_cm1_color_head_wm1_purse_UL": "shader_head_shader.maskWeight_64",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_purse_UL": "shader_head_shader.maskWeight_65",
"FRM_WMmultipliers.head_cm1_color_head_wm1_purse_UR": "shader_head_shader.maskWeight_66",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_purse_UR": "shader_head_shader.maskWeight_67",
"FRM_WMmultipliers.head_cm1_color_head_wm1_purse_DL": "shader_head_shader.maskWeight_68",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_purse_DL": "shader_head_shader.maskWeight_69",
"FRM_WMmultipliers.head_cm1_color_head_wm1_purse_DR": "shader_head_shader.maskWeight_70",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_purse_DR": "shader_head_shader.maskWeight_71",
"FRM_WMmultipliers.head_cm1_color_head_wm1_chinRaise_L": "shader_head_shader.maskWeight_72",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_chinRaise_L": "shader_head_shader.maskWeight_73",
"FRM_WMmultipliers.head_cm1_color_head_wm1_chinRaise_R": "shader_head_shader.maskWeight_74",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_chinRaise_R": "shader_head_shader.maskWeight_75",
"FRM_WMmultipliers.head_cm1_color_head_wm1_jawOpen": "shader_head_shader.maskWeight_76",
"FRM_WMmultipliers.head_wm1_normal_head_wm1_jawOpen": "shader_head_shader.maskWeight_77",
"FRM_WMmultipliers.head_cm2_color_head_wm2_neckStretch_L": "shader_head_shader.maskWeight_78",
"FRM_WMmultipliers.head_wm2_normal_head_wm2_neckStretch_L": "shader_head_shader.maskWeight_79",
"FRM_WMmultipliers.head_cm2_color_head_wm2_neckStretch_R": "shader_head_shader.maskWeight_80",
"FRM_WMmultipliers.head_wm2_normal_head_wm2_neckStretch_R": "shader_head_shader.maskWeight_81",
}
# Methods
def import_head_shaders(shader_scene_path, shaders_dir_path, masks_dir_path, maps_dir_path):
import_shader(shader_scene_path, MESH_SHADER_MAPPING)
resolve_scene_shader_paths(SHADERS, shaders_dir_path)
set_mask_textures(MASKS, masks_dir_path)
set_map_textures(COMMON_MAP_INFOS, maps_dir_path)
set_map_textures(MAP_INFOS, maps_dir_path)
connect_attributes_to_shader(SHADER_ATTRIBUTES_MAPPING)
def import_shader(shader_scene_path, mesh_shader_mapping):
print("Shader scene imported")
cmds.file(shader_scene_path, options="v=0", type="mayaAscii", i=True)
try:
items = mesh_shader_mapping.iteritems()
except:
items = mesh_shader_mapping.items()
for meshName, shaderName in items:
for lodLvl in range(0, 8):
try:
# Apply shader to all meshes based on LOD level
resolved_mesh_name = meshName + str(lodLvl) + "_mesh"
shader = "shader_" + shaderName
cmds.select(resolved_mesh_name, replace=True)
mel.eval("sets -e -forceElement " + shader + "SG")
except:
print("Skipped adding shader for mesh %s." % meshName)
def resolve_scene_shader_paths(shaders, folder_name):
for shader in shaders:
node_name = "shader_" + shader
if not cmds.objExists(node_name):
continue
file_shader_name = cmds.getAttr(node_name + ".shader")
shader_folder_name, shader_file_name = os.path.split(file_shader_name)
shader_folder_name = folder_name
cmds.setAttr(
node_name + ".shader",
shader_folder_name + "/" + shader_file_name,
type="string",
)
def set_mask_textures(masks, folder_name):
for mask in masks:
node_name = "maskFile_" + mask
if not cmds.objExists(node_name):
continue
file_texture_name = cmds.getAttr(node_name + ".fileTextureName")
texture_folder_name, texture_file_name = os.path.split(file_texture_name)
texture_folder_name = folder_name
cmds.setAttr(
node_name + ".fileTextureName",
texture_folder_name + "/" + texture_file_name,
type="string",
)
def set_map_textures(map_infos, folder_name):
for mapInfo in map_infos:
node_name = "mapFile_" + mapInfo[0]
if mapInfo[1]:
node_name = "baseMapFile_" + mapInfo[0]
if not cmds.objExists(node_name):
continue
file_texture_name = cmds.getAttr(node_name + ".fileTextureName")
texture_folder_name, texture_file_name = os.path.split(file_texture_name)
texture_folder_name = folder_name
cmds.setAttr(
node_name + ".fileTextureName",
texture_folder_name + "/" + texture_file_name,
type="string",
)
def connect_attributes_to_shader(shader_attributes_mapping):
print("Connecting attributes to shader...")
try:
items = shader_attributes_mapping.iteritems()
except Exception as ex:
print(f"Error: {ex}")
items = shader_attributes_mapping.items()
for frm_attribute, shaderAttribute in items:
if cmds.objExists(frm_attribute) and cmds.objExists(shaderAttribute):
cmds.connectAttr(frm_attribute, shaderAttribute, force=True)
def create_lights(lights_file_path, orient):
print("Creating lights...")
cmds.file(lights_file_path, defaultNamespace=True, i=True)
cmds.xform("Lights", ro=orient)
cmds.makeIdentity("Lights", apply=True)
# Define all paths
dna_path = f"{DATA_DIR}/mh4/dna_files/Ada.dna"
gui_path = f"{DATA_DIR}/mh4/gui.ma"
aas_path = f"{DATA_DIR}/mh4/additional_assemble_script.py"
ac_path = f"{DATA_DIR}/analog_gui.ma"
light_scene = f"{DATA_DIR}/lights.ma"
shader_scene = f"{DATA_DIR}/shader.ma"
shaders_dir = f"{DATA_DIR}/shaders"
masks_dir = f"{DATA_DIR}/masks"
maps_dir = f"{DATA_DIR}/maps"
output_scene = f"{ROOT_DIR}/output/Ada_rig.mb"
try:
start_time = time.time()
# open new scene
cmds.file(new=True, force=True)
# import DNA data
dna = DNA(dna_path)
config = RigConfig(
gui_path=gui_path,
analog_gui_path=ac_path,
aas_path=aas_path,
)
build_rig(dna=dna, config=config)
import_head_shaders(shader_scene, shaders_dir, masks_dir, maps_dir)
create_lights(light_scene, ORIENT_Y)
# save scene
cmds.file(rename=output_scene)
cmds.file(save=True, type="mayaBinary")
print("--- Import finished in %s seconds ---" % (time.time() - start_time))
except Exception as ex:
print("Error building scene", ex)

View File

@ -1,300 +0,0 @@
"""
This example demonstrates generating functional rig in maya scene and exporting fbx per lod.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
- usage in command line:
python dna_viewer_export_fbx.py
mayapy dna_viewer_export_fbx.py
NOTE: Script cannot be called with Python, it must be called with mayapy.
- usage in Maya:
1. copy whole content of this file to Maya Script Editor
2. change value of ROOT_DIR to absolute path of dna_calibration, e.g. `c:/dna_calibration` in Windows or `/home/user/dna_calibration`. Important:
Use `/` (forward slash), because Maya uses forward slashes in path.
- customization:
- change CHARACTER_NAME to Taro, or the name of a custom DNA file placed in /data/dna_files. If you change name to Taro,
or some other Masculine character, you need to change BODY_FILE with value f"{BODY_DIR}/masc_skeleton.ma"
- change ADD_COLOR_VERTEX to True, if you want to import fbx in Unreal Engine with painted vertices for fallowing cases:
- vertex normals that are going to be updated during import in Unreal Engine, its vertices must be painted with green color.
- for potential future GeneSplicer usage, skinwights on vertices which will need update in character mixing process, must be painted with blue color.
- change UP_AXIS in order change up axis, it can be 'z' or 'y', if put any value is put, ValueError will be raised
Expected:
- script will generate fbx files Ada_lodX.mb where X are values from 0 to 7, in OUTPUT_DIR
- script will generate workspace.mel in OUTPUT_DIR
NOTE: If OUTPUT_DIR does not exist, it will be created.
"""
from os import makedirs
from os import path as ospath
from pathlib import Path
# if you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
OUTPUT_DIR = f"{ROOT_DIR}/output"
EXAMPLES_DIR = f"{ROOT_DIR}/examples"
ROOT_LIB_DIR = f"{ROOT_DIR}/lib"
DATA_DIR = f"{ROOT_DIR}/data"
# Setting constants that will be used
FACIAL_ROOT_NAME = "FACIAL_C_FacialRoot"
CHARACTER_NAME = "Ada"
ADD_COLOR_VERTEX = False
DNA_DIR = f"{DATA_DIR}/dna_files"
BODY_DIR = f"{DATA_DIR}/body"
CHARACTER_DNA = f"{DNA_DIR}/{CHARACTER_NAME}.dna"
UP_AXIS = "z"
if UP_AXIS not in ("z", "y"):
raise ValueError("UP_AXIS can be 'z' or 'y'")
BODY_FILE = f"{BODY_DIR}/fem_skeleton.ma"
ADD_MESH_NAME_TO_BLEND_SHAPE_CHANNEL_NAME = True
FACIAL_ROOT_JOINTS = ["FACIAL_C_FacialRoot", "FACIAL_C_Neck1Root", "FACIAL_C_Neck2Root"]
NECK_JOINTS = ["head", "neck_01", "neck_02"]
ROOT_JOINT = "spine_04"
from dna import (
BinaryStreamReader,
BinaryStreamWriter,
DataLayer_All,
FileStream,
Status,
)
from dnacalib import DNACalibDNAReader, RotateCommand
from maya import cmds, mel
from vtx_color import MESH_SHADER_MAPPING, VTX_COLOR_MESHES, VTX_COLOR_VALUES
from dna_viewer import (
DNA,
Config,
build_meshes,
get_skin_weights_from_scene,
set_skin_weights_to_scene,
)
def load_dna_reader():
stream = FileStream(
CHARACTER_DNA, FileStream.AccessMode_Read, FileStream.OpenMode_Binary
)
reader = BinaryStreamReader(stream, DataLayer_All)
reader.read()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error loading DNA: {status.message}")
return reader
def save_dna(reader):
stream = FileStream(
f"{CHARACTER_DNA}.rotate.dna",
FileStream.AccessMode_Write,
FileStream.OpenMode_Binary,
)
writer = BinaryStreamWriter(stream)
writer.setFrom(reader)
writer.write()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error saving DNA: {status.message}")
def prepare_rotated_dna():
reader = load_dna_reader()
# Copies DNA contents and will serve as input/output parameter to commands
calibrated = DNACalibDNAReader(reader)
# Modifies calibrated DNA in-place
rotate = RotateCommand([90.0, 0.0, 0.0], [0.0, 0.0, 0.0])
rotate.run(calibrated)
save_dna(calibrated)
return DNA(f"{CHARACTER_DNA}.rotate.dna")
def get_dna():
if UP_AXIS == "z":
return prepare_rotated_dna()
return DNA(CHARACTER_DNA)
def cleanup():
path = Path(f"{CHARACTER_DNA}.rotate.dna")
if path.exists():
path.unlink()
def build_meshes_for_lod(dna, lod):
# Create config
config = Config(
group_by_lod=False,
create_display_layers=False,
lod_filter=[lod],
add_mesh_name_to_blend_shape_channel_name=ADD_MESH_NAME_TO_BLEND_SHAPE_CHANNEL_NAME,
)
# Builds and returns the created mesh paths in the scene
return build_meshes(dna, config)
def create_skin_cluster(influences, mesh, skin_cluster_name, maximum_influences):
cmds.select(influences[0], replace=True)
cmds.select(mesh, add=True)
skinCluster = cmds.skinCluster(
toSelectedBones=True,
name=skin_cluster_name,
maximumInfluences=maximum_influences,
skinMethod=0,
obeyMaxInfluences=True,
)
if len(influences) > 1:
cmds.skinCluster(
skinCluster, edit=True, addInfluence=influences[1:], weight=0.0
)
return skinCluster
def create_head_and_body_scene(mesh_names):
scene_mesh_names = []
skinweights = []
for mesh_name in mesh_names:
if cmds.objExists(mesh_name):
scene_mesh_names.append(mesh_name)
skinweights.append(get_skin_weights_from_scene(mesh_name))
cmds.delete(f"{mesh_name}_skinCluster")
for facial_joint in FACIAL_ROOT_JOINTS:
cmds.parent(facial_joint, world=True)
cmds.delete(ROOT_JOINT)
cmds.file(BODY_FILE, options="v=0", type="mayaAscii", i=True)
if UP_AXIS == "y":
cmds.joint("root", edit=True, orientation=[-90.0, 0.0, 0.0])
for facial_joint, neck_joint in zip(FACIAL_ROOT_JOINTS, NECK_JOINTS):
cmds.parent(facial_joint, neck_joint)
for mesh_name, skinweight in zip(scene_mesh_names, skinweights):
create_skin_cluster(
skinweight.joints,
mesh_name,
f"{mesh_name}_skinCluster",
skinweight.no_of_influences,
)
set_skin_weights_to_scene(mesh_name, skinweight)
def set_fbx_options():
# Executes FBX relate commands from the imported plugin
min_time = cmds.playbackOptions(minTime=True, query=True)
max_time = cmds.playbackOptions(maxTime=True, query=True)
cmds.FBXResetExport()
mel.eval("FBXExportBakeComplexAnimation -v true")
mel.eval(f"FBXExportBakeComplexStart -v {min_time}")
mel.eval(f"FBXExportBakeComplexEnd -v {max_time}")
mel.eval("FBXExportConstraints -v true")
mel.eval("FBXExportSkeletonDefinitions -v true")
mel.eval("FBXExportInputConnections -v true")
mel.eval("FBXExportSmoothingGroups -v true")
mel.eval("FBXExportSkins -v true")
mel.eval("FBXExportShapes -v true")
mel.eval("FBXExportCameras -v false")
mel.eval("FBXExportLights -v false")
cmds.FBXExportUpAxis(UP_AXIS)
# Deselects objects in Maya
cmds.select(clear=True)
def create_shader(name):
cmds.shadingNode("blinn", asShader=True, name=name)
shading_group = str(
cmds.sets(
renderable=True,
noSurfaceShader=True,
empty=True,
name=f"{name}SG",
)
)
cmds.connectAttr(f"{name}.outColor", f"{shading_group}.surfaceShader")
return shading_group
def add_shader():
for shader_name, meshes in MESH_SHADER_MAPPING.items():
shading_group = create_shader(shader_name)
for mesh in meshes:
try:
cmds.select(mesh, replace=True)
cmds.sets(edit=True, forceElement=shading_group)
except Exception as e:
print(f"Skipped adding shader for mesh {mesh}. Reason {e}")
def set_vertex_color():
for m, meshName in enumerate(VTX_COLOR_MESHES):
try:
cmds.select(meshName)
except Exception as e:
print(f"Skipped adding vtx color for mesh {meshName}. Reason {e}")
continue
for v, rgb in enumerate(VTX_COLOR_VALUES[m]):
cmds.polyColorPerVertex(f"{meshName}.vtx[{v}]", g=rgb[1], b=rgb[2])
def export_fbx(lod, meshes):
# Selects every mesh in the given lod
for item in meshes:
cmds.select(item, add=True)
# Adds facial root joint to selection
cmds.select(FACIAL_ROOT_NAME, add=True)
# Sets the file path
export_file_name = f"{OUTPUT_DIR}/{CHARACTER_NAME}_lod{lod}.fbx"
# Exports the fbx
mel.eval(f'FBXExport -f "{export_file_name}" -s true')
def export_fbx_for_lod(dna, lod):
# Creates the meshes for the given lod
result = build_meshes_for_lod(dna, lod)
meshes = result.get_all_meshes()
# Executes FBX relate commands from the imported plugin
create_head_and_body_scene(meshes)
set_fbx_options()
# Saves the result
if ADD_COLOR_VERTEX:
add_shader()
set_vertex_color()
export_fbx(lod, meshes)
if __name__ == "__main__":
makedirs(OUTPUT_DIR, exist_ok=True)
# Loads the builtin plugin needed for FBX
cmds.loadPlugin("fbxmaya.mll")
# This fixes warning when calling this script with headless maya Warning: line 1: Unknown object type: HIKCharacterNode
mel.eval(f"HIKCharacterControlsTool;")
# Generate workspace.mel
mel.eval(f'setProject "{OUTPUT_DIR}";')
# Export FBX for each lod
cmds.upAxis(ax=UP_AXIS)
dna = get_dna()
for lod in range(dna.get_lod_count()):
export_fbx_for_lod(dna, lod)
cleanup()

View File

@ -1,247 +0,0 @@
"""
This example demonstrates how to propagate changes from maya scene to dna file.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
Follow the steps:
1. Start Maya
2. open maya scene (do 2.1 or 2.2)
2.1. Generate new scene using build_meshes or
2.2. start DNA Viewer GUI (dna_viewer_run_in_maya.py)
- Select DNA file that you want to load and generate scene for
- Select meshes that you want to change
- Tick joints in Build Options
- Click Process
- in Maya scene rig is going to be assembled
3. Run this script to the part called "load data"
a. get current vertex positions for all meshes
4. In the scene, make modifications to the neutral mesh and joints (important note:
if you're rotating joints, be sure to freeze transformations, so they're stored as orientations)
5. Run this script from the part called "propagate changes to dna" to the end
a. set new joints translations
b. set new joints rotations
c. move all meshes vertices to new positions
After performing this steps, your changes in maya scene will pe propagated to dna.
- usage in Maya:
1. copy whole content of this file to Maya Script Editor
2. change value of ROOT_DIR to absolute path of dna_calibration, e.g. `c:/dna_calibration` in Windows or `/home/user/dna_calibration`. Important:
Use `/` (forward slash), because Maya uses forward slashes in path.
- customization:
- change CHARACTER_NAME to Taro, or the name of a custom DNA file placed in /data/dna_files
Expected:
- script will generate dna file <CHARACTER_NAME>_modified.dna in OUTPUT_DIR, e.g. Ada_modified.dna
- script will generate maya scene <CHARACTER_NAME>_modified.mb in OUTPUT_DIR, e.g. Ada_modified.mb
- script will generate workspace.mel in OUTPUT_DIR
NOTE: If OUTPUT_DIR does not exist, it will be created.
"""
import maya.OpenMaya as om
from maya import cmds
from os import makedirs
from os import path as ospath
CHARACTER_NAME = "Ada"
# If you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
OUTPUT_DIR = f"{ROOT_DIR}/output"
ROOT_LIB_DIR = f"{ROOT_DIR}/lib"
DATA_DIR = f"{ROOT_DIR}/data"
DNA_DIR = f"{DATA_DIR}/dna_files"
CHARACTER_DNA = f"{DNA_DIR}/{CHARACTER_NAME}.dna"
ANALOG_GUI = f"{DATA_DIR}/analog_gui.ma"
GUI = f"{DATA_DIR}/gui.ma"
ADDITIONAL_ASSEMBLE_SCRIPT = f"{DATA_DIR}/additional_assemble_script.py"
ADD_MESH_NAME_TO_BLEND_SHAPE_CHANNEL_NAME = True
MODIFIED_CHARACTER_DNA = f"{OUTPUT_DIR}/{CHARACTER_NAME}_modified"
from dna import (
BinaryStreamReader,
BinaryStreamWriter,
DataLayer_All,
FileStream,
Status,
)
from dnacalib import (
CommandSequence,
DNACalibDNAReader,
SetNeutralJointRotationsCommand,
SetNeutralJointTranslationsCommand,
SetVertexPositionsCommand,
VectorOperation_Add,
)
from dna_viewer import DNA, RigConfig, build_rig, build_meshes
def load_dna_reader(path):
stream = FileStream(path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
reader = BinaryStreamReader(stream, DataLayer_All)
reader.read()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error loading DNA: {status.message}")
return reader
def save_dna(reader):
stream = FileStream(
f"{MODIFIED_CHARACTER_DNA}.dna",
FileStream.AccessMode_Write,
FileStream.OpenMode_Binary,
)
writer = BinaryStreamWriter(stream)
writer.setFrom(reader)
writer.write()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error saving DNA: {status.message}")
def get_mesh_vertex_positions_from_scene(meshName):
try:
sel = om.MSelectionList()
sel.add(meshName)
dag_path = om.MDagPath()
sel.getDagPath(0, dag_path)
mf_mesh = om.MFnMesh(dag_path)
positions = om.MPointArray()
mf_mesh.getPoints(positions, om.MSpace.kObject)
return [
[positions[i].x, positions[i].y, positions[i].z]
for i in range(positions.length())
]
except RuntimeError:
print(f"{meshName} is missing, skipping it")
return None
def run_joints_command(reader, calibrated):
# Making arrays for joints' transformations and their corresponding mapping arrays
joint_translations = []
joint_rotations = []
for i in range(reader.getJointCount()):
joint_name = reader.getJointName(i)
translation = cmds.xform(joint_name, query=True, translation=True)
joint_translations.append(translation)
rotation = cmds.joint(joint_name, query=True, orientation=True)
joint_rotations.append(rotation)
# This is step 5 sub-step a
set_new_joints_translations = SetNeutralJointTranslationsCommand(joint_translations)
# This is step 5 sub-step b
set_new_joints_rotations = SetNeutralJointRotationsCommand(joint_rotations)
# Abstraction to collect all commands into a sequence, and run them with only one invocation
commands = CommandSequence()
# Add vertex position deltas (NOT ABSOLUTE VALUES) onto existing vertex positions
commands.add(set_new_joints_translations)
commands.add(set_new_joints_rotations)
commands.run(calibrated)
# Verify that everything went fine
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error run_joints_command: {status.message}")
def run_vertices_command(
calibrated, old_vertices_positions, new_vertices_positions, mesh_index
):
# Making deltas between old vertices positions and new one
deltas = []
for new_vertex, old_vertex in zip(new_vertices_positions, old_vertices_positions):
delta = []
for new, old in zip(new_vertex, old_vertex):
delta.append(new - old)
deltas.append(delta)
# This is step 5 sub-step c
new_neutral_mesh = SetVertexPositionsCommand(
mesh_index, deltas, VectorOperation_Add
)
commands = CommandSequence()
# Add nex vertex position deltas (NOT ABSOLUTE VALUES) onto existing vertex positions
commands.add(new_neutral_mesh)
commands.run(calibrated)
# Verify that everything went fine
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error run_vertices_command: {status.message}")
def assemble_maya_scene():
dna = DNA(f"{MODIFIED_CHARACTER_DNA}.dna")
config = RigConfig(
gui_path=f"{DATA_DIR}/gui.ma",
analog_gui_path=f"{DATA_DIR}/analog_gui.ma",
aas_path=ADDITIONAL_ASSEMBLE_SCRIPT,
)
build_rig(dna=dna, config=config)
cmds.file(rename=f"{MODIFIED_CHARACTER_DNA}.mb")
cmds.file(save=True)
makedirs(OUTPUT_DIR, exist_ok=True)
dna = DNA(CHARACTER_DNA)
##################################
# This is step 2 sub-step 1
# use this block only if you need to assemble of scene from step 2.1
# config = RigConfig(
# gui_path=f"{DATA_DIR}/gui.ma",
# analog_gui_path=f"{DATA_DIR}/analog_gui.ma",
# aas_path=ADDITIONAL_ASSEMBLE_SCRIPT,
# )
# build_meshes(dna=dna, config=config)
# This is end of step 2 sub-step 1
##################################
# This is step 3 sub-step a
current_vertices_positions = {}
mesh_indices = []
for mesh_index, name in enumerate(dna.meshes.names):
current_vertices_positions[name] = {
"mesh_index": mesh_index,
"positions": get_mesh_vertex_positions_from_scene(name),
}
# Loaded data - end of 3rd step
##################################
##################################
# Modify rig in maya, 4th step
##################################
##################################
# Propagate changes to dna, 5th step
reader = load_dna_reader(CHARACTER_DNA)
calibrated = DNACalibDNAReader(reader)
run_joints_command(reader, calibrated)
for name, item in current_vertices_positions.items():
new_vertices_positions = get_mesh_vertex_positions_from_scene(name)
if new_vertices_positions:
run_vertices_command(
calibrated, item["positions"], new_vertices_positions, item["mesh_index"]
)
save_dna(calibrated)
assemble_maya_scene()

View File

@ -1,17 +0,0 @@
"""
This example demonstrates Maya UI Window for simple and non-programmatic creation the scene with the creating functional rig.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
- usage in command line:
NOTE: Script cannot be called with Python or mayapy, it must be called in Maya Script Editor.
- usage in Maya:
Expected: Maya will show UI.
"""
# This example is intended to be used in Maya
import dna_viewer
dna_viewer.show()

View File

@ -1,144 +0,0 @@
"""
This example demonstrates how to remove all blend shape data from a DNA.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
- usage in command line:
python dnacalib_clear_blend_shapes.py
mayapy dnacalib_clear_blend_shapes.py
- usage in Maya:
1. copy whole content of this file to Maya Script Editor
2. change value of ROOT_DIR to absolute path of dna_calibration, e.g. `c:/dna_calibration` in Windows or `/home/user/dna_calibration`. Important:
Use `/` (forward slash), because Maya uses forward slashes in path.
- customization:
- change CHARACTER_NAME to Taro, or the name of a custom DNA file placed in /data/dna_files
Expected: Script will generate Ada_output.dna in OUTPUT_DIR from original Ada.dna.
NOTE: If OUTPUT_DIR does not exist, it will be created.
"""
from os import makedirs
from os import path as ospath
# if you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
OUTPUT_DIR = f"{ROOT_DIR}/output"
CHARACTER_NAME = "Ada"
DATA_DIR = f"{ROOT_DIR}/data"
CHARACTER_DNA = f"{DATA_DIR}/dna_files/{CHARACTER_NAME}.dna"
OUTPUT_DNA = f"{OUTPUT_DIR}/{CHARACTER_NAME}_output.dna"
from dna import DataLayer_All, FileStream, Status, BinaryStreamReader, BinaryStreamWriter
from dnacalib import (
DNACalibDNAReader,
ClearBlendShapesCommand
)
def load_dna(path):
stream = FileStream(path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
reader = BinaryStreamReader(stream, DataLayer_All)
reader.read()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error loading DNA: {status.message}")
return reader
def save_dna(reader, path):
stream = FileStream(path, FileStream.AccessMode_Write, FileStream.OpenMode_Binary)
writer = BinaryStreamWriter(stream)
writer.setFrom(reader)
writer.write()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error saving DNA: {status.message}")
def validate_geometry(dna):
mesh_count = dna.getMeshCount()
for mesh_index in range(mesh_count):
bs_tgt_count = dna.getBlendShapeTargetCount(mesh_index)
for bs_tgt_index in range(bs_tgt_count):
bs_tgt_delta_count = dna.getBlendShapeTargetDeltaCount(mesh_index, bs_tgt_index)
if bs_tgt_delta_count != 0:
raise RuntimeError("Blend shape target deltas not removed properly!")
def validate_animation_data(dna):
bs_channel_lods = dna.getBlendShapeChannelLODs()
bs_channel_input_indices = dna.getBlendShapeChannelInputIndices()
bs_channel_output_indices = dna.getBlendShapeChannelOutputIndices()
if len(bs_channel_lods) != dna.getLODCount():
raise RuntimeError("Blend shape animation data not removed properly! Number of blend shape LODs does not match LOD count!")
for lod in bs_channel_lods:
if lod != 0:
raise RuntimeError("Blend shape animation data not removed properly!")
if (len(bs_channel_input_indices) != 0) or (len(bs_channel_output_indices) != 0):
raise RuntimeError("Blend shape animation data not removed properly!")
def calibrate_dna(input_path, output_path):
dna = load_dna(input_path)
# Copies DNA contents and will serve as input/output parameter to commands
calibrated = DNACalibDNAReader(dna)
mesh_count = calibrated.getMeshCount()
print(f"Number of meshes: {mesh_count}")
for mesh_index in range(mesh_count):
bs_tgt_count = calibrated.getBlendShapeTargetCount(mesh_index)
print(f"Number of blendshape targets for mesh {calibrated.getMeshName(mesh_index)}({mesh_index}): {bs_tgt_count}")
for bs_tgt_index in range(bs_tgt_count):
bs_tgt_delta_count = calibrated.getBlendShapeTargetDeltaCount(mesh_index, bs_tgt_index)
print(f"Number of blendshape target deltas for mesh {calibrated.getMeshName(mesh_index)}({mesh_index}), blend shape target {bs_tgt_index}: {bs_tgt_delta_count}")
print(f"Blend shape channel LODs: {calibrated.getBlendShapeChannelLODs()}")
print(f"Blend shape channel input indices: {calibrated.getBlendShapeChannelInputIndices()}")
print(f"Blend shape channel output indices: {calibrated.getBlendShapeChannelOutputIndices()}")
# Clears all blend shapes
command = ClearBlendShapesCommand()
print("\n\nClearing blend shape data...\n\n")
# Modifies calibrated DNA in-place
command.run(calibrated)
validate_geometry(calibrated)
validate_animation_data(calibrated)
print(f"Number of meshes: {mesh_count}")
for mesh_index in range(mesh_count):
bs_tgt_count = calibrated.getBlendShapeTargetCount(mesh_index)
print(f"Number of blendshape targets for mesh {calibrated.getMeshName(mesh_index)}({mesh_index}): {bs_tgt_count}")
for bs_tgt_index in range(bs_tgt_count):
bs_tgt_delta_count = calibrated.getBlendShapeTargetDeltaCount(mesh_index, bs_tgt_index)
print(f"Number of blendshape target deltas for mesh {calibrated.getMeshName(mesh_index)}({mesh_index}), blend shape target {bs_tgt_index}: {bs_tgt_delta_count}")
bs_channel_lods = dna.getBlendShapeChannelLODs()
bs_channel_input_indices = dna.getBlendShapeChannelInputIndices()
bs_channel_output_indices = dna.getBlendShapeChannelOutputIndices()
print(f"Blend shape channel LODs: {bs_channel_lods}")
print(f"Blend shape channel input indices: {bs_channel_input_indices}")
print(f"Blend shape channel output indices: {bs_channel_output_indices}")
print("\n\nSuccessfully cleared blend shape data.")
print("Saving DNA...")
save_dna(calibrated, output_path)
print("Done.")
if __name__ == "__main__":
makedirs(OUTPUT_DIR, exist_ok=True)
calibrate_dna(CHARACTER_DNA, OUTPUT_DNA)

View File

@ -1,154 +0,0 @@
"""
This example demonstrates a few DNACalib's commands.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
- usage in command line:
python dnacalib_demo.py
mayapy dnacalib_demo.py
- usage in Maya:
1. copy whole content of this file to Maya Script Editor
2. change value of ROOT_DIR to absolute path of dna_calibration, e.g. `c:/dna_calibration` in Windows or `/home/user/dna_calibration`. Important:
Use `/` (forward slash), because Maya uses forward slashes in path.
- customization:
- change CHARACTER_NAME to Taro, or the name of a custom DNA file placed in /data/dna_files
Expected: Script will generate Ada_output.dna in OUTPUT_DIR from original Ada.dna.
NOTE: If OUTPUT_DIR does not exist, it will be created.
"""
from os import makedirs
from os import path as ospath
# if you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
OUTPUT_DIR = f"{ROOT_DIR}/output"
CHARACTER_NAME = "Ada"
DATA_DIR = f"{ROOT_DIR}/data"
CHARACTER_DNA = f"{DATA_DIR}/dna_files/{CHARACTER_NAME}.dna"
OUTPUT_DNA = f"{OUTPUT_DIR}/{CHARACTER_NAME}_output.dna"
from dna import DataLayer_All, FileStream, Status, BinaryStreamReader, BinaryStreamWriter
from dnacalib import (
CommandSequence,
DNACalibDNAReader,
RenameJointCommand,
ScaleCommand,
SetBlendShapeTargetDeltasCommand,
SetVertexPositionsCommand,
VectorOperation_Add,
VectorOperation_Interpolate,
)
def load_dna(path):
stream = FileStream(path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
reader = BinaryStreamReader(stream, DataLayer_All)
reader.read()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error loading DNA: {status.message}")
return reader
def save_dna(reader, path):
stream = FileStream(path, FileStream.AccessMode_Write, FileStream.OpenMode_Binary)
writer = BinaryStreamWriter(stream)
writer.setFrom(reader)
writer.write()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error saving DNA: {status.message}")
def build_command_list():
# Abstraction to collect all commands into a sequence, and run them with only one invocation
commands = CommandSequence()
print("Creating a sequence of commands...")
# Instantiate command with parameters: scale-factor = 2 , origin-xyz = (0, 120, 0)
scale_by_two = ScaleCommand(2.0, [0.0, 120.0, 0.0])
# Alternatively a command can be instantiated empty, and populated with parameters later, e.g.:
# scale_by_two = ScaleCommand()
# scale_by_two.setScale(2.0)
# scale_by_two.setOrigin([0.0, 120.0, 0.0])
commands.add(scale_by_two)
print("Added command to scale dna")
# Rename by joint index (faster)
commands.add(RenameJointCommand(10, "NewJointA"))
# Rename by matching joint name (slower)
commands.add(RenameJointCommand("OldJointB", "NewJointB"))
print("Added command to rename joint")
# Interpolate blend shape target deltas between original DNA and below specified deltas
# ¯\_(ツ)_/¯
# Deltas in [[x, y, z], [x, y, z], [x, y, z]] format
blend_shape_target_deltas = [[0.0, 0.0, 2.0], [0.0, -1.0, 4.0], [3.0, -3.0, 8.0]]
vertex_indices = [0, 1, 2]
# Weights for interpolation between original deltas and above defined deltas
# 1.0 == take the new value completely, 0.0 means keep the old value
# Format: [Delta-0-Mask, Delta-1-Mask, Delta-2-Mask]
masks = [1.0, 0.0, 0.5]
set_blend_shapes_m0_b0 = SetBlendShapeTargetDeltasCommand(
0, # mesh index
0, # blend shape target index
blend_shape_target_deltas,
vertex_indices,
masks,
VectorOperation_Interpolate,
)
commands.add(set_blend_shapes_m0_b0)
print("Added command to change blend shape target deltas")
# Add vertex position deltas onto existing vertex positions
# Note the alternative data format, instead of using nested lists, separate all X, Y, Z
# components into distinct lists (might also be faster)
position_xs = [1.0, -4.5, 7.2]
position_ys = [2.0, -5.5, -8.3]
position_zs = [3.0, -6.5, 9.7]
# Weights to be multiplied with the above specified delta positions, before adding
# them onto the original data
# Format: [Delta-0-Weight, Delta-1-Weight, Delta-2-Weight]
masks = [1.0, 0.2, 0.4]
set_vertices_m0 = SetVertexPositionsCommand(
0, # mesh index
position_xs,
position_ys,
position_zs,
masks,
VectorOperation_Add,
)
commands.add(set_vertices_m0)
print("Added command to change vertex positions")
return commands
def calibrate_dna(input_path, output_path):
dna = load_dna(input_path)
# Copies DNA contents and will serve as input/output parameter to commands
calibrated = DNACalibDNAReader(dna)
commands = build_command_list()
print("Running command sequence...")
# Modifies calibrated DNA in-place
commands.run(calibrated)
print("Saving DNA...")
save_dna(calibrated, output_path)
print("Done.")
if __name__ == "__main__":
makedirs(OUTPUT_DIR, exist_ok=True)
calibrate_dna(CHARACTER_DNA, OUTPUT_DNA)

View File

@ -1,86 +0,0 @@
"""
This example demonstrates creation of a new DNA from an existing one by extracting specific lods.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
- usage in command line:
python dnacalib_lod_demo.py
mayapy dnacalib_lod_demo.py
- usage in Maya:
1. copy whole content of this file to Maya Script Editor
2. change value of ROOT_DIR to absolute path of dna_calibration, e.g. `c:/dna_calibration` in Windows or `/home/user/dna_calibration`. Important:
Use `/` (forward slash), because Maya uses forward slashes in path.
- customization:
- change CHARACTER_NAME to Taro, or the name of a custom DNA file placed in /data/dna_files
- change value of LODS to list of lods needed to be extracted
Expected: Script will generate Ada_with_lods_1_and_3.dna in OUTPUT_DIR, from original Ada.
NOTE: If OUTPUT_DIR does not exist, it will be created.
"""
from os import makedirs
from os import path as ospath
# if you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
OUTPUT_DIR = f"{ROOT_DIR}/output"
from dna import DataLayer_All, FileStream, Status, BinaryStreamReader, BinaryStreamWriter
from dnacalib import (
DNACalibDNAReader,
SetLODsCommand,
)
# Sets DNA file path
DNA = f"{ROOT_DIR}/data/dna_files/Ada.dna"
# Sets new DNA output file path
DNA_NEW = f"{OUTPUT_DIR}/Ada_with_lods_1_and_3.dna"
# Sets lods to extract
LODS = [1, 3]
def save_dna(reader: DNACalibDNAReader, created_dna_path: str):
# Saves the dna
stream = FileStream(created_dna_path, FileStream.AccessMode_Write, FileStream.OpenMode_Binary)
writer = BinaryStreamWriter(stream)
writer.setFrom(reader)
writer.write()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error saving DNA: {status.message}")
def run_SetLODsCommand(reader):
calibrated = DNACalibDNAReader(reader)
command = SetLODsCommand()
# Set a list of LODs that will be exported to the new file
command.setLODs(LODS)
# Runs the command that reduces LODs of the DNA
command.run(calibrated)
print("Setting new LODs...")
if calibrated.getLODCount() != 2:
raise RuntimeError("Setting new number of LODs in DNA was unsuccessful!")
print("\nSuccessfully changed number of LODs in ")
print("Saving ..")
# Save the newly created DNA
save_dna(calibrated, DNA_NEW)
print("Done.")
def load_dna_calib(dna_path: str):
# Load the DNA
stream = FileStream(dna_path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
reader = BinaryStreamReader(stream, DataLayer_All)
reader.read()
return reader
if __name__ == "__main__":
makedirs(OUTPUT_DIR, exist_ok=True)
reader = load_dna_calib(DNA)
run_SetLODsCommand(reader)

View File

@ -1,139 +0,0 @@
"""
This example demonstrates how to subtract values from a neutral mesh and transfer those changes to its lower LOD meshes.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
- usage in command line:
python dnacalib_neutral_mesh_subtract.py
mayapy dnacalib_neutral_mesh_subtract.py
- usage in Maya:
1. copy whole content of this file to Maya Script Editor
2. change value of ROOT_DIR to absolute path of dna_calibration, e.g. `c:/dna_calibration` in Windows or `/home/user/dna_calibration`. Important:
Use `/` (forward slash), because Maya uses forward slashes in path.
- customization:
- change CHARACTER_NAME to Taro, or the name of a custom DNA file placed in /data/dna_files
Expected: Script will generate Ada_output.dna in OUTPUT_DIR from original Ada.dna.
NOTE: If OUTPUT_DIR does not exist, it will be created.
"""
from os import makedirs
from os import path as ospath
# if you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
OUTPUT_DIR = f"{ROOT_DIR}/output"
CHARACTER_NAME = "Ada"
DATA_DIR = f"{ROOT_DIR}/data"
CHARACTER_DNA = f"{DATA_DIR}/dna_files/{CHARACTER_NAME}.dna"
OUTPUT_DNA = f"{OUTPUT_DIR}/{CHARACTER_NAME}_output.dna"
from dna import DataLayer_All, FileStream, Status, BinaryStreamReader, BinaryStreamWriter
from dnacalib import (
DNACalibDNAReader,
SetVertexPositionsCommand,
VectorOperation_Subtract,
CalculateMeshLowerLODsCommand
)
from math import isclose
def load_dna(path):
stream = FileStream(path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
reader = BinaryStreamReader(stream, DataLayer_All)
reader.read()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error loading DNA: {status.message}")
return reader
def save_dna(reader, path):
stream = FileStream(path, FileStream.AccessMode_Write, FileStream.OpenMode_Binary)
writer = BinaryStreamWriter(stream)
writer.setFrom(reader)
writer.write()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error saving DNA: {status.message}")
def calibrate_dna(input_path, output_path):
dna = load_dna(input_path)
# Copies DNA contents and will serve as input/output parameter to commands
calibrated = DNACalibDNAReader(dna)
if calibrated.getMeshCount() == 0:
print("No meshes found in DNA.")
return
mesh_index = 0
vtx_count_mesh0 = calibrated.getVertexPositionCount(mesh_index)
xs = calibrated.getVertexPositionXs(mesh_index)
ys = calibrated.getVertexPositionYs(mesh_index)
zs = calibrated.getVertexPositionZs(mesh_index)
# Example values to subtract from original vertex positions for mesh with index 0
ones = [1.0] * vtx_count_mesh0
# Command used to subtract example values from a specified neutral mesh
subtract_command = SetVertexPositionsCommand()
subtract_command.setMeshIndex(mesh_index)
subtract_command.setOperation(VectorOperation_Subtract)
subtract_command.setPositions(ones, ones, ones)
# Alternatively, if you wanted to do the opposite, to subtract neutral mesh values
# from example values, you could do the following:
# xs_2 = [x * 2 for x in xs]
# ys_2 = [y * 2 for y in ys]
# zs_2 = [z * 2 for z in zs]
# subtract_command.setPositions(xs_2, ys_2, zs_2)
# # After running the command, the calibrated DNA will contain -xs, -ys, -zs
# subtract_command.run(calibrated)
#
# # Add the example values, which will result in:
# # ones - xs
# # ones - ys
# # ones - zs
# add_command = SetVertexPositionsCommand()
# add_command.setMeshIndex(mesh_index)
# add_command.setOperation(VectorOperation_Add) # remember to add VectorOperation_Add to the list of imports
# add_command.setPositions(ones, ones, ones)
# add_command.run(calibrated)
print(f"Subtracting values from neutral mesh \'{calibrated.getMeshName(mesh_index)}\'...")
# Modifies calibrated DNA in-place
subtract_command.run(calibrated)
# Command used to recalculate vertex positions of lower LOD neutral meshes of the specified neutral mesh
calculate_lower_lods_command = CalculateMeshLowerLODsCommand()
calculate_lower_lods_command.setMeshIndex(mesh_index)
print("Recalculating values for lower LOD meshes...")
# Modifies calibrated DNA in-place
calculate_lower_lods_command.run(calibrated)
new_xs = calibrated.getVertexPositionXs(mesh_index)
new_ys = calibrated.getVertexPositionYs(mesh_index)
new_zs = calibrated.getVertexPositionZs(mesh_index)
for i in range(vtx_count_mesh0):
if (not isclose(xs[i] - 1, new_xs[i], rel_tol=1e-7) or
not isclose(ys[i] - 1, new_ys[i], rel_tol=1e-7) or
not isclose(zs[i] - 1, new_zs[i], rel_tol=1e-7)):
raise RuntimeError("Vertex positions were not changed successfully!")
print("\nSuccessfully changed vertex positions.")
print("Saving DNA...")
save_dna(calibrated, output_path)
print("Done.")
if __name__ == "__main__":
makedirs(OUTPUT_DIR, exist_ok=True)
calibrate_dna(CHARACTER_DNA, OUTPUT_DNA)

View File

@ -1,102 +0,0 @@
"""
This example demonstrates a few DNACalib's commands.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
- usage in command line:
python dnacalib_remove_joint.py
mayapy dnacalib_remove_joint.py
- usage in Maya:
1. copy whole content of this file to Maya Script Editor
2. change value of ROOT_DIR to absolute path of dna_calibration, e.g. `c:/dna_calibration` in Windows or `/home/user/dna_calibration`. Important:
Use `/` (forward slash), because Maya uses forward slashes in path.
- customization:
- change CHARACTER_NAME to Taro, or the name of a custom DNA file placed in /data/dna_files
Expected: Script will generate Ada_output.dna in OUTPUT_DIR from original Ada.dna.
NOTE: If OUTPUT_DIR does not exist, it will be created.
"""
from os import makedirs
from os import path as ospath
# if you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
OUTPUT_DIR = f"{ROOT_DIR}/output"
CHARACTER_NAME = "Ada"
DATA_DIR = f"{ROOT_DIR}/data"
CHARACTER_DNA = f"{DATA_DIR}/dna_files/{CHARACTER_NAME}.dna"
OUTPUT_DNA = f"{OUTPUT_DIR}/{CHARACTER_NAME}_output.dna"
from dna import DataLayer_All, FileStream, Status, BinaryStreamReader, BinaryStreamWriter
from dnacalib import (
DNACalibDNAReader,
RemoveJointCommand,
)
def load_dna(path):
stream = FileStream(path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
reader = BinaryStreamReader(stream, DataLayer_All)
reader.read()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error loading DNA: {status.message}")
return reader
def save_dna(reader, path):
stream = FileStream(path, FileStream.AccessMode_Write, FileStream.OpenMode_Binary)
writer = BinaryStreamWriter(stream)
writer.setFrom(reader)
writer.write()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error saving DNA: {status.message}")
def get_joints(dna):
joints = []
for jointIndex in range(dna.getJointCount()):
joints.append(dna.getJointName(jointIndex))
return joints
def calibrate_dna(input_path, output_path):
dna = load_dna(input_path)
# Copies DNA contents and will serve as input/output parameter to command
calibrated = DNACalibDNAReader(dna)
original_joints = get_joints(calibrated)
# An example joint to remove
joint_index = 314
joint_name = calibrated.getJointName(joint_index)
# Removes joint with specified index
command = RemoveJointCommand(joint_index)
# Modifies calibrated DNA in-place
command.run(calibrated)
modified_joints = get_joints(calibrated)
if (len(modified_joints) != (len(original_joints) - 1)) or (joint_name in modified_joints):
raise RuntimeError("Joint not removed properly!")
print(f"Successfully removed joint `{joint_name}`.")
print("Saving DNA...")
save_dna(calibrated, output_path)
print("Done.")
if __name__ == "__main__":
makedirs(OUTPUT_DIR, exist_ok=True)
calibrate_dna(CHARACTER_DNA, OUTPUT_DNA)

View File

@ -1,74 +0,0 @@
"""
This example demonstrates rename of a joint.
IMPORTANT: You have to setup the environment before running this example. Please refer to the 'Environment setup' section in README.md.
- usage in command line:
python rename_joint_demo.py
mayapy rename_joint_demo.py
Expected: Script will generate Ada_output.dna in OUTPUT_DIR.
- usage in Maya:
1. copy whole content of this file to Maya Script Editor
2. change value of ROOT_DIR to absolute path of dna_calibration, e.g. `c:/dna_calibration` in Windows or `/home/user/dna_calibration`. Important:
Use `/` (forward slash), because Maya uses forward slashes in path.
- customization:
- change CHARACTER_NAME to Taro, or the name of a custom DNA file placed in /data/dna_files
Expected: Script will generate Ada_output.dna in OUTPUT_DIR from original Ada.dna.
NOTE: If OUTPUT_DIR does not exist, it will be created.
"""
from os import makedirs
from os import path as ospath
# if you use Maya, use absolute path
ROOT_DIR = f"{ospath.dirname(ospath.abspath(__file__))}/..".replace("\\", "/")
OUTPUT_DIR = f"{ROOT_DIR}/output"
CHARACTER_NAME = "Ada"
DATA_DIR = f"{ROOT_DIR}/data"
CHARACTER_DNA = f"{DATA_DIR}/dna_files/{CHARACTER_NAME}.dna"
OUTPUT_DNA = f"{OUTPUT_DIR}/{CHARACTER_NAME}_output.dna"
from dna import DataLayer_All, FileStream, Status, BinaryStreamReader, BinaryStreamWriter
from dnacalib import DNACalibDNAReader, RenameJointCommand
def load_dna(path):
stream = FileStream(path, FileStream.AccessMode_Read, FileStream.OpenMode_Binary)
reader = BinaryStreamReader(stream, DataLayer_All)
reader.read()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error loading DNA: {status.message}")
return reader
def save_dna(reader, path):
stream = FileStream(path, FileStream.AccessMode_Write, FileStream.OpenMode_Binary)
writer = BinaryStreamWriter(stream)
writer.setFrom(reader)
writer.write()
if not Status.isOk():
status = Status.get()
raise RuntimeError(f"Error saving DNA: {status.message}")
if __name__ == "__main__":
makedirs(OUTPUT_DIR, exist_ok=True)
dna_reader = load_dna(CHARACTER_DNA)
calibrated = DNACalibDNAReader(dna_reader)
# Prints current joint name
print(calibrated.getJointName(10))
# Creates rename command
rename = RenameJointCommand(10, "NewJointA")
# Executes command
rename.run(calibrated)
# Prints the new joint name
print(calibrated.getJointName(10))
save_dna(calibrated, OUTPUT_DNA)
print('Done.')