RCG Pipeline Release with Docs
This commit is contained in:
parent
df0fb32592
commit
3878990c4e
26 changed files with 2127 additions and 54 deletions
|
@ -1,20 +1,9 @@
|
|||
## cg.freecad.utils
|
||||
## FreeCAD's tools for publishing .FCStd scene.
|
||||
|
||||
Общие инструменты для работы в среде FreeCAD.
|
||||
|
||||
### freecad_to_json.py
|
||||
|
||||
Сценарий производит:
|
||||
- анализ FCStd сцены FreeCAD на наличие:
|
||||
- твердых тел
|
||||
- твердотельных поверхностей
|
||||
- точек координат LCS
|
||||
- иерархии
|
||||
- скрытых объектов
|
||||
- FEM материалов
|
||||
- тесселяцию твердых тел по заданным параметрам
|
||||
- формирование описания сцены в виде JSON конфигурации
|
||||
|
||||
### is_object_solid.py
|
||||
|
||||
Проверка, является ли объект твердотельным.
|
||||
```python
|
||||
import sys
|
||||
sys.path.append('/path/to/freecad_exporters')
|
||||
import freecad_exporters
|
||||
freecad_exporters.publish_project_database()
|
||||
```
|
||||
|
|
|
@ -48,15 +48,15 @@ logging.basicConfig(level=logging.INFO)
|
|||
def export_assembly_trees(doc, clones_dic=None) -> list:
|
||||
''' Read FreeCAD .FCStd hierarchy and store it to assembly JSON config files. '''
|
||||
# determine root locators
|
||||
lcs_root_points = []
|
||||
default_origins = []
|
||||
for obj in doc.Objects:
|
||||
if obj.isDerivedFrom('PartDesign::CoordinateSystem'):
|
||||
if (hasattr(obj, 'Robossembler_DefaultOrigin')
|
||||
and getattr(obj, 'Robossembler_DefaultOrigin')):
|
||||
lcs_root_points.append(obj)
|
||||
if len(lcs_root_points) > 1:
|
||||
default_origins.append(obj)
|
||||
if len(default_origins) > 1:
|
||||
root_locators = []
|
||||
for lcs in lcs_root_points:
|
||||
for lcs in default_origins:
|
||||
if hasattr(lcs, 'Robossembler_RootLocator'):
|
||||
root_locators.append(getattr(lcs, 'Robossembler_DefaultOrigin'))
|
||||
else:
|
||||
|
@ -68,23 +68,23 @@ def export_assembly_trees(doc, clones_dic=None) -> list:
|
|||
root_locators = [
|
||||
root for root in doc.Objects
|
||||
if not root.InList
|
||||
if root .isDerivedFrom('App::Part')]
|
||||
if root.isDerivedFrom('App::Part')]
|
||||
|
||||
config_files = []
|
||||
tree_files = []
|
||||
for root_locator in root_locators:
|
||||
dict_tree = {}
|
||||
freecad_tools.hierarchy_tree(root_locator, dict_tree, clones_dic)
|
||||
|
||||
# write file
|
||||
main_file_dir = os.path.dirname(doc.FileName)
|
||||
assembly_tree_path = os.path.join(main_file_dir, f'{root_locator.Label}.json')
|
||||
project_dir = os.path.dirname(doc.FileName)
|
||||
assembly_tree_path = os.path.join(project_dir, root_locator.Label + '_tree_version.json')
|
||||
with open(assembly_tree_path, 'w', encoding='utf-8') as json_file:
|
||||
json.dump(dict_tree, json_file, ensure_ascii=False, indent=4)
|
||||
logger.info('Assembly tree saved successfully to %s!', assembly_tree_path)
|
||||
config_files.append(assembly_tree_path)
|
||||
logger.info('Saved %s assembly trees!', len(config_files))
|
||||
tree_files.append(assembly_tree_path)
|
||||
logger.info('Saved %s assembly trees!', len(tree_files))
|
||||
|
||||
return config_files
|
||||
return tree_files
|
||||
|
||||
|
||||
def export_parts_database(
|
||||
|
@ -94,9 +94,7 @@ def export_parts_database(
|
|||
Collect parts database and export as JSON config file
|
||||
[
|
||||
{
|
||||
'type': '',
|
||||
'name': '',
|
||||
'attributes': [],
|
||||
'part_path': '',
|
||||
'material_path': ''
|
||||
},
|
||||
|
@ -104,13 +102,19 @@ def export_parts_database(
|
|||
]
|
||||
'''
|
||||
# path directory
|
||||
main_file_dir = os.path.dirname(doc.FileName)
|
||||
parts_dir = os.path.join(main_file_dir, 'parts')
|
||||
if os.path.exists(parts_dir):
|
||||
shutil.rmtree(parts_dir)
|
||||
os.makedirs(parts_dir)
|
||||
project_dir = os.path.dirname(doc.FileName)
|
||||
materials_dir = os.path.join(project_dir, 'parts', 'materials')
|
||||
if os.path.exists(materials_dir):
|
||||
shutil.rmtree(materials_dir)
|
||||
os.makedirs(materials_dir)
|
||||
else:
|
||||
os.makedirs(parts_dir)
|
||||
os.makedirs(materials_dir)
|
||||
objects_dir = os.path.join(project_dir, 'parts', 'objects')
|
||||
if os.path.exists(objects_dir):
|
||||
shutil.rmtree(objects_dir)
|
||||
os.makedirs(objects_dir)
|
||||
else:
|
||||
os.makedirs(objects_dir)
|
||||
|
||||
# collect all materials
|
||||
fem_mats = [fem_mat for fem_mat in doc.Objects
|
||||
|
@ -140,7 +144,11 @@ def export_parts_database(
|
|||
logger.warning('Part has non solid shape! Please check %s', obj.Label)
|
||||
continue
|
||||
|
||||
db_obj = {'type': 'PART'}
|
||||
db_obj = {
|
||||
'name': '',
|
||||
'part_path': '',
|
||||
'material_path': ''
|
||||
}
|
||||
|
||||
db_obj['name'] = obj.Label
|
||||
if clones_dic:
|
||||
|
@ -154,7 +162,7 @@ def export_parts_database(
|
|||
for attr in robossembler_attrs:
|
||||
db_obj['attributes'].append({attr: getattr(obj, attr)})
|
||||
|
||||
part_path = os.path.join(parts_dir, db_obj['name'] + '.stl')
|
||||
part_path = os.path.join(objects_dir, db_obj['name'] + '.stl')
|
||||
if os.path.exists(part_path):
|
||||
# this is clone
|
||||
continue
|
||||
|
@ -167,7 +175,7 @@ def export_parts_database(
|
|||
# export to stl files
|
||||
#Mesh.export([mesh_from_shape], part_path, tolerance=linear_deflection)
|
||||
mesh_from_shape.Mesh.write(part_path)
|
||||
db_obj['part_path'] = os.path.relpath(part_path, main_file_dir)
|
||||
db_obj['part_path'] = os.path.relpath(part_path, project_dir)
|
||||
logger.info('Part %s exported to stl file %s.', obj.Label, part_path)
|
||||
|
||||
# find linked material path
|
||||
|
@ -176,15 +184,20 @@ def export_parts_database(
|
|||
for ref in fem_mat.References
|
||||
if ref[0].Label == obj.Label]
|
||||
if fem_mat_name:
|
||||
for material_path in material_paths:
|
||||
if fem_mat_name[0] in material_path:
|
||||
db_obj['material_path'] = material_path
|
||||
for source_path in material_paths:
|
||||
if fem_mat_name[0] not in source_path:
|
||||
continue
|
||||
material_path = os.path.join(
|
||||
materials_dir, os.path.basename(source_path))
|
||||
if not os.path.exists(material_path):
|
||||
shutil.copy2(source_path, material_path)
|
||||
db_obj['material_path'] = os.path.relpath(material_path, project_dir)
|
||||
|
||||
# append to database
|
||||
parts_db.append(db_obj)
|
||||
|
||||
logger.info('Passed %s parts without errors', len(parts_db))
|
||||
parts_db_path = os.path.join(main_file_dir, f'{FreeCAD.ActiveDocument.Label}.FCStd.json')
|
||||
parts_db_path = os.path.join(project_dir, FreeCAD.ActiveDocument.Label + '_parts_data.json')
|
||||
with open(parts_db_path, 'w', encoding='utf-8') as json_file:
|
||||
json.dump(parts_db, json_file, ensure_ascii=False, indent=4)
|
||||
logger.info('Parts Database exported successfully to %s!', parts_db_path)
|
||||
|
@ -218,9 +231,8 @@ def publish_project_database(doc=FreeCAD.getDocument(FreeCAD.ActiveDocument.Labe
|
|||
#doc.saveAs(u"/<file_dir>/<file_name>")
|
||||
|
||||
clones_dic = freecad_tools.collect_clones(doc)
|
||||
|
||||
export_assembly_trees(doc, clones_dic)
|
||||
export_parts_database(doc, clones_dic, **tesselation_params)
|
||||
tree_files = export_assembly_trees(doc, clones_dic)
|
||||
parts_data = export_parts_database(doc, clones_dic, **tesselation_params)
|
||||
|
||||
logger.info('FreeCAD document %s published!', doc.Label)
|
||||
|
||||
|
|
|
@ -244,8 +244,7 @@ def hierarchy_tree(obj, dict_tree, clones_dic=None) -> dict:
|
|||
'type': '',
|
||||
'name': '',
|
||||
'base_name': '',
|
||||
'loc_xyz': [],
|
||||
'rot_xyzw': [],
|
||||
'pose': [{'loc_xyz': []}, {'rot_xyzw': []}],
|
||||
'attributes': [],
|
||||
'children': [
|
||||
{
|
||||
|
@ -261,6 +260,7 @@ def hierarchy_tree(obj, dict_tree, clones_dic=None) -> dict:
|
|||
]
|
||||
}
|
||||
'''
|
||||
|
||||
# collect type
|
||||
if obj.isDerivedFrom('Part::Feature'):
|
||||
if obj.isDerivedFrom('PartDesign::CoordinateSystem'):
|
||||
|
@ -274,22 +274,25 @@ def hierarchy_tree(obj, dict_tree, clones_dic=None) -> dict:
|
|||
# collect name
|
||||
dict_tree['name'] = obj.Label
|
||||
# collect base_name
|
||||
dict_tree['base_name'] = ''
|
||||
if clones_dic:
|
||||
if obj.isDerivedFrom('Part::Feature'):
|
||||
for k, v in clones_dic.items():
|
||||
if obj.Label in v:
|
||||
dict_tree['base_name'] = k
|
||||
# collect transforms
|
||||
dict_tree['loc_xyz'] = list(obj.Placement.Base)
|
||||
dict_tree['rot_xyzw'] = list(obj.Placement.Rotation.Q)
|
||||
dict_tree['pose'] = [
|
||||
{'loc_xyz': list(obj.Placement.Base)},
|
||||
{'rot_xyzw': list(obj.Placement.Rotation.Q)}
|
||||
]
|
||||
# collect attributes
|
||||
dict_tree['attributes'] = []
|
||||
robossembler_attrs = [attr for attr in dir(obj) if 'Robossembler' in attr]
|
||||
if robossembler_attrs:
|
||||
dict_tree['attributes'] = []
|
||||
for attr in robossembler_attrs:
|
||||
dict_tree['attributes'].append({attr: getattr(obj, attr)})
|
||||
# collect children
|
||||
if obj.OutList:
|
||||
# collect children for LOCATOR only
|
||||
if obj.OutList and obj.isDerivedFrom('App::Part'):
|
||||
dict_tree['children'] = []
|
||||
for child in obj.OutList:
|
||||
# skip hidden objects
|
||||
|
|
157
rcg_pipeline/LICENSE
Normal file
157
rcg_pipeline/LICENSE
Normal file
|
@ -0,0 +1,157 @@
|
|||
# GNU LESSER GENERAL PUBLIC LICENSE
|
||||
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc.
|
||||
<https://fsf.org/>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim copies of this
|
||||
license document, but changing it is not allowed.
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates the
|
||||
terms and conditions of version 3 of the GNU General Public License,
|
||||
supplemented by the additional permissions listed below.
|
||||
|
||||
## 0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the
|
||||
GNU General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License, other
|
||||
than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
## 1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
## 2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
- a) under this License, provided that you make a good faith effort
|
||||
to ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
- b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
## 3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from a
|
||||
header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
- a) Give prominent notice with each copy of the object code that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
- b) Accompany the object code with a copy of the GNU GPL and this
|
||||
license document.
|
||||
|
||||
## 4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that, taken
|
||||
together, effectively do not restrict modification of the portions of
|
||||
the Library contained in the Combined Work and reverse engineering for
|
||||
debugging such modifications, if you also do each of the following:
|
||||
|
||||
- a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
- b) Accompany the Combined Work with a copy of the GNU GPL and this
|
||||
license document.
|
||||
- c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
- d) Do one of the following:
|
||||
- 0) Convey the Minimal Corresponding Source under the terms of
|
||||
this License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
- 1) Use a suitable shared library mechanism for linking with
|
||||
the Library. A suitable mechanism is one that (a) uses at run
|
||||
time a copy of the Library already present on the user's
|
||||
computer system, and (b) will operate properly with a modified
|
||||
version of the Library that is interface-compatible with the
|
||||
Linked Version.
|
||||
- e) Provide Installation Information, but only if you would
|
||||
otherwise be required to provide such information under section 6
|
||||
of the GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the Application
|
||||
with a modified version of the Linked Version. (If you use option
|
||||
4d0, the Installation Information must accompany the Minimal
|
||||
Corresponding Source and Corresponding Application Code. If you
|
||||
use option 4d1, you must provide the Installation Information in
|
||||
the manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.)
|
||||
|
||||
## 5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the Library
|
||||
side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
- a) Accompany the combined library with a copy of the same work
|
||||
based on the Library, uncombined with any other library
|
||||
facilities, conveyed under the terms of this License.
|
||||
- b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
## 6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Library
|
||||
as you received it specifies that a certain numbered version of the
|
||||
GNU Lesser General Public License "or any later version" applies to
|
||||
it, you have the option of following the terms and conditions either
|
||||
of that published version or of any later version published by the
|
||||
Free Software Foundation. If the Library as you received it does not
|
||||
specify a version number of the GNU Lesser General Public License, you
|
||||
may choose any version of the GNU Lesser General Public License ever
|
||||
published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
31
rcg_pipeline/Makefile
Normal file
31
rcg_pipeline/Makefile
Normal file
|
@ -0,0 +1,31 @@
|
|||
.FORCE:
|
||||
|
||||
BLUE=\033[0;34m
|
||||
BLACK=\033[0;30m
|
||||
|
||||
help:
|
||||
@echo "$(BLUE) make test - run all unit tests"
|
||||
@echo " make coverage - run unit tests and coverage report"
|
||||
@echo " make dist - build dist files"
|
||||
@echo " make upload - upload to PyPI"
|
||||
@echo " make clean - remove dist and docs build files"
|
||||
@echo " make help - this message$(BLACK)"
|
||||
|
||||
test:
|
||||
pytest
|
||||
|
||||
coverage:
|
||||
coverage run --omit=\*/test_\* -m unittest
|
||||
coverage report
|
||||
|
||||
dist: .FORCE
|
||||
#$(MAKE) test
|
||||
python -m build
|
||||
ls -lh dist
|
||||
|
||||
upload: .FORCE
|
||||
twine upload dist/*
|
||||
|
||||
clean: .FORCE
|
||||
-rm -r *.egg-info
|
||||
-rm -r dist build
|
37
rcg_pipeline/README.md
Normal file
37
rcg_pipeline/README.md
Normal file
|
@ -0,0 +1,37 @@
|
|||
## Robossembler CG Pipeline
|
||||
|
||||
Алгоритмы запуска технологии компьютерной графики.
|
||||
|
||||
Пакетное производство 3д ассетов из базы данных тесселированных объектов САПР (parts) их сборочной иерархии.
|
||||
|
||||
Поддерживается работа поверх Blender в качестве модуля!
|
||||
|
||||
Этапы алгоритма:
|
||||
|
||||
* генерация Blender сцены,
|
||||
* исправоение parts объектов,
|
||||
* генерация и назначение CG материала parts объектам,
|
||||
* перестроение иерархии сборок на основе данных LCS объектов,
|
||||
* группировка parts объектов объектов в составные RENDER ассеты,
|
||||
* генерация, и развертка монолитных VISUAL ассетов из RENDER ассетов,
|
||||
* запекание поверхности и материала RENDER ассетоа в текстуры VISUAL ассетов,
|
||||
* назначение материалов и тестур VISUAL ассетам,
|
||||
* генерация COLLISION ассетов из VISUAL ассетов,
|
||||
- экспорт всех типов ассетов в требуемые форматы базы данных.
|
||||
|
||||
Пример запуска::
|
||||
```python
|
||||
# nixGL /nix/store/gd3shnza1i50zn8zs04fa729ribr88m9-python3-3.11.8/bin/python3
|
||||
|
||||
import sys
|
||||
sys.path.append('/nix/store/ip49yhfl2l8gphylj4i43kdpq5qnq93i-bpy-4.1.0/lib/python3.11/site-packages')
|
||||
sys.path.append('/media/disk/robossembler/project/collab/150-cg-render-assets/rcg_pipeline')
|
||||
sys.path.append('/nix/store/x1rqfn240xn6m6p6077gxfqxdxxj1cmc-python3.11-numpy-1.26.4/lib/python3.11/site-packages')
|
||||
import rcg_pipeline
|
||||
project_dir = '/path/to/<my_freecad_project>'
|
||||
parts_tree_path = '/path/to/<my_assembly>_tree_version.json'
|
||||
libs_data_path = '/path/to/<my_freecad_project>_libs_data.json'
|
||||
|
||||
rcg_pipeline.libs.generate_libs_database(project_dir)
|
||||
rcg_pipeline.render_asset.build_render_asset(parts_tree_path, libs_data_path, project_dir)
|
||||
```
|
29
rcg_pipeline/pyproject.toml
Normal file
29
rcg_pipeline/pyproject.toml
Normal file
|
@ -0,0 +1,29 @@
|
|||
[build-system]
|
||||
requires = ["setuptools >= 69.0"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["rcg_pipeline"]
|
||||
|
||||
[project]
|
||||
name = "rcg_pipeline"
|
||||
version = "1.0.0"
|
||||
description = "Robossembler CG Pipeline"
|
||||
readme = "README.md"
|
||||
license = {file = "LICENSE"}
|
||||
authors = [{name = "Ilia Kurochkin", email = "brothermechanic@yandex.com"}]
|
||||
maintainers = [{name = "Igor Brylyov", email = "movefasta@dezcom.org"}]
|
||||
requires-python = ">= 3.10"
|
||||
dependencies = ["numpy >= 1.26.4"]
|
||||
classifiers = [
|
||||
"Development Status :: 3 - Alpha",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Operating System :: Linux",
|
||||
]
|
||||
keywords = ["cg", "robossembler", "blender", "engineering", "robotics", "gamedev"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://robossembler.org/"
|
||||
Documentation = "https://robossembler.org/docs/technologies/cad-cg-pipeline"
|
||||
Repository = "https://gitlab.com/robossembler/framework.git"
|
||||
Issues = "https://gitlab.com/robossembler/framework/-/issues"
|
67
rcg_pipeline/rcg_pipeline/__init__.py
Normal file
67
rcg_pipeline/rcg_pipeline/__init__.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2021-2024 Robossembler LLC
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This file is part of Robossembler Framework
|
||||
# project repo: https://gitlab.com/robossembler/framework
|
||||
#
|
||||
# Robossembler Framework is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Computer Graphics Pipeline for Robossembler Framework.
|
||||
Batch production of 3d assets from the database
|
||||
of tessellated CAD objects (parts) and their assembling hierarchy.
|
||||
'''
|
||||
|
||||
__title__ = 'Robossembler CG Pipeline'
|
||||
__version__ = '1.0.0'
|
||||
__author__ = 'Ilia Kurochkin'
|
||||
__email__ = 'brothermechanic@yandex.com'
|
||||
__copyright__ = 'Copyright (C) 2021-2024 Robossembler LLC'
|
||||
__url__ = ['https://robossembler.org']
|
||||
__license__ = 'GPL-3'
|
||||
#__all__ = ['libs', 'render_asset', 'rcg_full_pipeline']
|
||||
|
||||
import logging
|
||||
import os
|
||||
|
||||
from . import libs
|
||||
from . import render_asset
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
def rcg_full_pipeline(project_dir):
|
||||
'''
|
||||
'''
|
||||
# 1 generate libs
|
||||
libs_data_path = libs.generate_libs_database(project_dir)
|
||||
|
||||
# 2 build render assets
|
||||
parts_tree_names = [
|
||||
data for data in os.listdir(project_dir)
|
||||
if data.endswith('tree_version.json')]
|
||||
for parts_tree_name in parts_tree_names:
|
||||
parts_tree_path = os.path.join(project_dir, parts_tree_name)
|
||||
render_asset.build_render_asset(parts_tree_path, libs_data_path, project_dir)
|
||||
return True
|
77
rcg_pipeline/rcg_pipeline/export/__init__.py
Normal file
77
rcg_pipeline/rcg_pipeline/export/__init__.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Decorator for export functions.
|
||||
'''
|
||||
|
||||
import os
|
||||
import bpy
|
||||
import mathutils
|
||||
|
||||
|
||||
def export_decorator(func):
|
||||
|
||||
def wrapper(**kwargs):
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
# add defaults
|
||||
kwargs.setdefault('global_scale', 1000)
|
||||
kwargs.setdefault('axis_forward', 'Y')
|
||||
kwargs.setdefault('axis_up', 'Z')
|
||||
kwargs.setdefault('file_dir', '//')
|
||||
kwargs.setdefault('sub_dir', '')
|
||||
kwargs.setdefault('reset_transforms', False)
|
||||
|
||||
if kwargs['reset_transforms']:
|
||||
obj = bpy.data.objects.get(kwargs['obj_name'])
|
||||
obj.name = kwargs['obj_name'] + '_orig'
|
||||
obj_data = obj.data.copy()
|
||||
obj_tmp = obj.copy()
|
||||
obj_tmp.data = obj_data
|
||||
obj_tmp.name = kwargs['obj_name']
|
||||
bpy.context.collection.objects.link(obj_tmp)
|
||||
obj_tmp.parent = None
|
||||
obj_tmp.matrix_world = mathutils.Matrix()
|
||||
|
||||
obj = bpy.data.objects.get(kwargs['obj_name'])
|
||||
# deselect all but just one object and make it active
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
obj.select_set(state=True)
|
||||
bpy.context.view_layer.objects.active = obj
|
||||
file_dir = os.path.join(kwargs['file_dir'], kwargs['sub_dir'])
|
||||
os.makedirs(file_dir, exist_ok=True)
|
||||
kwargs['outpath'] = os.path.join(file_dir, kwargs['obj_name'])
|
||||
# return export function
|
||||
file_path = func(**kwargs)
|
||||
|
||||
#cleanup temporary object
|
||||
if kwargs['reset_transforms']:
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
obj = bpy.data.objects.get(kwargs['obj_name'] + '_orig')
|
||||
obj.name = kwargs['obj_name']
|
||||
|
||||
return file_path
|
||||
|
||||
return wrapper
|
75
rcg_pipeline/rcg_pipeline/export/dae.py
Normal file
75
rcg_pipeline/rcg_pipeline/export/dae.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Collada object exporter.
|
||||
'''
|
||||
|
||||
__version__ = '0.3'
|
||||
|
||||
import bpy
|
||||
from . import export_decorator
|
||||
|
||||
|
||||
@export_decorator
|
||||
def export(**kwargs):
|
||||
file_path = ('{}.dae'.format(kwargs['outpath']))
|
||||
|
||||
bpy.ops.wm.collada_export(
|
||||
filepath=file_path,
|
||||
check_existing=False,
|
||||
apply_modifiers=True,
|
||||
export_mesh_type=0,
|
||||
export_mesh_type_selection='view',
|
||||
export_global_forward_selection=kwargs['axis_forward'],
|
||||
export_global_up_selection=kwargs['axis_up'],
|
||||
apply_global_orientation=False,
|
||||
selected=True,
|
||||
include_children=False,
|
||||
include_armatures=False,
|
||||
include_shapekeys=False,
|
||||
deform_bones_only=False,
|
||||
include_animations=False,
|
||||
include_all_actions=True,
|
||||
export_animation_type_selection='sample',
|
||||
sampling_rate=1,
|
||||
keep_smooth_curves=False,
|
||||
keep_keyframes=False,
|
||||
keep_flat_curves=False,
|
||||
active_uv_only=False,
|
||||
use_texture_copies=True,
|
||||
triangulate=True,
|
||||
use_object_instantiation=True,
|
||||
use_blender_profile=True,
|
||||
sort_by_name=False,
|
||||
export_object_transformation_type=0,
|
||||
export_object_transformation_type_selection='matrix',
|
||||
export_animation_transformation_type=0,
|
||||
export_animation_transformation_type_selection='matrix',
|
||||
open_sim=False,
|
||||
limit_precision=False,
|
||||
keep_bind_info=False)
|
||||
|
||||
return file_path
|
84
rcg_pipeline/rcg_pipeline/export/fbx.py
Normal file
84
rcg_pipeline/rcg_pipeline/export/fbx.py
Normal file
|
@ -0,0 +1,84 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
FBX object exporter.
|
||||
'''
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
import bpy
|
||||
from . import export_decorator
|
||||
|
||||
|
||||
@export_decorator
|
||||
def export(**kwargs):
|
||||
file_path = ('{}.fbx'.format(kwargs['outpath']))
|
||||
|
||||
bpy.ops.export_scene.fbx(
|
||||
filepath=file_path,
|
||||
check_existing=False,
|
||||
filter_glob="*.fbx",
|
||||
use_selection=True,
|
||||
use_visible=False,
|
||||
use_active_collection=False,
|
||||
global_scale=1,
|
||||
apply_unit_scale=True,
|
||||
apply_scale_options='FBX_SCALE_NONE',
|
||||
use_space_transform=True,
|
||||
bake_space_transform=False,
|
||||
object_types={'MESH'},
|
||||
use_mesh_modifiers=True,
|
||||
use_mesh_modifiers_render=True,
|
||||
mesh_smooth_type='FACE',
|
||||
colors_type='SRGB',
|
||||
use_subsurf=False,
|
||||
use_mesh_edges=False,
|
||||
use_tspace=False,
|
||||
use_triangles=True,
|
||||
use_custom_props=False,
|
||||
add_leaf_bones=True,
|
||||
primary_bone_axis='Y',
|
||||
secondary_bone_axis='X',
|
||||
use_armature_deform_only=False,
|
||||
armature_nodetype='NULL',
|
||||
bake_anim=False,
|
||||
bake_anim_use_all_bones=True,
|
||||
bake_anim_use_nla_strips=True,
|
||||
bake_anim_use_all_actions=True,
|
||||
bake_anim_force_startend_keying=True,
|
||||
bake_anim_step=1,
|
||||
bake_anim_simplify_factor=1,
|
||||
path_mode='AUTO',
|
||||
embed_textures=False,
|
||||
batch_mode='OFF',
|
||||
use_batch_own_dir=True,
|
||||
use_metadata=True,
|
||||
# '-Z'
|
||||
axis_forward=kwargs['axis_forward'],
|
||||
# 'Y'
|
||||
axis_up=kwargs['axis_up'])
|
||||
|
||||
return file_path
|
83
rcg_pipeline/rcg_pipeline/export/glb.py
Normal file
83
rcg_pipeline/rcg_pipeline/export/glb.py
Normal file
|
@ -0,0 +1,83 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
glTF object exporter.
|
||||
'''
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
import bpy
|
||||
from . import export_decorator
|
||||
|
||||
|
||||
@export_decorator
|
||||
def export(**kwargs):
|
||||
file_path = ('{}.glb'.format(kwargs['outpath']))
|
||||
|
||||
bpy.ops.export_scene.gltf(
|
||||
filepath=file_path,
|
||||
check_existing=False,
|
||||
gltf_export_id="",
|
||||
export_use_gltfpack=False,
|
||||
export_format='GLB',
|
||||
export_copyright="glTF object exporter",
|
||||
export_image_format='AUTO',
|
||||
export_image_add_webp=False,
|
||||
export_image_webp_fallback=False,
|
||||
export_texture_dir="",
|
||||
export_jpeg_quality=75,
|
||||
export_image_quality=75,
|
||||
export_keep_originals=False,
|
||||
export_texcoords=True,
|
||||
export_normals=True,
|
||||
# no custom
|
||||
export_draco_mesh_compression_enable=False,
|
||||
export_tangents=False,
|
||||
export_materials='EXPORT',
|
||||
export_unused_images=False,
|
||||
export_unused_textures=False,
|
||||
export_attributes=False,
|
||||
# custom
|
||||
use_selection=True,
|
||||
use_visible=False,
|
||||
use_renderable=False,
|
||||
use_active_collection_with_nested=True,
|
||||
use_active_collection=False,
|
||||
use_active_scene=False,
|
||||
export_extras=False,
|
||||
# no custom
|
||||
export_yup=True,
|
||||
export_apply=False,
|
||||
# custom
|
||||
export_animations=False,
|
||||
# custom
|
||||
export_morph=False,
|
||||
export_hierarchy_flatten_bones=False,
|
||||
export_original_specular=False,
|
||||
will_save_settings=False,
|
||||
export_hierarchy_full_collections=False)
|
||||
|
||||
return file_path
|
55
rcg_pipeline/rcg_pipeline/export/ply.py
Normal file
55
rcg_pipeline/rcg_pipeline/export/ply.py
Normal file
|
@ -0,0 +1,55 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
PLY object exporter.
|
||||
'''
|
||||
|
||||
__version__ = "0.3"
|
||||
|
||||
import bpy
|
||||
from . import export_decorator
|
||||
|
||||
|
||||
@export_decorator
|
||||
def export(**kwargs):
|
||||
file_path = ('{}.ply'.format(kwargs['outpath']))
|
||||
|
||||
bpy.ops.wm.ply_export(
|
||||
filepath=file_path,
|
||||
check_existing=False,
|
||||
forward_axis=kwargs['axis_forward'],
|
||||
up_axis=kwargs['axis_up'],
|
||||
global_scale=kwargs['global_scale'],
|
||||
apply_modifiers=True,
|
||||
export_selected_objects=True,
|
||||
export_uv=True,
|
||||
export_normals=True,
|
||||
export_colors='SRGB',
|
||||
export_attributes=True,
|
||||
export_triangulated_mesh=True,
|
||||
ascii_format=True)
|
||||
|
||||
return file_path
|
52
rcg_pipeline/rcg_pipeline/export/stl.py
Normal file
52
rcg_pipeline/rcg_pipeline/export/stl.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
STL object exporter.
|
||||
'''
|
||||
|
||||
__version__ = '0.3'
|
||||
|
||||
import bpy
|
||||
from . import export_decorator
|
||||
|
||||
|
||||
@export_decorator
|
||||
def export(**kwargs):
|
||||
file_path = ('{}.stl'.format(kwargs['outpath']))
|
||||
|
||||
bpy.ops.wm.stl_export(
|
||||
filepath=file_path,
|
||||
check_existing=False,
|
||||
ascii_format=False,
|
||||
use_batch=False,
|
||||
export_selected_objects=True,
|
||||
global_scale=kwargs['global_scale'],
|
||||
use_scene_unit=False,
|
||||
forward_axis=kwargs['axis_forward'],
|
||||
up_axis=kwargs['axis_up'],
|
||||
apply_modifiers=True)
|
||||
|
||||
return file_path
|
208
rcg_pipeline/rcg_pipeline/libs.py
Normal file
208
rcg_pipeline/rcg_pipeline/libs.py
Normal file
|
@ -0,0 +1,208 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2021-2024 Robossembler LLC
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This file is part of Robossembler Framework
|
||||
# project repo: https://gitlab.com/robossembler/framework
|
||||
#
|
||||
# Robossembler Framework is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Generate CG libs database for Robossembler Framework.
|
||||
'''
|
||||
|
||||
__version__ = '1.0'
|
||||
|
||||
import logging
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import bpy
|
||||
|
||||
from .utils.collection_tools import remove_collections_with_objects
|
||||
from .utils.cleanup_orphan_data import cleanup_orphan_data
|
||||
from .utils.object_transforms import apply_transforms
|
||||
from .material_generators import material_generator, black_material
|
||||
from .utils.object_converter import mesh_to_mesh
|
||||
from .export import dae, fbx, glb, ply
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def generate_libs_database(project_dir):
|
||||
''' Generate Blender LIBS database from PARTs database. '''
|
||||
parts_data_name = [
|
||||
data for data in os.listdir(project_dir)
|
||||
if data.endswith('_parts_data.json')]
|
||||
if len(parts_data_name) != 1:
|
||||
raise Exception(
|
||||
'No database! Only single supported! Check %s directory' % project_dir)
|
||||
parts_data_path = os.path.join(project_dir, parts_data_name[0])
|
||||
|
||||
data_name = parts_data_name[0].split('_parts_data.json')[0]
|
||||
|
||||
with open(parts_data_path, encoding='utf-8') as data:
|
||||
parts_data = json.load(data)
|
||||
|
||||
libs_data = []
|
||||
|
||||
# material libs
|
||||
materials_dir = os.path.join(project_dir, 'libs', 'materials')
|
||||
if os.path.exists(materials_dir):
|
||||
shutil.rmtree(materials_dir)
|
||||
os.makedirs(materials_dir)
|
||||
else:
|
||||
os.makedirs(materials_dir)
|
||||
|
||||
for part in parts_data:
|
||||
bpy.ops.wm.read_homefile()
|
||||
obj = bpy.data.objects['Cube']
|
||||
if not part.get('material_path'):
|
||||
continue
|
||||
material_name = os.path.splitext(os.path.basename(part['material_path']))[0]
|
||||
# do not regenerate exists material
|
||||
material_in_libs = False
|
||||
for lib in libs_data:
|
||||
if lib['type'] == 'MATERIAL' and lib['name'] == material_name:
|
||||
material_in_libs = True
|
||||
break
|
||||
if material_in_libs:
|
||||
continue
|
||||
# generate material
|
||||
mat = material_generator(obj, os.path.join(project_dir, part['material_path']))
|
||||
bpy.data.materials[mat.name].use_fake_user = True
|
||||
remove_collections_with_objects()
|
||||
cleanup_orphan_data()
|
||||
bpy.data.materials.remove(bpy.data.materials['Dots Stroke'])
|
||||
bpy.ops.wm.previews_ensure()
|
||||
|
||||
img = bpy.data.images.new(mat.name, *mat.preview.image_size, alpha=True)
|
||||
img.pixels.foreach_set(mat.preview.image_pixels_float)
|
||||
img_path = os.path.join(materials_dir, mat.name + '.png')
|
||||
img.save(filepath=img_path)
|
||||
|
||||
blend_path = os.path.join(materials_dir, mat.name + '.blend')
|
||||
logger.info('Material stored as libs %s to %s.', mat.name, materials_dir)
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_path, compress=True)
|
||||
|
||||
libs_data.append(
|
||||
{
|
||||
'type': mat.rna_type.name.upper(),
|
||||
'name': mat.name,
|
||||
'path': os.path.relpath(blend_path, project_dir),
|
||||
'thumbnail': os.path.relpath(img_path, project_dir),
|
||||
}
|
||||
)
|
||||
|
||||
# object libs
|
||||
objects_dir = os.path.join(project_dir, 'libs', 'objects')
|
||||
if os.path.exists(objects_dir):
|
||||
shutil.rmtree(objects_dir)
|
||||
os.makedirs(objects_dir)
|
||||
else:
|
||||
os.makedirs(objects_dir)
|
||||
|
||||
for part in parts_data:
|
||||
bpy.ops.wm.read_homefile()
|
||||
remove_collections_with_objects()
|
||||
cleanup_orphan_data()
|
||||
obj_collection = bpy.data.collections.new(part['name'])
|
||||
bpy.context.scene.collection.children.link(obj_collection)
|
||||
# set active to collection
|
||||
active_collection = bpy.context.view_layer.layer_collection.children[part['name']]
|
||||
bpy.context.view_layer.active_layer_collection = active_collection
|
||||
# import stl file
|
||||
bpy.ops.wm.stl_import(
|
||||
filepath=os.path.join(project_dir, part['part_path']),
|
||||
global_scale=0.001,
|
||||
use_facet_normal=False,
|
||||
forward_axis='Y',
|
||||
up_axis='Z')
|
||||
obj = bpy.data.objects.get(part['name'])
|
||||
if not obj:
|
||||
raise Exception('STL Import Fail for %s' % part['name'])
|
||||
apply_transforms(obj, scale=True)
|
||||
# setup material
|
||||
if part.get('material_path'):
|
||||
material_name = os.path.splitext(os.path.basename(part['material_path']))[0]
|
||||
for lib in libs_data:
|
||||
if lib['type'] == 'MATERIAL' and lib['name'] == material_name:
|
||||
lib_path = os.path.join(project_dir, lib['path'])
|
||||
bpy.ops.wm.link(
|
||||
filepath=lib_path,
|
||||
directory=os.path.join(lib_path, 'Material'),
|
||||
filename=lib['name'],
|
||||
relative_path=True,
|
||||
do_reuse_local_id=True)
|
||||
obj.data.materials.append(bpy.data.materials[lib['name']])
|
||||
else:
|
||||
black_material(obj)
|
||||
|
||||
with bpy.context.temp_override(selected_editable_objects=[obj]):
|
||||
bpy.ops.object.shade_smooth_by_angle(angle=math.radians(12))
|
||||
obj.modifiers.new(
|
||||
type='WEIGHTED_NORMAL', name='weighted_normal').keep_sharp = True
|
||||
obj = mesh_to_mesh(obj)
|
||||
|
||||
obj.asset_mark()
|
||||
obj.asset_generate_preview()
|
||||
img = bpy.data.images.new(part['name'], *obj.preview.image_size, alpha=True)
|
||||
img.pixels.foreach_set(obj.preview.image_pixels_float)
|
||||
img_path = os.path.join(objects_dir, part['name'] + '.png')
|
||||
img.save(filepath=img_path)
|
||||
obj.asset_clear()
|
||||
|
||||
blend_path = os.path.join(objects_dir, part['name'] + '.blend')
|
||||
logger.info('Object stored as libs %s to %s.', part['name'], objects_dir)
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_path, compress=True)
|
||||
|
||||
libs_data.append(
|
||||
{
|
||||
'type': obj.rna_type.name.upper(),
|
||||
'name': part['name'],
|
||||
'path': os.path.relpath(blend_path, project_dir),
|
||||
'dae': os.path.relpath(
|
||||
dae.export(obj_name=part['name'], file_dir=objects_dir),
|
||||
project_dir),
|
||||
'fbx': os.path.relpath(
|
||||
fbx.export(obj_name=part['name'], file_dir=objects_dir,
|
||||
axis_forward='-Z', axis_up='Y'),
|
||||
project_dir),
|
||||
'glb': os.path.relpath(
|
||||
glb.export(obj_name=part['name'], file_dir=objects_dir),
|
||||
project_dir),
|
||||
'ply': os.path.relpath(
|
||||
ply.export(obj_name=part['name'], file_dir=objects_dir),
|
||||
project_dir),
|
||||
'thumbnail': os.path.relpath(img_path, project_dir),
|
||||
}
|
||||
)
|
||||
|
||||
# write db file
|
||||
libs_data_path = os.path.join(project_dir, data_name + '_libs_data.json')
|
||||
with open(libs_data_path, 'w', encoding='utf-8') as libs_data_file:
|
||||
json.dump(libs_data, libs_data_file, ensure_ascii=False, indent=4)
|
||||
logger.info('Database saved successfully to %s!', libs_data_path)
|
||||
|
||||
return libs_data_path
|
165
rcg_pipeline/rcg_pipeline/material_generators.py
Normal file
165
rcg_pipeline/rcg_pipeline/material_generators.py
Normal file
|
@ -0,0 +1,165 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2021-2024 Robossembler LLC
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This file is part of Robossembler Framework
|
||||
# project repo: https://gitlab.com/robossembler/framework
|
||||
#
|
||||
# Robossembler Framework is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Blender material generation functions.
|
||||
'''
|
||||
|
||||
__version__ = '1.0'
|
||||
|
||||
import configparser
|
||||
import logging
|
||||
import os
|
||||
|
||||
import bpy
|
||||
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
|
||||
|
||||
from .utils.shininess_to_roughness import shiny_to_rough
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def black_material(obj):
|
||||
''' Generate absolute black body shader '''
|
||||
mat_name = 'Robossembler_Black_Body'
|
||||
|
||||
if mat_name in bpy.data.materials:
|
||||
# assign material to object
|
||||
if len(obj.material_slots) < 1:
|
||||
obj.data.materials.append(bpy.data.materials[mat_name])
|
||||
else:
|
||||
obj.material_slots[0].material = bpy.data.materials[mat_name]
|
||||
else:
|
||||
bmat = bpy.data.materials.new(name=mat_name)
|
||||
bmat.use_nodes = True
|
||||
bmat.diffuse_color = (0, 0, 0, 1)
|
||||
principled = bmat.node_tree.nodes['Principled BSDF']
|
||||
principled.inputs['Base Color'].default_value = (0, 0, 0, 1)
|
||||
principled.inputs['Specular IOR Level'].default_value = 0.0
|
||||
principled.inputs['Roughness'].default_value = 1.0
|
||||
# assign material to object
|
||||
if len(obj.material_slots) < 1:
|
||||
obj.data.materials.append(bmat)
|
||||
else:
|
||||
obj.material_slots[0].material = bmat
|
||||
|
||||
logger.debug('Material was assigned to object: %s -> %s', bmat.name, obj.name)
|
||||
return bmat
|
||||
|
||||
|
||||
def material_generator(obj, material_path):
|
||||
''' Generate shader from FreeCAD's FEM material '''
|
||||
material = configparser.ConfigParser()
|
||||
with open(material_path, encoding='utf-8') as data:
|
||||
material.read_file(data)
|
||||
mat_name = os.path.splitext(os.path.basename(material_path))[0]
|
||||
if mat_name != material['General']['Name']:
|
||||
logger.warning(
|
||||
'Material %s is not supported! '
|
||||
'For Robossembler CG Pipeline material should have '
|
||||
'a same Name and FCMat filename!',
|
||||
mat_name)
|
||||
return black_material(obj)
|
||||
|
||||
if mat_name in bpy.data.materials:
|
||||
# assign material to object
|
||||
if len(obj.material_slots) < 1:
|
||||
obj.data.materials.append(bpy.data.materials[mat_name])
|
||||
else:
|
||||
obj.material_slots[0].material = bpy.data.materials[mat_name]
|
||||
|
||||
logger.debug('Material was assigned to object: %s -> %s', mat_name, obj.name)
|
||||
return obj
|
||||
|
||||
if 'Rendering' not in material:
|
||||
logger.warning(
|
||||
'Material %s is not supported! '
|
||||
'For Robossembler CG Pipeline material should have '
|
||||
'Rendering category with shading parameters!',
|
||||
mat_name)
|
||||
return black_material(obj)
|
||||
|
||||
rendering = material['Rendering']
|
||||
|
||||
if rendering.get('DiffuseColor'):
|
||||
d_col_str = rendering['DiffuseColor']
|
||||
d_col4 = tuple(map(float, d_col_str[1:-1].split(', ')))
|
||||
d_col = d_col4[:-1]
|
||||
else:
|
||||
logger.warning(
|
||||
'DiffuseColor not found for %s %s.', mat_name, obj.name)
|
||||
d_col = (1.0, 0.0, 0.0)
|
||||
if rendering.get('Father'):
|
||||
if rendering['Father'] == 'Metal':
|
||||
me = 1
|
||||
else:
|
||||
me = 0
|
||||
else:
|
||||
me = 0
|
||||
if rendering.get('Shininess'):
|
||||
shiny = float(rendering['Shininess'])
|
||||
if shiny == 0:
|
||||
rg = 0.5
|
||||
else:
|
||||
rg = shiny_to_rough(shiny)
|
||||
else:
|
||||
rg = 0.5
|
||||
if rendering.get('EmissiveColor'):
|
||||
e_col_str = rendering['EmissiveColor']
|
||||
e_col4 = tuple(map(float, e_col_str[1:-1].split(', ')))
|
||||
e_col = e_col4[:-1]
|
||||
else:
|
||||
e_col = (0.0, 0.0, 0.0)
|
||||
if rendering.get('Transparency'):
|
||||
tr_str = rendering['Transparency']
|
||||
alpha = 1.0 - float(tr_str)
|
||||
else:
|
||||
alpha = 1.0
|
||||
|
||||
bmat = bpy.data.materials.new(name=mat_name)
|
||||
bmat.use_nodes = True
|
||||
principled = PrincipledBSDFWrapper(bmat, is_readonly=False)
|
||||
principled.base_color = d_col
|
||||
principled.metallic = me
|
||||
principled.roughness = rg
|
||||
principled.emission_color = e_col
|
||||
principled.alpha = alpha
|
||||
bevel = bmat.node_tree.nodes.new(type="ShaderNodeBevel")
|
||||
bevel.location = -300, -300
|
||||
bevel.samples = 32
|
||||
bevel.inputs[0].default_value = 0.001
|
||||
principled_node = bmat.node_tree.nodes["Principled BSDF"]
|
||||
bmat.node_tree.links.new(bevel.outputs['Normal'], principled_node.inputs['Normal'])
|
||||
# assign material to object
|
||||
if len(obj.material_slots) < 1:
|
||||
obj.data.materials.append(bmat)
|
||||
else:
|
||||
obj.material_slots[0].material = bmat
|
||||
|
||||
logger.debug('Material was assigned to object: %s -> %s', bmat.name, obj.name)
|
||||
return bmat
|
238
rcg_pipeline/rcg_pipeline/render_asset.py
Normal file
238
rcg_pipeline/rcg_pipeline/render_asset.py
Normal file
|
@ -0,0 +1,238 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2021-2024 Robossembler LLC
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This file is part of Robossembler Framework
|
||||
# project repo: https://gitlab.com/robossembler/framework
|
||||
#
|
||||
# Robossembler Framework is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Generate render asset from assembly tree and CG libs database.
|
||||
'''
|
||||
|
||||
__version__ = '1.0'
|
||||
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import shutil
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector, Matrix
|
||||
|
||||
from .utils.collection_tools import remove_collections_with_objects
|
||||
from .utils.cleanup_orphan_data import cleanup_orphan_data
|
||||
from .utils.object_relations import unparenting, parenting
|
||||
from .utils.object_transforms import round_transforms
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def recursive_layer_collection(layer_coll, coll_name):
|
||||
found = None
|
||||
if layer_coll.name == coll_name:
|
||||
return layer_coll
|
||||
for layer in layer_coll.children:
|
||||
found = recursive_layer_collection(layer, coll_name)
|
||||
if found:
|
||||
return found
|
||||
return False
|
||||
|
||||
|
||||
def assembly_builder(item, libs_data, libs_data_dir, collection=None, parent=None):
|
||||
'''
|
||||
'''
|
||||
if not collection:
|
||||
collection = bpy.context.scene.collection
|
||||
item_obj = None
|
||||
if item['type'] == 'LOCATOR':
|
||||
item_obj = bpy.data.objects.new(item['name'], None)
|
||||
item_obj.empty_display_type = 'CUBE'
|
||||
item_obj.empty_display_size = 0.01
|
||||
collection.objects.link(item_obj)
|
||||
elif item['type'] == 'LCS':
|
||||
item_obj = bpy.data.objects.new(item['name'], None)
|
||||
item_obj.empty_display_type = 'ARROWS'
|
||||
item_obj.empty_display_size = round(random.uniform(0.05, 0.15), 3)
|
||||
item_obj.show_in_front = True
|
||||
collection.objects.link(item_obj)
|
||||
elif item['type'] == 'PART':
|
||||
# link clones
|
||||
if item.get('base_name'):
|
||||
item_data = [
|
||||
data for data in libs_data
|
||||
if data['type'] == 'OBJECT'
|
||||
if data['name'] == item['base_name']]
|
||||
if not item_data:
|
||||
logger.error('No %s in database!', item['name'])
|
||||
return False
|
||||
# if there is local base_named object in scene -> rename it
|
||||
local_obj = [
|
||||
loc for loc in bpy.data.objects
|
||||
if loc.name == item['base_name']
|
||||
if not loc.library]
|
||||
if local_obj:
|
||||
local_obj[0].name += '_loc'
|
||||
item_file_path = os.path.join(libs_data_dir, item_data[0]['path'])
|
||||
# TODO already linked
|
||||
bpy.ops.wm.link(
|
||||
filepath=item_file_path,
|
||||
directory=os.path.join(item_file_path, 'Collection'),
|
||||
filename=item['base_name'],
|
||||
relative_path=True,
|
||||
do_reuse_local_id=True,
|
||||
active_collection=True,
|
||||
)
|
||||
item_obj = bpy.data.objects[item['base_name']]
|
||||
item_obj.name = item['name']
|
||||
# rename local back
|
||||
if local_obj:
|
||||
local_obj[0].name = item['base_name']
|
||||
# link unical
|
||||
else:
|
||||
item_data = [
|
||||
data for data in libs_data
|
||||
if data['type'] == 'OBJECT'
|
||||
if data['name'] == item['name']]
|
||||
if not item_data:
|
||||
logger.error('No %s in database!', item['name'])
|
||||
return False
|
||||
item_file_path = os.path.join(libs_data_dir, item_data[0]['path'])
|
||||
bpy.ops.wm.link(
|
||||
filepath=item_file_path,
|
||||
directory=os.path.join(item_file_path, 'Collection'),
|
||||
filename=item['name'],
|
||||
relative_path=True,
|
||||
do_reuse_local_id=True,
|
||||
active_collection=True,
|
||||
)
|
||||
item_obj = bpy.data.objects[item['name']]
|
||||
item_obj.empty_display_type = 'PLAIN_AXES'
|
||||
item_obj.empty_display_size = 0.01
|
||||
else:
|
||||
logger.error('Unknown object type %s of %s', item['type'], item['name'])
|
||||
return False
|
||||
item_obj.location = Vector(item['pose'][0]['loc_xyz']) * 0.001
|
||||
item_obj.rotation_mode = 'QUATERNION'
|
||||
item_obj.rotation_quaternion = [
|
||||
item['pose'][1]['rot_xyzw'][3]] + item['pose'][1]['rot_xyzw'][:3]
|
||||
|
||||
if item.get('attributes'):
|
||||
for attr in item['attributes']:
|
||||
item_obj[list(attr)[0]] = attr[list(attr)[0]]
|
||||
|
||||
item_obj.parent = parent
|
||||
|
||||
if item.get('children'):
|
||||
for child_item in item.get('children'):
|
||||
assembly_builder(
|
||||
child_item, libs_data, libs_data_dir, collection, parent=item_obj)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def assembly_rebuilder():
|
||||
''' Restructure assembling hierarchy. '''
|
||||
default_origin = [
|
||||
lcs for lcs in bpy.data.objects
|
||||
if lcs.type == 'EMPTY'
|
||||
if lcs.get('Robossembler_SocketFlow') == 'inlet'
|
||||
if lcs.get('Robossembler_DefaultOrigin')]
|
||||
if not default_origin:
|
||||
raise Exception('Default Origin not found!')
|
||||
if len(default_origin) > 1:
|
||||
raise Exception('Several Default Origins do not supported!')
|
||||
default_origin = default_origin[0]
|
||||
|
||||
root_locator = [
|
||||
obj for obj in bpy.data.objects
|
||||
if not obj.parent
|
||||
if not obj.library]
|
||||
for i in root_locator:
|
||||
print(i.name)
|
||||
if len(root_locator) > 1:
|
||||
raise Exception('Render asset should consist of only one hierarchy!')
|
||||
root_locator = root_locator[0]
|
||||
|
||||
# retree_by lcs
|
||||
for lcs in bpy.data.objects:
|
||||
if lcs.type == 'EMPTY':
|
||||
if lcs.get('Robossembler_SocketFlow'):
|
||||
unparenting(lcs)
|
||||
round_transforms(lcs)
|
||||
|
||||
parenting(default_origin, root_locator)
|
||||
|
||||
for lcs in bpy.data.objects:
|
||||
if lcs.type == 'EMPTY':
|
||||
if lcs.get('Robossembler_SocketFlow'):
|
||||
if lcs != default_origin:
|
||||
parenting(default_origin, lcs)
|
||||
|
||||
default_origin.matrix_world = Matrix()
|
||||
|
||||
logger.info('Restructuring assembling hierarchy finished!')
|
||||
|
||||
return default_origin
|
||||
|
||||
|
||||
def build_render_asset(parts_tree_path, libs_data_path, project_dir):
|
||||
'''
|
||||
'''
|
||||
# start from stratch
|
||||
bpy.ops.wm.read_homefile()
|
||||
remove_collections_with_objects()
|
||||
cleanup_orphan_data()
|
||||
|
||||
with open(parts_tree_path, encoding='utf-8') as data:
|
||||
parts_tree_item = json.load(data)
|
||||
with open(libs_data_path, encoding='utf-8') as data:
|
||||
libs_data = json.load(data)
|
||||
|
||||
# create redner collection
|
||||
render_collection = bpy.data.collections.new(parts_tree_item['name'])
|
||||
bpy.context.scene.collection.children.link(render_collection)
|
||||
active_collection = recursive_layer_collection(
|
||||
bpy.context.view_layer.layer_collection,
|
||||
render_collection.name)
|
||||
bpy.context.view_layer.active_layer_collection = active_collection
|
||||
|
||||
# build original hierarchy
|
||||
assembly_builder(parts_tree_item, libs_data, project_dir, render_collection)
|
||||
# rebuild to LCS hierarchy
|
||||
assembly_rebuilder()
|
||||
|
||||
# render assets dir
|
||||
render_assets_dir = os.path.join(project_dir, 'assets', 'render')
|
||||
if os.path.exists(render_assets_dir):
|
||||
shutil.rmtree(render_assets_dir)
|
||||
os.makedirs(render_assets_dir)
|
||||
else:
|
||||
os.makedirs(render_assets_dir)
|
||||
|
||||
blend_path = os.path.join(render_assets_dir, parts_tree_item['name'] + '.blend')
|
||||
bpy.ops.wm.save_as_mainfile(filepath=blend_path)
|
||||
logger.info('Render asset %s generated!', parts_tree_item['name'])
|
||||
|
||||
return blend_path
|
28
rcg_pipeline/rcg_pipeline/utils/__init__.py
Normal file
28
rcg_pipeline/rcg_pipeline/utils/__init__.py
Normal file
|
@ -0,0 +1,28 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Blender utils.
|
||||
'''
|
67
rcg_pipeline/rcg_pipeline/utils/cleanup_orphan_data.py
Normal file
67
rcg_pipeline/rcg_pipeline/utils/cleanup_orphan_data.py
Normal file
|
@ -0,0 +1,67 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Blender scene from stratch.
|
||||
'''
|
||||
|
||||
__version__ = '0.1'
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def cleanup_orphan_data():
|
||||
'''Removes all data without users'''
|
||||
for block in bpy.data.meshes:
|
||||
if block.users == 0:
|
||||
bpy.data.meshes.remove(block)
|
||||
|
||||
for block in bpy.data.materials:
|
||||
if block.users == 0:
|
||||
bpy.data.materials.remove(block)
|
||||
|
||||
for block in bpy.data.textures:
|
||||
if block.users == 0:
|
||||
bpy.data.textures.remove(block)
|
||||
|
||||
for block in bpy.data.images:
|
||||
if block.users == 0:
|
||||
bpy.data.images.remove(block)
|
||||
|
||||
for block in bpy.data.collections:
|
||||
if block.users == 0:
|
||||
bpy.data.collections.remove(block)
|
||||
|
||||
for block in bpy.data.cameras:
|
||||
if block.users == 0:
|
||||
bpy.data.cameras.remove(block)
|
||||
|
||||
for block in bpy.data.lights:
|
||||
if block.users == 0:
|
||||
bpy.data.lights.remove(block)
|
||||
|
||||
for block in bpy.data.scenes:
|
||||
if block.users == 0:
|
||||
bpy.data.scenes.remove(block)
|
92
rcg_pipeline/rcg_pipeline/utils/collection_tools.py
Normal file
92
rcg_pipeline/rcg_pipeline/utils/collection_tools.py
Normal file
|
@ -0,0 +1,92 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Various collection tools.
|
||||
'''
|
||||
|
||||
__version__ = '0.1'
|
||||
|
||||
import bpy
|
||||
from collections import defaultdict
|
||||
|
||||
|
||||
def copy_objects(from_col, to_col, linked, double_lut):
|
||||
'''Function copying objects from collection to collection.'''
|
||||
for obj in from_col.objects:
|
||||
double = obj.copy()
|
||||
if not linked and obj.data:
|
||||
double.data = double.data.copy()
|
||||
to_col.objects.link(double)
|
||||
double_lut[obj] = double
|
||||
return True
|
||||
|
||||
|
||||
def copy_collections_recursive(collection, suffix='copy', linked=False):
|
||||
'''Function recursive copying collection.'''
|
||||
double_lut = defaultdict(lambda: None)
|
||||
parent = [p for p in (bpy.data.collections[:] + [bpy.context.scene.collection])
|
||||
if collection.name in p.children.keys()][0]
|
||||
|
||||
def _copy(parent, collection, suffix, linked=False):
|
||||
'''Function copying collection.'''
|
||||
clone_collection = bpy.data.collections.new(
|
||||
'_'.join((collection.name, suffix))
|
||||
)
|
||||
copy_objects(collection, clone_collection, linked, double_lut)
|
||||
|
||||
for _collection in collection.children:
|
||||
_copy(clone_collection, _collection, suffix, linked)
|
||||
|
||||
parent.children.link(clone_collection)
|
||||
|
||||
_copy(parent, collection, suffix, linked)
|
||||
for obj, double in tuple(double_lut.items()):
|
||||
parent = double_lut[obj.parent]
|
||||
if parent:
|
||||
double.parent = parent
|
||||
return '_'.join((collection.name, suffix))
|
||||
|
||||
|
||||
def unlink_from_collections(obj):
|
||||
''' Unlinking object from all collections. '''
|
||||
for col in bpy.data.collections:
|
||||
if obj.name in col.objects:
|
||||
col.objects.unlink(obj)
|
||||
return obj
|
||||
|
||||
|
||||
def remove_collections_with_objects(collection=None):
|
||||
'''Removes all collection (or given) with objects from scene '''
|
||||
if collection:
|
||||
for obj in collection.objects:
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
bpy.data.collections.remove(collection)
|
||||
else:
|
||||
for col in bpy.data.collections:
|
||||
for obj in col.objects:
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
bpy.data.collections.remove(col)
|
||||
return True
|
173
rcg_pipeline/rcg_pipeline/utils/generative_modifiers.py
Normal file
173
rcg_pipeline/rcg_pipeline/utils/generative_modifiers.py
Normal file
|
@ -0,0 +1,173 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Basic mesh processing for asset pipeline.
|
||||
DEPRECATED
|
||||
'''
|
||||
|
||||
__version__ = '0.1'
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def shell_remesher(lowpoly_obj, mod_name='shell_mod', tree_name='shell_tree'):
|
||||
''' Conctruct geometry nodes modifier. '''
|
||||
|
||||
modifier = lowpoly_obj.modifiers.new(mod_name, type='NODES')
|
||||
tree = bpy.data.node_groups.new(name=tree_name, type='GeometryNodeTree')
|
||||
modifier.node_group = tree
|
||||
|
||||
group_input = tree.nodes.new(type='NodeGroupInput')
|
||||
|
||||
collection_info = tree.nodes.new(type='GeometryNodeCollectionInfo')
|
||||
collection_info.location = (300, 0)
|
||||
collection_info.transform_space = 'RELATIVE'
|
||||
|
||||
tree.interface.new_socket(name='Collection', in_out='INPUT',
|
||||
socket_type='NodeSocketCollection')
|
||||
tree.links.new(group_input.outputs['Collection'],
|
||||
collection_info.inputs['Collection'])
|
||||
|
||||
realize_instances = tree.nodes.new(type='GeometryNodeRealizeInstances')
|
||||
realize_instances.location = (600, 0)
|
||||
|
||||
tree.links.new(collection_info.outputs[0],
|
||||
realize_instances.inputs['Geometry'])
|
||||
|
||||
mesh_to_volume = tree.nodes.new(type='GeometryNodeMeshToVolume')
|
||||
mesh_to_volume.location = (900, 0)
|
||||
mesh_to_volume.resolution_mode = 'VOXEL_SIZE'
|
||||
mesh_to_volume.inputs['Density'].default_value = 10.0
|
||||
mesh_to_volume.inputs['Voxel Size'].default_value = 0.005
|
||||
#mesh_to_volume.inputs['Exterior Band Width'].default_value = 0.005
|
||||
|
||||
tree.links.new(realize_instances.outputs['Geometry'],
|
||||
mesh_to_volume.inputs['Mesh'])
|
||||
|
||||
volume_to_mesh = tree.nodes.new(type='GeometryNodeVolumeToMesh')
|
||||
volume_to_mesh.location = (1200, 0)
|
||||
|
||||
tree.links.new(mesh_to_volume.outputs['Volume'],
|
||||
volume_to_mesh.inputs['Volume'])
|
||||
|
||||
extrude_mesh = tree.nodes.new(type='GeometryNodeExtrudeMesh')
|
||||
extrude_mesh.location = (1500, 0)
|
||||
extrude_mesh.inputs['Offset Scale'].default_value = 0.001
|
||||
extrude_mesh.inputs['Individual'].default_value = False
|
||||
|
||||
tree.links.new(volume_to_mesh.outputs['Mesh'],
|
||||
extrude_mesh.inputs['Mesh'])
|
||||
|
||||
# 1 pass
|
||||
mesh_to_volume = tree.nodes.new(type='GeometryNodeMeshToVolume')
|
||||
mesh_to_volume.location = (1800, 0)
|
||||
mesh_to_volume.resolution_mode = 'VOXEL_SIZE'
|
||||
mesh_to_volume.inputs['Density'].default_value = 1.0
|
||||
mesh_to_volume.inputs['Voxel Size'].default_value = 0.003
|
||||
#mesh_to_volume.inputs['Exterior Band Width'].default_value = 0.003
|
||||
|
||||
tree.links.new(extrude_mesh.outputs['Mesh'],
|
||||
mesh_to_volume.inputs['Mesh'])
|
||||
|
||||
volume_to_mesh = tree.nodes.new(type='GeometryNodeVolumeToMesh')
|
||||
volume_to_mesh.location = (2100, 0)
|
||||
|
||||
tree.links.new(mesh_to_volume.outputs['Volume'],
|
||||
volume_to_mesh.inputs['Volume'])
|
||||
|
||||
set_position_01 = tree.nodes.new(type='GeometryNodeSetPosition')
|
||||
set_position_01.location = (2400, -300)
|
||||
|
||||
tree.links.new(volume_to_mesh.outputs['Mesh'],
|
||||
set_position_01.inputs['Geometry'])
|
||||
|
||||
# 2 pass
|
||||
mesh_to_volume = tree.nodes.new(type='GeometryNodeMeshToVolume')
|
||||
mesh_to_volume.location = (2700, 0)
|
||||
mesh_to_volume.resolution_mode = 'VOXEL_SIZE'
|
||||
mesh_to_volume.inputs['Density'].default_value = 1.0
|
||||
mesh_to_volume.inputs['Voxel Size'].default_value = 0.001
|
||||
#mesh_to_volume.inputs['Exterior Band Width'].default_value = 0.001
|
||||
|
||||
tree.links.new(set_position_01.outputs['Geometry'],
|
||||
mesh_to_volume.inputs['Mesh'])
|
||||
|
||||
volume_to_mesh = tree.nodes.new(type='GeometryNodeVolumeToMesh')
|
||||
volume_to_mesh.location = (3000, 0)
|
||||
|
||||
tree.links.new(mesh_to_volume.outputs['Volume'],
|
||||
volume_to_mesh.inputs['Volume'])
|
||||
|
||||
set_position_02 = tree.nodes.new(type='GeometryNodeSetPosition')
|
||||
set_position_02.location = (3300, -300)
|
||||
|
||||
tree.links.new(volume_to_mesh.outputs['Mesh'],
|
||||
set_position_02.inputs['Geometry'])
|
||||
|
||||
# 3 pass
|
||||
mesh_to_volume = tree.nodes.new(type='GeometryNodeMeshToVolume')
|
||||
mesh_to_volume.location = (3600, 0)
|
||||
mesh_to_volume.resolution_mode = 'VOXEL_SIZE'
|
||||
mesh_to_volume.inputs['Density'].default_value = 1.0
|
||||
mesh_to_volume.inputs['Voxel Size'].default_value = 0.0005
|
||||
#mesh_to_volume.inputs['Exterior Band Width'].default_value = 0.0001
|
||||
|
||||
tree.links.new(set_position_02.outputs['Geometry'],
|
||||
mesh_to_volume.inputs['Mesh'])
|
||||
|
||||
volume_to_mesh = tree.nodes.new(type='GeometryNodeVolumeToMesh')
|
||||
volume_to_mesh.location = (3900, 0)
|
||||
|
||||
tree.links.new(mesh_to_volume.outputs['Volume'],
|
||||
volume_to_mesh.inputs['Volume'])
|
||||
|
||||
set_position_03 = tree.nodes.new(type='GeometryNodeSetPosition')
|
||||
set_position_03.location = (4200, -300)
|
||||
|
||||
tree.links.new(volume_to_mesh.outputs['Mesh'],
|
||||
set_position_03.inputs['Geometry'])
|
||||
|
||||
group_output = tree.nodes.new(type='NodeGroupOutput')
|
||||
group_output.location = (4500, 0)
|
||||
|
||||
tree.interface.new_socket(name='Geometry', in_out='OUTPUT',
|
||||
socket_type='NodeSocketGeometry')
|
||||
tree.links.new(set_position_03.outputs['Geometry'],
|
||||
group_output.inputs['Geometry'])
|
||||
|
||||
geometry_proximity = tree.nodes.new(type='GeometryNodeProximity')
|
||||
geometry_proximity.location = (1200, -1000)
|
||||
|
||||
tree.links.new(realize_instances.outputs['Geometry'],
|
||||
geometry_proximity.inputs['Target'])
|
||||
tree.links.new(geometry_proximity.outputs['Position'],
|
||||
set_position_01.inputs['Position'])
|
||||
tree.links.new(geometry_proximity.outputs['Position'],
|
||||
set_position_02.inputs['Position'])
|
||||
tree.links.new(geometry_proximity.outputs['Position'],
|
||||
set_position_03.inputs['Position'])
|
||||
|
||||
return modifier
|
118
rcg_pipeline/rcg_pipeline/utils/mesh_tools.py
Normal file
118
rcg_pipeline/rcg_pipeline/utils/mesh_tools.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Various mesh tools for Edit Mode.
|
||||
'''
|
||||
|
||||
__version__ = '0.1'
|
||||
|
||||
import math
|
||||
import bpy
|
||||
import bmesh
|
||||
|
||||
|
||||
def select_peaks(me, peak_limit_angle=60, peak_accuracy_angle=10):
|
||||
''' Select sharp vertices that stand alone. '''
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
|
||||
def is_sharp(vert, eps=math.radians(peak_limit_angle)):
|
||||
sharps = []
|
||||
face_before = None
|
||||
for face in vert.link_faces:
|
||||
if face_before:
|
||||
face_angle = face.normal.angle(face_before.normal)
|
||||
if face_angle > math.radians(peak_accuracy_angle):
|
||||
angle = vert.normal.angle(face.normal)
|
||||
if angle > eps:
|
||||
sharps.append(angle)
|
||||
face_before = face
|
||||
return (
|
||||
(len(sharps) + 1) == len(vert.link_faces)
|
||||
or (len(sharps) + 2) == len(vert.link_faces)
|
||||
)
|
||||
|
||||
def non_single(vert):
|
||||
for edge in vert.link_edges:
|
||||
if edge.other_vert(vert).select:
|
||||
return False
|
||||
return True
|
||||
|
||||
for v in bm.verts:
|
||||
v.select_set(
|
||||
is_sharp(v)
|
||||
)
|
||||
|
||||
for v in bm.verts:
|
||||
if v.select:
|
||||
v.select_set(
|
||||
non_single(v)
|
||||
)
|
||||
|
||||
bmesh.update_edit_mesh(me)
|
||||
return me
|
||||
|
||||
|
||||
def select_zero_faces(me):
|
||||
''' Select very small faces. '''
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
for myface in bm.faces:
|
||||
if myface.calc_area() < 1e-7:
|
||||
myface.select_set(True)
|
||||
bmesh.update_edit_mesh(me)
|
||||
return me
|
||||
|
||||
|
||||
def select_stratched_edges(me, edge_length_limit=0.002):
|
||||
''' Select very stratched edges of small faces. '''
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
faces_stratched = [f for f in bm.faces if f.calc_area() < 1e-6]
|
||||
for face in faces_stratched:
|
||||
edges_lengths = {e: e.calc_length() for e in face.edges}
|
||||
edge_max_length = max(edges_lengths.values())
|
||||
if edge_max_length > edge_length_limit:
|
||||
edge_max = [k for k, v in edges_lengths.items() if v == edge_max_length][0]
|
||||
edge_max.select_set(True)
|
||||
bmesh.update_edit_mesh(me)
|
||||
return me
|
||||
|
||||
|
||||
def collect_less_volume_objs(objs: list, min_volume):
|
||||
''' Separate selection for less volume objects. '''
|
||||
less_volume_objs = []
|
||||
for obj in objs:
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
if obj.type != 'MESH':
|
||||
continue
|
||||
# requed for bmesh
|
||||
obj.hide_set(False)
|
||||
obj.select_set(state=True)
|
||||
if obj.type == 'MESH':
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
bm = bmesh.from_edit_mesh(obj.data)
|
||||
if bm.calc_volume() < min_volume:
|
||||
less_volume_objs.append(obj)
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
return less_volume_objs
|
53
rcg_pipeline/rcg_pipeline/utils/object_converter.py
Normal file
53
rcg_pipeline/rcg_pipeline/utils/object_converter.py
Normal file
|
@ -0,0 +1,53 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Convert all deformers and modifiers of object to it's mesh.
|
||||
'''
|
||||
|
||||
__version__ = '0.1'
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def mesh_to_mesh(obj):
|
||||
''' Convert all deformers and modifiers of object to it's mesh. '''
|
||||
if obj and obj.type == 'MESH':
|
||||
deg = bpy.context.evaluated_depsgraph_get()
|
||||
eval_mesh = obj.evaluated_get(deg).data.copy()
|
||||
|
||||
orig_name = obj.name
|
||||
obj.name = '{}_temp'.format(orig_name)
|
||||
converted_obj = bpy.data.objects.new(orig_name, eval_mesh)
|
||||
converted_obj.matrix_world = obj.matrix_world
|
||||
|
||||
bpy.context.view_layer.update()
|
||||
converted_obj.matrix_world = obj.matrix_world.copy()
|
||||
|
||||
obj.users_collection[0].objects.link(converted_obj)
|
||||
|
||||
bpy.data.objects.remove(obj, do_unlink=True)
|
||||
|
||||
return converted_obj
|
50
rcg_pipeline/rcg_pipeline/utils/object_relations.py
Normal file
50
rcg_pipeline/rcg_pipeline/utils/object_relations.py
Normal file
|
@ -0,0 +1,50 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Blender object relations functions.
|
||||
'''
|
||||
|
||||
__version__ = '0.1'
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def parenting(parent, child):
|
||||
''' Parenting child object to parent object. '''
|
||||
child.parent = parent
|
||||
child.matrix_parent_inverse = parent.matrix_world.inverted()
|
||||
return child
|
||||
|
||||
|
||||
def unparenting(child):
|
||||
''' Unarenting child object from parent object. '''
|
||||
# update database
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
world_matrix = child.matrix_world.copy()
|
||||
child.parent = None
|
||||
child.matrix_world = world_matrix
|
||||
return child
|
88
rcg_pipeline/rcg_pipeline/utils/object_transforms.py
Normal file
88
rcg_pipeline/rcg_pipeline/utils/object_transforms.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Blender object transforms functions.
|
||||
'''
|
||||
|
||||
__version__ = '0.3'
|
||||
|
||||
from mathutils import Matrix, Vector, Quaternion
|
||||
import bpy
|
||||
|
||||
|
||||
def apply_transforms(obj, location=False, rotation=False, scale=False):
|
||||
''' bake local object transforms '''
|
||||
# original idea from https://github.com/machin3io/MACHIN3tools
|
||||
# update database
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
def get_loc_matrix(location):
|
||||
return Matrix.Translation(location)
|
||||
|
||||
def get_rot_matrix(rotation):
|
||||
return rotation.to_matrix().to_4x4()
|
||||
|
||||
def get_sca_matrix(scale):
|
||||
scene_scale_martix = Matrix()
|
||||
for i in range(3):
|
||||
scene_scale_martix[i][i] = scale[i]
|
||||
return scene_scale_martix
|
||||
|
||||
if location and rotation and scale:
|
||||
loc, rot, sca = obj.matrix_world.decompose()
|
||||
mesh_martix = get_loc_matrix(loc) @ get_rot_matrix(
|
||||
rot) @ get_sca_matrix(sca)
|
||||
obj.data.transform(mesh_martix)
|
||||
apply_matrix = get_loc_matrix(Vector.Fill(3, 0)) @ get_rot_matrix(Quaternion()) @ get_sca_matrix(Vector.Fill(3, 1))
|
||||
obj.matrix_world = apply_matrix
|
||||
else:
|
||||
if location:
|
||||
raise Exception(
|
||||
'Location only applies with all transformations (rotate and scale) together!'
|
||||
)
|
||||
if rotation:
|
||||
loc, rot, sca = obj.matrix_world.decompose()
|
||||
mesh_martix = get_rot_matrix(rot)
|
||||
obj.data.transform(mesh_martix)
|
||||
apply_matrix = get_loc_matrix(loc) @ get_rot_matrix(Quaternion()) @ get_sca_matrix(sca)
|
||||
obj.matrix_world = apply_matrix
|
||||
|
||||
if scale:
|
||||
loc, rot, sca = obj.matrix_world.decompose()
|
||||
mesh_martix = get_sca_matrix(sca)
|
||||
obj.data.transform(mesh_martix)
|
||||
apply_matrix = get_loc_matrix(loc) @ get_rot_matrix(rot) @ get_sca_matrix(Vector.Fill(3, 1))
|
||||
obj.matrix_world = apply_matrix
|
||||
|
||||
obj.rotation_mode = 'XYZ'
|
||||
return obj
|
||||
|
||||
|
||||
def round_transforms(obj):
|
||||
''' Geting location of object and round it. '''
|
||||
for idx, axis in enumerate(obj.location[:]):
|
||||
obj.location[idx] = round(axis, 5)
|
||||
return obj.location
|
42
rcg_pipeline/rcg_pipeline/utils/shininess_to_roughness.py
Normal file
42
rcg_pipeline/rcg_pipeline/utils/shininess_to_roughness.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# original alg from https://github.com/assimp/assimp/issues/4573
|
||||
# Copyright (C) 2024 Ilia Kurochkin <brothermechanic@yandex.com>
|
||||
#
|
||||
# Created by Ilia Kurochkin (brothermechanic)
|
||||
# contact: brothermechanic@yandex.com
|
||||
#
|
||||
# This is free software;
|
||||
# you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 3
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, see <https://www.gnu.org/licenses/>.
|
||||
#
|
||||
# ***** END GPL LICENSE BLOCK *****
|
||||
#
|
||||
# coding: utf-8
|
||||
'''
|
||||
DESCRIPTION.
|
||||
Math function.
|
||||
'''
|
||||
|
||||
__version__ = '0.1'
|
||||
|
||||
import math
|
||||
|
||||
|
||||
def shiny_to_rough(shininess):
|
||||
''' convert shiny to roughness '''
|
||||
a, b = -1.0, 2.0
|
||||
c = (shininess / 100.0) - 1.0
|
||||
D = math.pow(b, 2) - (4 * a * c)
|
||||
roughness = (-b + math.sqrt(D)) / (2 * a)
|
||||
return max(roughness, 0)
|
Loading…
Add table
Add a link
Reference in a new issue