Module kawa_scripts.combiner
Tool for combining few objects into single one.
See BaseMeshCombiner
.
Expand source code
# Kawashirov's Scripts (c) 2021 by Sergey V. Kawashirov
#
# Kawashirov's Scripts is licensed under a
# Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
#
# You should have received a copy of the license along with this
# work. If not, see <http://creativecommons.org/licenses/by-nc-sa/3.0/>.
#
#
"""
Tool for combining few objects into single one.
See `kawa_scripts.combiner.BaseMeshCombiner`.
"""
from collections import deque as _deque
import bpy as _bpy
from bpy import data as _D
from bpy import context as _C
from . import commons as _commons
from .reporter import LambdaReporter as _LambdaReporter
from ._internals import log as _log
import typing as _typing
if _typing.TYPE_CHECKING:
from typing import *
from bpy.types import *
class BaseMeshCombiner:
"""
Base class for Combining Meshes.
You must extend this class with required and necessary methods for your case,
configure variables and then run `combine_meshes`.
This tool creates/destroys bpy-Objects intensive, so unique `str` ID names is used for Object-references.
You must not rename objects while combiner is running.
You also should be careful with references to related Objects, as it most likely will become invalid.
How does this work:
- You should put an "root" Object ID name into `roots_names`.
There may be several root objects, it will be combined independently in single run.
- Combiner will try to join all children Objects of given root Object into given root Object itself.
You can not combine non-children Objects.
It is assumed that parts of the complex hierarchy will be flattened to a pair of combined objects.
- You can provide a group for given object (See `group_child`). Objects in same group will be joined together.
Group is a string, but can be `None` for default group or `False` if object must not be combined and should kept as is.
..
This can be used to join all Opaque objects into one Object, and all Transparent objects into different Object.
For example, I also use this to keep Meshes for rendering and Meshes for collisions separately for Unity.
- Root Object is not required to be a Mesh-Object. This can be any type of object.
If `force_mesh_root`, then given non-Mesh root Object will be replaced with Mesh Object.
This can be used to avoid unnecessary empty Objects.
- If root object is a Mesh-Object, children Objects of default group will be joined to the root Object.
- After combining names of newly created objects put into `created_objects`.
For example, you can use this to switch back to Object-references after running this tool.
Here example hierarchy of raw object before combining:
![combiner_example_before.png](https://i.imgur.com/hA9Od1G.png)
Here same hierarchy of final objects after combining:
![combiner_example_after.png](https://i.imgur.com/7QcrBDT.png)
As you can see addition groups `ClldrDynamic`, `ClldrProp` and `NoCast` where used.
"""
def __init__(self):
self.roots_names = set() # type: Set[str]
self.report_time = 5
# Если указано, ограничивает работу скрипта только этой сценой.
# Иначе, рассматривается активная сцена.
self.scene = None # type: Optional[Scene]
# Объекты, которые были замешены во время работы этого скрипта.
self.created_objects = set() # type: Set[str]
self.replaced_objects = set() # type: Set[str]
# Если root-объект - не меш-объект, то следует ли пересоздать его?
# - False - в иерархии root-объекта создается новый меш-объект, для None-группы.
# Объект будет иметь суффикс .default_group.
# - True - root-объект будет заменён на новый меш-объект, для None-группы.
# Пара объектов (заменённый, заменивший) будет сохранена в .recreated
self.force_mesh_root = False # type: bool
self.default_group = 'Default' # type: str
def before_group(self, root: 'str', children: 'Set[str]'):
# Функция, вызываемая перед группировкой детей.
pass
def group_child(self, root: 'str', child: 'str') -> 'Union[None, str, bool]':
# Функция, которая говорит, как объединять меши.
# Аргументы:
# - объект из root, в который предлагается подсоединить меш
# - предлагаемый дочерний меш-объект, который предлагается подсоединить к первому
# Функция должна вернуть одно из:
# - False если предлагаемый объект объединять не нужно
# - строку с именем группы объединения
# - None, что бы объединить в группу по-молчанию / без группы
# Если root-объект - меш-объект, то объекты с группой None вольются в него.
# Если root-объект - не меш-объект, то поведение зависит от .force_mesh_root
return None
def before_join(self, root: 'str', join_to: 'str', group_name: 'Optional[str]', group_objs: 'Set[str]'):
# Функция, вызываемая после создания нового меш-объекта, к которому будет присоединение.
# Аргументы:
# - root - исходный объект из root
# - join_to - куда будет происходить присоединения.
# - group_name - группа, т.е. то, что вернул .selector
# - group_objs - объекты (которые выбраны .selector-ом) для присоеднинения
# Может быть root is join_to
# Ничего возвращать не нужно.
# Можно использовать, например, для применения/переноса какх-то свойств объекта
# или для правки UV для корректного сведения.
pass
def after_join(self, root: 'str', join_to: 'str', group_name: 'Optional[str]'):
# Функция, вызываемая после присоединения.
# Аргументы:
# - root - исходный объект из root
# - join_to - куда было выполнено присоединение.
# - group_name - группа, т.е. то, что вернул .selector
# Может быть root is join_to
# Ничего возвращать не нужно.
# Можно использовать, например, для починки lightmap UV.
pass
def _check_roots(self):
scene = self.scene or _C.scene
wrong = list()
for root_name in self.roots_names:
if root_name not in scene.collection.objects:
wrong.append(root_name)
if len(wrong) > 0:
wrongstr = ', '.join('"' + r + '"' for r in wrong)
msg = 'There is {0} root-objects not from scene "{1}": {2}.'.format(len(wrong), scene.name, wrongstr)
_log.error(msg)
raise RuntimeError(msg, wrong)
# Проверяем, что бы объекты в .roots были независимы друг от друга,
# а именно не были детьми/родителями друг друга.
# TODO Довольно много итераций, можно ли это как-то ускорить?
related = list()
for obja_n in self.roots_names:
obja = _D.objects[obja_n]
for objb_n in self.roots_names:
objb = _D.objects[objb_n]
if obja is objb:
continue
objc = obja
while objc is not None:
if objc is objb:
related.append((obja_n, objb_n))
break
objc = objc.parent
if len(related) > 0:
pairs = ', '.join('"{0}" is child of "{1}"'.format(a, b) for a, b in related)
msg = 'There is {0} objects pairs have child-parent relations between each other: {1}.'\
.format(len(related), pairs)
_log.error(msg)
raise RuntimeError(msg, related)
def _call_before_group(self, root_name: 'str', child_name: 'Set[str]'):
try:
self.before_group(root_name, child_name)
except Exception as exc:
raise RuntimeError(root_name, child_name) from exc
def _call_group_child(self, root_name: 'str', child_name: 'str') -> 'Union[False, None, str]':
group_name = None
try:
group_name = self.group_child(root_name, child_name)
if not isinstance(group_name, (type(None), str)) and group_name is not False:
msg = 'Group should be None, False or str, got ({0}) "{1}" from .group_child'.format(type(group_name), str(group_name))
_log.error(msg)
raise RuntimeError(msg, group_name)
return group_name
except Exception as exc:
msg = 'Can not group object "{0}" in object "{1}" ({2})'.format(child_name, root_name, repr(group_name))
_log.error(msg)
raise RuntimeError(msg, root_name, child_name, group_name) from exc
def _join_objects(self, target: 'Object', children: 'Iterable[str]'):
# log.info("Joining: %s <- %s", target.name, children)
_commons.ensure_deselect_all_objects()
for child_name in children:
obj = _D.objects[child_name]
obj.hide_set(False)
obj.select_set(True)
_commons.move_children_to_grandparent(obj)
_commons.activate_object(target)
_commons.ensure_op_finished(_bpy.ops.object.join(), name='bpy.ops.object.join')
_commons.ensure_deselect_all_objects()
# log.info("Joined: %s <- %s", target, children)
pass
def _call_before_join(self, root: 'str', join_to: 'str', group_name: 'Optional[str]', group_objs: 'Set[str]'):
try:
self.before_join(root, join_to, group_name, group_objs)
except Exception as exc:
msg = 'Error before_join: root={0}, join_to={1}, group_name={2}, group_objs={3}'.format(root, join_to, group_name, group_objs)
raise RuntimeError(msg, root, join_to, group_name, group_objs) from exc
def _call_after_join(self, root: 'str', join_to: 'str', group_name: 'Optional[str]'):
try:
self.after_join(root, join_to, group_name)
except Exception as exc:
msg = 'Error after_join: root={0}, join_to={1}, group_name={2}'.format(root, join_to, group_name)
raise RuntimeError(msg, root, join_to, group_name) from exc
def _process_root(self, root_name: 'str') -> 'int':
# Поиск меш-объектов-детей root на этой же сцене.
scene_objs = (self.scene or _C.scene).collection.objects
children_queue = _deque() # type: Deque[str]
children = set() # type: Set[str]
children_queue.extend(x.name for x in _D.objects[root_name].children)
while len(children_queue) > 0:
child_name = children_queue.pop()
child = _D.objects[child_name]
children_queue.extend(x.name for x in child.children)
if child_name not in scene_objs:
continue
if not isinstance(child.data, _bpy.types.Mesh):
continue
children.add(child_name)
self._call_before_group(root_name, children)
groups = dict() # type: Dict[Optional[str], Set[str]]
for child_name in children:
group_name = self._call_group_child(root_name, child_name)
if group_name is False:
continue # skip
group = groups.get(group_name)
if group is None:
group = set()
groups[group_name] = group
group.add(child_name)
# log.info('%s %s', root_name, repr(groups))
def create_mesh_obj(name):
new_mesh = _D.meshes.new(name + '-Mesh')
new_obj = _D.objects.new(name, object_data=new_mesh)
new_obj.name = name # force rename
scene_objs.link(new_obj)
return new_obj
# Далее намерено избегаем ссылок на root объект, т.к. объекты меняются
# и можно отхватить ошибку StructRNA of type Object has been removed
obj_group_count = 0
for group_name, obj_group in groups.items():
join_to = None
if group_name is None:
if isinstance(_D.objects[root_name].data, _bpy.types.Mesh):
# root - Это меш, приклееваем к нему.
join_to = _D.objects[root_name]
elif self.force_mesh_root:
# root - Это НЕ меш, но force_mesh_root.
base_name = root_name
old_root = _D.objects[root_name]
old_root.name = base_name + '-Replaced'
self.replaced_objects.add(old_root.name)
join_to = create_mesh_obj(base_name)
root_name = join_to.name # Фактическое новое имя
self.created_objects.add(root_name)
join_to.parent = old_root.parent
join_to.parent_type = 'OBJECT'
join_to.location = old_root.location
join_to.rotation_mode = old_root.rotation_mode
join_to.rotation_axis_angle = old_root.rotation_axis_angle
join_to.rotation_euler = old_root.rotation_euler
join_to.rotation_quaternion = old_root.rotation_quaternion
join_to.scale = old_root.scale
for sub_child in old_root.children: # type: Object
sub_child.parent = join_to
else:
# root - Это НЕ меш, создаём подгруппу.
join_to = create_mesh_obj(root_name + '-' + self.default_group)
self.created_objects.add(join_to.name)
join_to.parent = _D.objects[root_name]
join_to.parent_type = 'OBJECT'
_commons.identity_transform(join_to)
else:
join_to = create_mesh_obj(root_name + '-' + group_name)
self.created_objects.add(join_to.name)
join_to.parent = _D.objects[root_name]
join_to.parent_type = 'OBJECT'
_commons.identity_transform(join_to)
self._call_before_join(root_name, join_to.name, group_name, obj_group)
self._join_objects(join_to, obj_group)
self._call_after_join(root_name, join_to.name, group_name)
obj_group_count += len(obj_group)
return obj_group_count
def combine_meshes(self):
self._check_roots()
obj_n, obj_i, joins = len(self.roots_names), 0, 0
reporter = _LambdaReporter(self.report_time)
reporter.func = lambda r, t: _log.info(
"Joining meshes: Roots={0}/{1}, Joined={2}, Time={3:.1f} sec, ETA={4:.1f} sec...".format(
obj_i, obj_n, joins, t, r.get_eta(1.0 * obj_i / obj_n)))
for root_name in self.roots_names:
joins += self._process_root(root_name)
obj_i += 1
reporter.ask_report(False)
reporter.ask_report(True)
Classes
class BaseMeshCombiner
-
Base class for Combining Meshes. You must extend this class with required and necessary methods for your case, configure variables and then run
combine_meshes
.This tool creates/destroys bpy-Objects intensive, so unique
str
ID names is used for Object-references. You must not rename objects while combiner is running. You also should be careful with references to related Objects, as it most likely will become invalid.How does this work:
-
You should put an "root" Object ID name into
roots_names
. There may be several root objects, it will be combined independently in single run. -
Combiner will try to join all children Objects of given root Object into given root Object itself. You can not combine non-children Objects. It is assumed that parts of the complex hierarchy will be flattened to a pair of combined objects.
-
You can provide a group for given object (See
group_child
). Objects in same group will be joined together. Group is a string, but can beNone
for default group orFalse
if object must not be combined and should kept as is. .. This can be used to join all Opaque objects into one Object, and all Transparent objects into different Object. For example, I also use this to keep Meshes for rendering and Meshes for collisions separately for Unity. -
Root Object is not required to be a Mesh-Object. This can be any type of object. If
force_mesh_root
, then given non-Mesh root Object will be replaced with Mesh Object. This can be used to avoid unnecessary empty Objects. -
If root object is a Mesh-Object, children Objects of default group will be joined to the root Object.
-
After combining names of newly created objects put into
created_objects
. For example, you can use this to switch back to Object-references after running this tool.
Here example hierarchy of raw object before combining:
Here same hierarchy of final objects after combining:
As you can see addition groups
ClldrDynamic
,ClldrProp
andNoCast
where used.Expand source code
class BaseMeshCombiner: """ Base class for Combining Meshes. You must extend this class with required and necessary methods for your case, configure variables and then run `combine_meshes`. This tool creates/destroys bpy-Objects intensive, so unique `str` ID names is used for Object-references. You must not rename objects while combiner is running. You also should be careful with references to related Objects, as it most likely will become invalid. How does this work: - You should put an "root" Object ID name into `roots_names`. There may be several root objects, it will be combined independently in single run. - Combiner will try to join all children Objects of given root Object into given root Object itself. You can not combine non-children Objects. It is assumed that parts of the complex hierarchy will be flattened to a pair of combined objects. - You can provide a group for given object (See `group_child`). Objects in same group will be joined together. Group is a string, but can be `None` for default group or `False` if object must not be combined and should kept as is. .. This can be used to join all Opaque objects into one Object, and all Transparent objects into different Object. For example, I also use this to keep Meshes for rendering and Meshes for collisions separately for Unity. - Root Object is not required to be a Mesh-Object. This can be any type of object. If `force_mesh_root`, then given non-Mesh root Object will be replaced with Mesh Object. This can be used to avoid unnecessary empty Objects. - If root object is a Mesh-Object, children Objects of default group will be joined to the root Object. - After combining names of newly created objects put into `created_objects`. For example, you can use this to switch back to Object-references after running this tool. Here example hierarchy of raw object before combining: ![combiner_example_before.png](https://i.imgur.com/hA9Od1G.png) Here same hierarchy of final objects after combining: ![combiner_example_after.png](https://i.imgur.com/7QcrBDT.png) As you can see addition groups `ClldrDynamic`, `ClldrProp` and `NoCast` where used. """ def __init__(self): self.roots_names = set() # type: Set[str] self.report_time = 5 # Если указано, ограничивает работу скрипта только этой сценой. # Иначе, рассматривается активная сцена. self.scene = None # type: Optional[Scene] # Объекты, которые были замешены во время работы этого скрипта. self.created_objects = set() # type: Set[str] self.replaced_objects = set() # type: Set[str] # Если root-объект - не меш-объект, то следует ли пересоздать его? # - False - в иерархии root-объекта создается новый меш-объект, для None-группы. # Объект будет иметь суффикс .default_group. # - True - root-объект будет заменён на новый меш-объект, для None-группы. # Пара объектов (заменённый, заменивший) будет сохранена в .recreated self.force_mesh_root = False # type: bool self.default_group = 'Default' # type: str def before_group(self, root: 'str', children: 'Set[str]'): # Функция, вызываемая перед группировкой детей. pass def group_child(self, root: 'str', child: 'str') -> 'Union[None, str, bool]': # Функция, которая говорит, как объединять меши. # Аргументы: # - объект из root, в который предлагается подсоединить меш # - предлагаемый дочерний меш-объект, который предлагается подсоединить к первому # Функция должна вернуть одно из: # - False если предлагаемый объект объединять не нужно # - строку с именем группы объединения # - None, что бы объединить в группу по-молчанию / без группы # Если root-объект - меш-объект, то объекты с группой None вольются в него. # Если root-объект - не меш-объект, то поведение зависит от .force_mesh_root return None def before_join(self, root: 'str', join_to: 'str', group_name: 'Optional[str]', group_objs: 'Set[str]'): # Функция, вызываемая после создания нового меш-объекта, к которому будет присоединение. # Аргументы: # - root - исходный объект из root # - join_to - куда будет происходить присоединения. # - group_name - группа, т.е. то, что вернул .selector # - group_objs - объекты (которые выбраны .selector-ом) для присоеднинения # Может быть root is join_to # Ничего возвращать не нужно. # Можно использовать, например, для применения/переноса какх-то свойств объекта # или для правки UV для корректного сведения. pass def after_join(self, root: 'str', join_to: 'str', group_name: 'Optional[str]'): # Функция, вызываемая после присоединения. # Аргументы: # - root - исходный объект из root # - join_to - куда было выполнено присоединение. # - group_name - группа, т.е. то, что вернул .selector # Может быть root is join_to # Ничего возвращать не нужно. # Можно использовать, например, для починки lightmap UV. pass def _check_roots(self): scene = self.scene or _C.scene wrong = list() for root_name in self.roots_names: if root_name not in scene.collection.objects: wrong.append(root_name) if len(wrong) > 0: wrongstr = ', '.join('"' + r + '"' for r in wrong) msg = 'There is {0} root-objects not from scene "{1}": {2}.'.format(len(wrong), scene.name, wrongstr) _log.error(msg) raise RuntimeError(msg, wrong) # Проверяем, что бы объекты в .roots были независимы друг от друга, # а именно не были детьми/родителями друг друга. # TODO Довольно много итераций, можно ли это как-то ускорить? related = list() for obja_n in self.roots_names: obja = _D.objects[obja_n] for objb_n in self.roots_names: objb = _D.objects[objb_n] if obja is objb: continue objc = obja while objc is not None: if objc is objb: related.append((obja_n, objb_n)) break objc = objc.parent if len(related) > 0: pairs = ', '.join('"{0}" is child of "{1}"'.format(a, b) for a, b in related) msg = 'There is {0} objects pairs have child-parent relations between each other: {1}.'\ .format(len(related), pairs) _log.error(msg) raise RuntimeError(msg, related) def _call_before_group(self, root_name: 'str', child_name: 'Set[str]'): try: self.before_group(root_name, child_name) except Exception as exc: raise RuntimeError(root_name, child_name) from exc def _call_group_child(self, root_name: 'str', child_name: 'str') -> 'Union[False, None, str]': group_name = None try: group_name = self.group_child(root_name, child_name) if not isinstance(group_name, (type(None), str)) and group_name is not False: msg = 'Group should be None, False or str, got ({0}) "{1}" from .group_child'.format(type(group_name), str(group_name)) _log.error(msg) raise RuntimeError(msg, group_name) return group_name except Exception as exc: msg = 'Can not group object "{0}" in object "{1}" ({2})'.format(child_name, root_name, repr(group_name)) _log.error(msg) raise RuntimeError(msg, root_name, child_name, group_name) from exc def _join_objects(self, target: 'Object', children: 'Iterable[str]'): # log.info("Joining: %s <- %s", target.name, children) _commons.ensure_deselect_all_objects() for child_name in children: obj = _D.objects[child_name] obj.hide_set(False) obj.select_set(True) _commons.move_children_to_grandparent(obj) _commons.activate_object(target) _commons.ensure_op_finished(_bpy.ops.object.join(), name='bpy.ops.object.join') _commons.ensure_deselect_all_objects() # log.info("Joined: %s <- %s", target, children) pass def _call_before_join(self, root: 'str', join_to: 'str', group_name: 'Optional[str]', group_objs: 'Set[str]'): try: self.before_join(root, join_to, group_name, group_objs) except Exception as exc: msg = 'Error before_join: root={0}, join_to={1}, group_name={2}, group_objs={3}'.format(root, join_to, group_name, group_objs) raise RuntimeError(msg, root, join_to, group_name, group_objs) from exc def _call_after_join(self, root: 'str', join_to: 'str', group_name: 'Optional[str]'): try: self.after_join(root, join_to, group_name) except Exception as exc: msg = 'Error after_join: root={0}, join_to={1}, group_name={2}'.format(root, join_to, group_name) raise RuntimeError(msg, root, join_to, group_name) from exc def _process_root(self, root_name: 'str') -> 'int': # Поиск меш-объектов-детей root на этой же сцене. scene_objs = (self.scene or _C.scene).collection.objects children_queue = _deque() # type: Deque[str] children = set() # type: Set[str] children_queue.extend(x.name for x in _D.objects[root_name].children) while len(children_queue) > 0: child_name = children_queue.pop() child = _D.objects[child_name] children_queue.extend(x.name for x in child.children) if child_name not in scene_objs: continue if not isinstance(child.data, _bpy.types.Mesh): continue children.add(child_name) self._call_before_group(root_name, children) groups = dict() # type: Dict[Optional[str], Set[str]] for child_name in children: group_name = self._call_group_child(root_name, child_name) if group_name is False: continue # skip group = groups.get(group_name) if group is None: group = set() groups[group_name] = group group.add(child_name) # log.info('%s %s', root_name, repr(groups)) def create_mesh_obj(name): new_mesh = _D.meshes.new(name + '-Mesh') new_obj = _D.objects.new(name, object_data=new_mesh) new_obj.name = name # force rename scene_objs.link(new_obj) return new_obj # Далее намерено избегаем ссылок на root объект, т.к. объекты меняются # и можно отхватить ошибку StructRNA of type Object has been removed obj_group_count = 0 for group_name, obj_group in groups.items(): join_to = None if group_name is None: if isinstance(_D.objects[root_name].data, _bpy.types.Mesh): # root - Это меш, приклееваем к нему. join_to = _D.objects[root_name] elif self.force_mesh_root: # root - Это НЕ меш, но force_mesh_root. base_name = root_name old_root = _D.objects[root_name] old_root.name = base_name + '-Replaced' self.replaced_objects.add(old_root.name) join_to = create_mesh_obj(base_name) root_name = join_to.name # Фактическое новое имя self.created_objects.add(root_name) join_to.parent = old_root.parent join_to.parent_type = 'OBJECT' join_to.location = old_root.location join_to.rotation_mode = old_root.rotation_mode join_to.rotation_axis_angle = old_root.rotation_axis_angle join_to.rotation_euler = old_root.rotation_euler join_to.rotation_quaternion = old_root.rotation_quaternion join_to.scale = old_root.scale for sub_child in old_root.children: # type: Object sub_child.parent = join_to else: # root - Это НЕ меш, создаём подгруппу. join_to = create_mesh_obj(root_name + '-' + self.default_group) self.created_objects.add(join_to.name) join_to.parent = _D.objects[root_name] join_to.parent_type = 'OBJECT' _commons.identity_transform(join_to) else: join_to = create_mesh_obj(root_name + '-' + group_name) self.created_objects.add(join_to.name) join_to.parent = _D.objects[root_name] join_to.parent_type = 'OBJECT' _commons.identity_transform(join_to) self._call_before_join(root_name, join_to.name, group_name, obj_group) self._join_objects(join_to, obj_group) self._call_after_join(root_name, join_to.name, group_name) obj_group_count += len(obj_group) return obj_group_count def combine_meshes(self): self._check_roots() obj_n, obj_i, joins = len(self.roots_names), 0, 0 reporter = _LambdaReporter(self.report_time) reporter.func = lambda r, t: _log.info( "Joining meshes: Roots={0}/{1}, Joined={2}, Time={3:.1f} sec, ETA={4:.1f} sec...".format( obj_i, obj_n, joins, t, r.get_eta(1.0 * obj_i / obj_n))) for root_name in self.roots_names: joins += self._process_root(root_name) obj_i += 1 reporter.ask_report(False) reporter.ask_report(True)
Methods
def after_join(self, root: str, join_to: str, group_name: Optional[str])
-
Expand source code
def after_join(self, root: 'str', join_to: 'str', group_name: 'Optional[str]'): # Функция, вызываемая после присоединения. # Аргументы: # - root - исходный объект из root # - join_to - куда было выполнено присоединение. # - group_name - группа, т.е. то, что вернул .selector # Может быть root is join_to # Ничего возвращать не нужно. # Можно использовать, например, для починки lightmap UV. pass
def before_group(self, root: str, children: Set[str])
-
Expand source code
def before_group(self, root: 'str', children: 'Set[str]'): # Функция, вызываемая перед группировкой детей. pass
def before_join(self, root: str, join_to: str, group_name: Optional[str], group_objs: Set[str])
-
Expand source code
def before_join(self, root: 'str', join_to: 'str', group_name: 'Optional[str]', group_objs: 'Set[str]'): # Функция, вызываемая после создания нового меш-объекта, к которому будет присоединение. # Аргументы: # - root - исходный объект из root # - join_to - куда будет происходить присоединения. # - group_name - группа, т.е. то, что вернул .selector # - group_objs - объекты (которые выбраны .selector-ом) для присоеднинения # Может быть root is join_to # Ничего возвращать не нужно. # Можно использовать, например, для применения/переноса какх-то свойств объекта # или для правки UV для корректного сведения. pass
def combine_meshes(self)
-
Expand source code
def combine_meshes(self): self._check_roots() obj_n, obj_i, joins = len(self.roots_names), 0, 0 reporter = _LambdaReporter(self.report_time) reporter.func = lambda r, t: _log.info( "Joining meshes: Roots={0}/{1}, Joined={2}, Time={3:.1f} sec, ETA={4:.1f} sec...".format( obj_i, obj_n, joins, t, r.get_eta(1.0 * obj_i / obj_n))) for root_name in self.roots_names: joins += self._process_root(root_name) obj_i += 1 reporter.ask_report(False) reporter.ask_report(True)
def group_child(self, root: str, child: str) ‑> Union[None, str, bool]
-
Expand source code
def group_child(self, root: 'str', child: 'str') -> 'Union[None, str, bool]': # Функция, которая говорит, как объединять меши. # Аргументы: # - объект из root, в который предлагается подсоединить меш # - предлагаемый дочерний меш-объект, который предлагается подсоединить к первому # Функция должна вернуть одно из: # - False если предлагаемый объект объединять не нужно # - строку с именем группы объединения # - None, что бы объединить в группу по-молчанию / без группы # Если root-объект - меш-объект, то объекты с группой None вольются в него. # Если root-объект - не меш-объект, то поведение зависит от .force_mesh_root return None
-