Compare commits
143 Commits
v3.0-alpha
...
v4.0
Author | SHA1 | Date | |
---|---|---|---|
b1199f6a21 | |||
47a8d81ecd | |||
24ac07a3b3 | |||
b647d7256f | |||
421f01b3db | |||
f215d3487f | |||
1661f82a07 | |||
512e7e868b | |||
427bad4f6b | |||
209d212287 | |||
2271b0a621 | |||
6940428b88 | |||
aa602a7bb8 | |||
8588f097a2 | |||
5d8ffb7e48 | |||
270fddff52 | |||
084e7fbe61 | |||
190be6ec61 | |||
36e925101e | |||
84e7e8380f | |||
c58af8ce48 | |||
07298fd21c | |||
3a1a0fb0f6 | |||
3396947115 | |||
6cf2ab895d | |||
b039dd8b43 | |||
8bad1a487c | |||
0f18559d3f | |||
5a5053440c | |||
02a1222210 | |||
f8c344f65e | |||
0bec108dcb | |||
997839a187 | |||
da71d5560c | |||
02082bf99e | |||
c82c094519 | |||
8ec101e1f1 | |||
8499c25b67 | |||
200ac40648 | |||
25091a48a7 | |||
259f99ddf8 | |||
f123bdacc0 | |||
8f27fec3c8 | |||
0d0d4db2db | |||
bfbdf5c99b | |||
50b2cf2cf1 | |||
013096459a | |||
31aa5c3127 | |||
f9502fe2d4 | |||
b5565d796a | |||
6b31401240 | |||
2c006b4528 | |||
680f367a42 | |||
318d661ac1 | |||
321e1b6fe9 | |||
20aba6c273 | |||
c5ce6e8a2c | |||
9315ff723d | |||
50b7eb0bce | |||
03e8feac67 | |||
35f2c4a389 | |||
6bc3933291 | |||
03441c642b | |||
77b15a8797 | |||
94872957fd | |||
a6de69c882 | |||
52f3936e42 | |||
2f123e6a3c | |||
f6569313bf | |||
ebb22c9ec1 | |||
ae9a848864 | |||
ca7e047c09 | |||
5c34bbad38 | |||
70dd525315 | |||
3025fcf305 | |||
52452a96d5 | |||
84b3ace13d | |||
7116d7198a | |||
6944415912 | |||
88fe1519e3 | |||
5bb3abed20 | |||
e79982c43f | |||
8e2d7a4133 | |||
cb80fa8b03 | |||
e84c1148f3 | |||
1432c2990a | |||
1a2dd08092 | |||
59a1275f68 | |||
d128ffcde5 | |||
fd17d3b5c9 | |||
1f0444009f | |||
bd40e0fdf2 | |||
6991307b8b | |||
b7ad5c67d4 | |||
5bbaa895eb | |||
1299a14f3b | |||
8fad643dd9 | |||
ec0749f3e5 | |||
6e4b1b37da | |||
1eaaedfcbd | |||
c1f92c7244 | |||
2896602e6a | |||
9079bf1bb3 | |||
f7dd5f32ba | |||
b4bb85cc71 | |||
bcbe9c987e | |||
16d4fc2bca | |||
4f10b1a9e9 | |||
484a4101ad | |||
ea6cb430fe | |||
354b3cd180 | |||
1d1de08bd7 | |||
21345b8251 | |||
b94e7e2b22 | |||
5584bc66a0 | |||
4f693b555d | |||
4733295a39 | |||
d9b9531828 | |||
1db6cab8ca | |||
8de1adda04 | |||
f07cfc91d5 | |||
0a3b3c5862 | |||
0d1e382b37 | |||
12d5f8c236 | |||
6fe856fa8e | |||
c2a85a2d86 | |||
dafb679780 | |||
f3663a4280 | |||
9c8d365ab6 | |||
0be036fcea | |||
e153e51abd | |||
d292ce389a | |||
807e006245 | |||
8d7a982e50 | |||
ddf6b7befe | |||
a300ddbb49 | |||
c9e51c9b6a | |||
b58f837a94 | |||
5fe865c621 | |||
9b9fc9cde8 | |||
ef459a210d | |||
7680d11c0e | |||
e7376a3e9c |
9
.gitattributes
vendored
9
.gitattributes
vendored
@ -1,6 +1,7 @@
|
||||
# all png are binary
|
||||
*.png binary
|
||||
# our generated mesh should be save as binary
|
||||
*.bin binary
|
||||
# json is data and not good for human reading(althought I edit it on my own hand.)
|
||||
# so set it as binary
|
||||
ballance_blender_plugin/json/basic_blocks/*.json binary
|
||||
ballance_blender_plugin/json/derived_blocks/*.json binary
|
||||
# the raw json data should be binary
|
||||
# although i edit it manually
|
||||
bbp_ng/raw_jsons/*.json binary
|
||||
|
27
.github/workflows/main.yml
vendored
Normal file
27
.github/workflows/main.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
name: Publish docs via GitHub Pages
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Deploy docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: master
|
||||
|
||||
- name: Install MkDocs
|
||||
run: |
|
||||
echo 'mkdocs' > requirements.txt
|
||||
echo 'pymdown-extensions' >> requirements.txt
|
||||
|
||||
- name: Deploy docs
|
||||
uses: mhausenblas/mkdocs-deploy-gh-pages@nomaterial
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
CONFIG_FILE: docs/mkdocs.yml
|
||||
# GITHUB_DOMAIN: github.myenterprise.com
|
140
.gitignore
vendored
140
.gitignore
vendored
@ -1,138 +1,2 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
# disable distribution build folder
|
||||
redist/
|
||||
|
175
README.md
175
README.md
@ -1,176 +1,7 @@
|
||||
# Ballance Blender Helper
|
||||
# BBP NG
|
||||
|
||||
[中文版本](README_ZH.md)
|
||||
|
||||
## Brief Introduction
|
||||
|
||||
This is a Blender plugin which is served for Ballance mapping in Blender.
|
||||
The latest commit may not be stable to use, please use the latest commit with git tag as the stable version.
|
||||
This plugin contain various aspect of Ballance mapping. However, if some features can be easily gotten from other Blender plugin, this plugin will not provide them duplicatedly. We highly recommend that use this plugin with following plugins.
|
||||
|
||||
* [BenjaminSauder/SimpleLattice](https://github.com/BenjaminSauder/SimpleLattice): Create lattice quickly to transform object.
|
||||
* [egtwobits/Mesh Align Plus](https://github.com/egtwobits/mesh_mesh_align_plus): Provide powerful align functions which far beyond vanilla Blender align function.
|
||||
|
||||
## Technical Infomation
|
||||
|
||||
Used BM file spec can be found in [there](https://github.com/yyc12345/gist/blob/master/BMFileSpec/BMSpec_ZH.md)(Chinese only).
|
||||
Used tools chain principle and the file format located in `meshes` can be found in [there](https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md)(Chinese only).
|
||||
The format of the files which are under the `jsons` folder and belong to the BMERevenge section, can be found in [here](https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_ZH.md)
|
||||
|
||||
This plugin will continuously support Blender lastest **LTS** version. This plugin will migrate to new version when the new LTS version released. Currently, it based on Blender **3.3.x**.
|
||||
|
||||
## Installation
|
||||
|
||||
Put `ballance_blender_plugin` into Blender's plugin folder, `scripts/addons_contrib`. Then enable this plugin in Blender's preferences (DO NOT forget to configure this plugin's settings after first installation or updating plugin.).
|
||||
|
||||
## Feature Introduction
|
||||
|
||||
### Plugin Settings
|
||||
|
||||
* External Texture Folder: Please fill in the `Texture` directory of Ballance, the plugin will refer the external texture file from this directory (the texture file originally with Ballance)
|
||||
* No Component Collection: Objects located in this collection will be forced to be set as non-component. If left blank, this function will be shutdown. This function is frequently used in forced component replacement.
|
||||
* Temp Texture Folder: used to cache texture files extracted from BM files. Please arrange a directory that will not be automatically cleaned up. Since Blender will continue to read texture files from this directory, it can not be emptied casually.
|
||||
|
||||
Temp Texture Folder does not allow files with duplicated name. Because of this, imagine this situation, there are two texture files with the same name in two BM files, but they represent different images. When you import them one by one for different maps. The later file will overwrite the previous file, And this will cause a texture error when the first Blender document was opened again. For the solution of this issue, the best way is to force packaging once. After successfully importing the BM, click `File - External Data - Pack Resources`, then you can clear Temp Texture Folder safely. With your preference, you also can click `File - External Data - Unpack Resources` to extract textures. This operation will extract and re-refer all textures into standalone texture folder within the folder where this Blender document is.
|
||||
|
||||
### BM Import / Export
|
||||
|
||||
Click `File - Import - Ballance Map` to import BM file.
|
||||
When name conflicts occur during importing BM, you have ability to choose different strategies for 4 different data types, Texture, Material, Mesh and Object. You can specify them to create a new instance or use current data block.
|
||||
|
||||
Click `File - Export - Ballance Map` to export a BM file.
|
||||
You can export a collection or an object (Export Mode), and specify target (Export Target) correspondingly.
|
||||
Although plugin provide Virtools Group feature and give you ability to grouping object in Blender. The export function also depend on Tools Chain Principle. Because of this, if you do not follow Tools Chain Principle, some convenient features will be disabled, for example, your exported BM file may larger than common one.
|
||||
|
||||
It should be noted that once the BM is exported, all the faces in the file will be converted to triangular faces, please make a backup in advance.
|
||||
It is recommended to use a flat collection structure, do not put a collection within another collection, which may cause some unnecessary problems.
|
||||
The suffix name of BM file is BMX, X stands for compression. BMX and BM is the same thing.
|
||||
|
||||
### Ballance 3D
|
||||
|
||||
Ballance 3D is a set of light tools related to 3D operations, which can be found in the upper left corner menu bar of 3D View. This menu is named as Ballance.
|
||||
|
||||
#### 3ds Max Align
|
||||
|
||||
Provide 3ds Max like align tools. Current active object will be seen as reference object. All selected objects (except active object) will be seen as operating object (So you can select multiple objects to align to a single object).
|
||||
|
||||
#### Create Rail UV
|
||||
|
||||
To create UVs for the rails in the map, you need to select the objects that need to add UVs similar to the rails, and then click this button to create.
|
||||
In the dialog, you can select the material to be used. You can also choose the unfolding mode. In some unfolding modes, projection axis and zoom ratio options is available. Although Ballance will process all rail UV in game internally, it is essential that give a perfect UV in designer.
|
||||
If you want that the rail have in-game UV (it represent as smooth texture), you can choose `TT_ReflectionMapping` unfolding mode. This mode is written with the reverse work of game used function. This unfolding mode may be useful when you creating advertisement image in Blender for your map.
|
||||
|
||||
#### Flatten UV
|
||||
|
||||
In the object editing mode, it is a operator which is used to attach the currently selected surface to the UV. And you can specific the edge which will be attached into the V axis.
|
||||
Note that only convex face is supported. Applying this for a concave face will cause undefined behavior.
|
||||
|
||||
In the edit mode, select the surface, click Flatten UV, and then scroll the slider to select an edge as a reference.
|
||||
If the generated UV is not attached correctly, such as the FloorSide's band is pasted to the bottom, you can reselect the reference edge and redo the operation until it is correct.
|
||||
|
||||
### Quick Struct Adder
|
||||
|
||||
In the add menu, we have added a set of commonly used objects. After adding, the object will move to the 3D cursor.
|
||||
|
||||
#### Elements
|
||||
|
||||
Add elements, you can also specify attributes such as section when adding (it will not be displayed for unique objects such as start point)
|
||||
|
||||
#### Rail section
|
||||
|
||||
Add rail section, you can choose monorail or rail (just decide the number of rail section loops added, and will not help you rotate the angle), as well as rail radius and rail span (default value is standard value).
|
||||
|
||||
#### Floors
|
||||
|
||||
A powerful floor adder feature belong to the extension of BMERevenge project.
|
||||
In menu, Basic Floor is basic floor component. Derived Floor is consisted by basic floor components. Commonly, frequently used models are located in Derived Floor section.
|
||||
After selecting a floor type, you can assign 2 expand value at most, according to its property. You also can use options to decide whether side faces and bottom face can be generated.
|
||||
Comparing with trditional Ballance Map Editor, this function can massively reduce useless vertices.
|
||||
|
||||
The floor type can be simply grouped as Flat Floor, Sink Floor, Wide Floor and Platform.
|
||||
Additionally, Trafo Block and Transition between Flat Floor and Sink Floor are available.
|
||||
|
||||
It is recommended to merge the vertices by distance, unless you need do some special work.
|
||||
|
||||
### Virtools Group
|
||||
|
||||
Plugin add a new property for each Blender objects, called Virtools Group. It takes the same functionality of Group in Virtools.
|
||||
Select an object, You can find `Virtools Group` panel in `Object Properties` panel.
|
||||
Click Add or Delete icon to group or ungroup for object.
|
||||
Double click item in list to rename it.
|
||||
|
||||
After click Add button, you can choose Predefined option, and select a name from all legal Ballance used group names.
|
||||
Or, choose Custom option and write your own group name.
|
||||
|
||||
### Virtools Material
|
||||
|
||||
Plugin add a new property for each Blender materials, called Virtools Material. It create a bridge between Virtools Material and Blender Material.
|
||||
Navigate to `Material Properties` panel, select a material, you can find `Virtools Material` panel.
|
||||
In default, user created material will not enable Virtools Material feature. You need to click checkbox of `Virtools Material` panel to enable or disable it.
|
||||
|
||||
After enable Virtools Material, `Basic Parameters` section and `Advanced Parameters` section can be set. Set your material peroperties just like operating in Virtools.
|
||||
Just like its name, `Basic Parameters` is basic material properties. `Advanced Parameters` is mainly related to transparent properties and usually used in the bottom of transparent column.
|
||||
|
||||
In `Operation` section, `Apply Virtools Material` will clean all existed Blender material and create a new material graph according to Virtools material properties.
|
||||
And, `Parse from Blender Principled BSDF` will try parsing a Principled BSDF to Virtools material.
|
||||
If your material highly rely on Blender material, please execute `Parse from Blender Principled BSDF` or disable Virtools Material feature before exporting BM file, otherwise material can not be saved correctly.
|
||||
|
||||
### Select by Group
|
||||
|
||||
Plugin add 2 selection functions according to Virtools Group in Select menu.
|
||||
|
||||
#### Select by Virtools Group
|
||||
|
||||
Select objects in active collection according to its Virtools Group properties.
|
||||
The hidden object also can be selected if you check `Ignore Hide Property`.
|
||||
Check `Merge Selection` will merge current selection and previous selection.
|
||||
|
||||
#### Filter by Virtools Group
|
||||
|
||||
Filter current selected object by its Virtools Group properties.
|
||||
Check `Reverse` remove objects matching the requirements, not keep them.
|
||||
|
||||
### Quick Grouping
|
||||
|
||||
Plugin add quick grouping menu in 2 places.
|
||||
You can select various objects, right click and find quick grouping menu in Object Context menu.
|
||||
Also you can pick objects in Outline View and right click them, find quick grouping menu in Object menu.
|
||||
|
||||
#### Group into
|
||||
|
||||
Group selected objects into your specified group.
|
||||
|
||||
#### Ungroup from
|
||||
|
||||
Ungroup selected objects from your specified group.
|
||||
|
||||
#### Clear Grouping
|
||||
|
||||
Clean the grouping infomation for selected objects.
|
||||
|
||||
### Auto Grouping & Rename
|
||||
|
||||
In Outline View, you can find auto grouping and rename menu via right click any collection.
|
||||
|
||||
This plugin now support 2 name standard.
|
||||
First one has been introduced in Technical Infomation chapter. In plugin, its name is `YYC Tools Chains`.
|
||||
The second one is used by [Imengyu/Ballance](https://github.com/imengyu/Ballance). In plugin, its name is `Imengyu Ballance`.
|
||||
|
||||
All functions within this menu will only output a summary when finishing. If you want to check out some objects in detail, please click `Window - Switch System Terminal`. Plugin output a detailed report in that place.
|
||||
|
||||
#### Rename by Group
|
||||
|
||||
Rename object with proper name according to its Virtools Group properties.
|
||||
This usually use when migrating original map. Some Ballance derived applications do not have Group concept. They rely on name to get group infomations.
|
||||
|
||||
#### Convert Name
|
||||
|
||||
Convert name between different name standard.
|
||||
Frequently used in convertion between 2 different Ballance derived applications.
|
||||
|
||||
#### Auto Grouping
|
||||
|
||||
Auto grouping according to specified name standard.
|
||||
Please pay attention that previous grouping infomations will be overwritten.
|
||||
If you following some mapping standard during all mapping stages, this function will auto grouping all objects for you.
|
||||
BBP NG, abbr **B**allance **B**lender **P**lugin **N**ext **G**eneration.
|
||||
|
||||
For an introduction to this plugin, installing it, compiling it, reporting bugs, etc., see the GitHub Page of this project: https://yyc12345.github.io/BallanceBlenderHelper
|
||||
|
178
README_ZH.md
178
README_ZH.md
@ -1,179 +1,7 @@
|
||||
# Ballance Blender Helper
|
||||
# BBP NG
|
||||
|
||||
[English version](README.md)
|
||||
|
||||
## 简介
|
||||
|
||||
这是一个用于Blender的插件,其主要是服务于Ballance制图。
|
||||
请选择打了tag的最新commit使用。最新的commit不能保证其是稳定可用的。
|
||||
本插件囊括了Ballance制图中可能会用到的各种功能。对于一些其它插件可以提供的功能,本插件不再重复提供。建议与下列插件合用以取得更好制图效果:
|
||||
|
||||
* [BenjaminSauder/SimpleLattice](https://github.com/BenjaminSauder/SimpleLattice):快速创建晶格以便变形物体。
|
||||
* [egtwobits/Mesh Align Plus](https://github.com/egtwobits/mesh_mesh_align_plus):提供远超Blender原生的对齐功能。
|
||||
|
||||
## 技术信息
|
||||
|
||||
使用的BM文件标准可以在[这里](https://github.com/yyc12345/gist/blob/master/BMFileSpec/BMSpec_ZH.md)查找。
|
||||
使用的制图链标准以及`meshes`文件夹下的文件的格式可以在[这里](https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md)查找
|
||||
`jsons`文件夹下的,隶属于BMERevenge部分的文件的格式可以在[这里](https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_ZH.md)查找
|
||||
|
||||
支持Blender的原则是支持当前最新的 **LTS** 版本,在最新的LTS版本释出之后会花一些时间迁移插件。当前插件基于**3.3.x**版本
|
||||
|
||||
## 安装
|
||||
|
||||
将`ballance_blender_plugin`直接复制到Blender插件目录`scripts/addons_contrib`内即可。然后在Blender偏好设置中启用即可(请在第一次安装后或更新插件后配置插件设置)。
|
||||
|
||||
## 功能介绍
|
||||
|
||||
### 插件设置
|
||||
|
||||
* External texture folder:请填写为Ballance的`Texture`目录,插件将从此目录下调用外置贴图文件(即Ballance原本带有的贴图文件)
|
||||
* No component collection:处于此集合中的物体将被强制指定为非Component。如果留空则表示不需要这个功能。此功能通常用于机关模型强制替换。
|
||||
* Temp texture folder:用于缓存从BM文件中提取的贴图文件,请安排一个平时不会被自动清理的目录。由于Blender会持续从这个目录读取贴图文件,因此不能随意清空。
|
||||
|
||||
Temp texture folder不允许同名文件存在,即如果我为2个地图分别导入两个BM,这两个BM中存在贴图文件名相同但图像不同的两个文件,那么后来的文件将会覆盖前面的文件,并进而导致前者导入后的文档再次打开时出现贴图错误。关于这个问题的解决方案,最好的方法是强制打包一次。在导入BM成功之后,选择`文件-外部数据-打包资源`,然后就可以安全清空Temp texture folder所在目录或导入新BM文件。如果有需要可以再点击`文件-外部数据-解包资源`,将贴图重新依赖到工程文件夹下的独立贴图库内。
|
||||
|
||||
### BM导入导出
|
||||
|
||||
点击`文件-导入-Ballance Map`以导入BM文件。
|
||||
在导入发生名称冲突时,可以对贴图,材质,网格,物体这四种类型的数据分别决定是使用现有数据还是创建新的数据。
|
||||
|
||||
点击`文件-导出-Ballance Map`以导出BM文件。
|
||||
可以选择导出一个集合或者是一个物体(Export mode),并给定对象(Export target)即可。
|
||||
尽管插件提供了Virtools组功能,让你可以直接在Blender中归组完毕,但BM导出功能仍然受限于制图链标准。因此如果不按照制图链标准进行命名,那么在导出过程中则无法享受一些便利性功能,例如最终导出的文件可能会过大等。
|
||||
|
||||
一旦导出BM,文件中所有的面将全部转换为三角形面,请提前做好备份。
|
||||
在导出时,建议使用平铺的集合结构,不要在集合内嵌套集合,因为这样可能会导致一些不必要的问题。
|
||||
BM文件的后缀名是BMX,表示BM的压缩。BMX与BM为同一含义。
|
||||
|
||||
### Ballance 3D
|
||||
|
||||
Ballance 3D是一套简单的用于制图3D相关的轻型工具集合,可以在3D视图左上角菜单栏中找到,菜单名称为Ballance。
|
||||
|
||||
#### 3ds Max Align
|
||||
|
||||
提供一种类似于3ds Max的对齐方式。当前活动物体将被设为参照对象,当前选中的所有物体(如果参照也被选中则去掉参照对象)将被视为操作对象(因此可以选择多个物体一起对齐到参照对象)。
|
||||
|
||||
#### Create Rail UV
|
||||
|
||||
为地图中的钢轨创建UV,你需要先选中需要添加类似钢轨UV的物体,然后点击这个按钮以创建。
|
||||
在弹出设置窗口中,可以选择使用的材质。还可以选择展开模式,在部分展开模式下,还可以选择投影轴和缩放大小。尽管Ballance最终会为所有钢轨重新上UV,一个在界面中看着赏心悦目的钢轨贴图还是比较重要的。
|
||||
如果您需要在Blender中呈现游戏内钢轨的贴图效果(表现为所谓的平滑贴图),您可以选择`TT_ReflectionMapping`展开模式。此功能由逆向游戏所用函数得来。这在渲染地图宣传画时可能会很有用。
|
||||
|
||||
#### Flatten UV
|
||||
|
||||
在物体编辑模式下,用于将当前选中面按某一边贴附到V轴上的模式,展开到UV上。
|
||||
此功能只支持凸多边形面,对于凹多边形面会有未定义行为。
|
||||
|
||||
编辑模式下,选中面,点击Flatten UV,然后选中一个边作为参考。
|
||||
如果最后生成的边贴附不对,比如把路面花纹贴到了下部,可以重新选择参考边再进行操作,直到正确为止。
|
||||
|
||||
### 快速添加结构
|
||||
|
||||
在添加菜单中我们添加了一系列较为常用的物体。添加后物体会移动到3D游标处。
|
||||
|
||||
#### Elements
|
||||
|
||||
添加机关,添加时还可以指定添加的小节等属性(对于飞船等唯一物体不会显示)
|
||||
|
||||
#### Rail section
|
||||
|
||||
添加钢轨截面,可以选择单轨还是双轨(只是决定添加的界面数量,并不会帮你旋转角度),以及轨道半径和轨道间距(默认值就是标准数据)。
|
||||
|
||||
#### Floors
|
||||
|
||||
一个非常强大的添加路面功能,隶属于BMERevenge工程的拓展。
|
||||
菜单中的Basic floor是基本的路面组件,而Derived floor则是由基本组件组成的常用组件。通常而言,大部分需要的路面都在Derived floor中。
|
||||
在选择一个路面后,可以根据其本身属性,设置最多2个延展方向的数值。此外还可以控制侧面和底面是否生成。
|
||||
与Ballance Map Editor相比,还具有减少大量无用顶点的优势。
|
||||
|
||||
可添加的路面大致分为平路面,凹路面,宽路面以及各类平台。
|
||||
此外还有变球器底座,平凹转换路面可供添加。
|
||||
|
||||
建议添加后除有特殊需求外,应该立即按距离合并顶点一次以避免各类问题。
|
||||
|
||||
### Virtools组
|
||||
|
||||
插件为每一个Blender物体添加了新的属性,被称为Virtools Group。与Virtools中的组具有相同的功能。
|
||||
选择一个物体,在`物体属性`面板可以找到`Virtools Group`面板。
|
||||
可以点击添加与删除图标,为物体归组和取消归组。
|
||||
亦可在列表中双击修改组名。
|
||||
|
||||
在点击添加按钮后,可以选择预定义,然后从所有合法的Ballance组名中选择一个添加。
|
||||
或选择自定义,然后输入你想要的组名添加。
|
||||
|
||||
### Virtools材质
|
||||
|
||||
插件为每一个Blender材质添加了新的属性,被称为Virtools Material。它在Virtools材质与Blender材质之间架起沟通的桥梁。
|
||||
转到`材质属性`面板,选择一个材质,即可以找到`Virtools Material`面板。
|
||||
默认情况下,由用户创建的材质不启用Virtools Material,您可以通过点击`Virtools Material`面板的复选框来启用或关闭它。
|
||||
|
||||
在启用Virtools Material后,可以在`Basic Parameters`和`Advanced Parameters`中设置材质属性,就像在Virtools中操作一般。
|
||||
`Basic Parameters`是基础材质属性。`Advanced Parameters`则是与透明相关的材质属性,主要用于半透明柱子底部等。
|
||||
|
||||
`Operation`中的`Apply Virtools Material`将把Virtools Material应用到Blender材质上。
|
||||
而`Parse from Blender Principled BSDF`将尝试将一个原理化BSDF转换为Virtools材质数据。
|
||||
如果您是从Blender材质编辑的,请务必对此材质在导出前执行`Parse from Blender Principled BSDF`,或关闭Virtools Material功能,否则材质将无法正确保存。
|
||||
|
||||
### 按组选择
|
||||
|
||||
选择菜单中新增了两项按照Virtools归组数据进行筛选的功能。
|
||||
|
||||
#### Select by Virtools Group
|
||||
|
||||
将对当前活动集合内的物体按照其Virtools Group属性进行选择。
|
||||
勾选`Ignore Hide Property`后,即使是隐藏的物体,也会被筛选。
|
||||
勾选`Merge Selection`,将会把选中的物体合并到当前选定的内容中。
|
||||
|
||||
#### Filter by Virtools Group
|
||||
|
||||
将会按照Virtools Group属性,过滤当前选中物体。
|
||||
勾选`Reverse`将会反向操作,即去除掉符合条件的物体。
|
||||
|
||||
如果可以,请尽可能使用`Filter by Virtools Group`而不是`Select by Virtools Group`。因为这样可以避免分析过多的物体。
|
||||
例如先选定一个大致的范围,然后使用`Filter by Virtools Group`过滤,比直接使用`Select by Virtools Group`效率更高。
|
||||
|
||||
### 快速归组
|
||||
|
||||
插件在2个地方添加了为物体快速归组的功能。
|
||||
可以选择一系列物体,然后右键,在物体上下文菜单中找到快速归组功能。
|
||||
也可以在大纲窗口中,右键选择的物体,找到快速归组功能。
|
||||
|
||||
#### Group into
|
||||
|
||||
把选择物体归入你选择的组。
|
||||
|
||||
#### Ungroup from
|
||||
|
||||
把选择物体从你选择的组中取消归组。
|
||||
|
||||
#### Clear Grouping
|
||||
|
||||
清空选择物体的所有归组信息。
|
||||
|
||||
### 自动归组与重命名
|
||||
|
||||
在大纲视图中,对任意集合右键,可以得到自动归组与重命名菜单。
|
||||
|
||||
本插件目前支持两种命名标准。
|
||||
其一为技术信息章节已经阐述的制图链标准,在本插件中的名称为`YYC Tools Chains`。
|
||||
其二为[Imengyu/Ballance](https://github.com/imengyu/Ballance)所用命名标准,在本插件中的名称为`Imengyu Ballance`。
|
||||
|
||||
这些功能最终只会展示成功与否的一个概括性消息。如果您需要详细查看某个物体为什么不能转换,请点击`窗口-切换系统控制台`,插件在那里有更详细的输出。
|
||||
|
||||
#### Rename by Group
|
||||
|
||||
根据当前物体的归组信息,为其重命名为合适的名称。
|
||||
这通常用在迁移原版地图的过程中。一些Ballance衍生程序没有Virtools组概念,因此需要依赖名称来取得归组信息。
|
||||
|
||||
#### Convert Name
|
||||
|
||||
在不同命名标准之间切换。
|
||||
通常用于在不同Ballance衍生程序中进行转换。
|
||||
|
||||
#### Auto Grouping
|
||||
|
||||
根据给定的命名标准,为物体自动填充归组信息。
|
||||
需要注意的是,原有的归组信息会被覆盖。
|
||||
在制图过程中,如果你遵守了某些命名标准,则此功能可以为你自动完成归组功能。
|
||||
BBP NG,又名**B**allance **B**lender **P**lugin **N**ext **G**eneration(下一代Ballance Blender插件)。
|
||||
|
||||
有关此插件的介绍,安装,编译,汇报错误等,请参阅本项目的GitHub Page页面:https://yyc12345.github.io/BallanceBlenderHelper
|
||||
|
@ -1,343 +0,0 @@
|
||||
import bpy,bmesh,bpy_extras,mathutils
|
||||
import pathlib,zipfile,time,os,tempfile,math
|
||||
import struct, shutil
|
||||
from bpy_extras import io_utils, node_shader_utils
|
||||
from . import UTILS_constants, UTILS_functions, UTILS_file_io, UTILS_zip_helper, UTILS_virtools_prop
|
||||
|
||||
class BALLANCE_OT_export_bm(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
|
||||
"""Save a Ballance Map File (BM file spec 1.4)"""
|
||||
bl_idname = "ballance.export_bm"
|
||||
bl_label = 'Export BM'
|
||||
bl_options = {'PRESET'}
|
||||
|
||||
# ExportHelper mixin class uses this
|
||||
filename_ext = ".bmx"
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default="*.bmx",
|
||||
options={'HIDDEN'},
|
||||
maxlen=255, # Max internal buffer length, longer would be clamped.
|
||||
)
|
||||
|
||||
export_mode: bpy.props.EnumProperty(
|
||||
name="Export mode",
|
||||
items=(('COLLECTION', "Collection", "Export a collection"),
|
||||
('OBJECT', "Objects", "Export an objects"),
|
||||
),
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
if ((self.export_mode == 'COLLECTION' and context.scene.BallanceBlenderPluginProperty.collection_picker is None) or
|
||||
(self.export_mode == 'OBJECT' and context.scene.BallanceBlenderPluginProperty.object_picker is None)):
|
||||
UTILS_functions.show_message_box(("No specific target", ), "Lost parameter", 'ERROR')
|
||||
else:
|
||||
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||
|
||||
if self.export_mode == 'COLLECTION':
|
||||
export_bm(context, self.filepath,
|
||||
prefs.no_component_collection,
|
||||
self.export_mode, context.scene.BallanceBlenderPluginProperty.collection_picker)
|
||||
elif self.export_mode == 'OBJECT':
|
||||
export_bm(context, self.filepath,
|
||||
prefs.no_component_collection,
|
||||
self.export_mode, context.scene.BallanceBlenderPluginProperty.object_picker)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "export_mode", expand=True)
|
||||
if self.export_mode == 'COLLECTION':
|
||||
layout.prop(context.scene.BallanceBlenderPluginProperty, "collection_picker")
|
||||
elif self.export_mode == 'OBJECT':
|
||||
layout.prop(context.scene.BallanceBlenderPluginProperty, "object_picker")
|
||||
|
||||
|
||||
def export_bm(context, bmx_filepath, prefs_fncg, opts_exportMode, opts_exportTarget):
|
||||
# ============================================ alloc a temp folder
|
||||
utils_tempFolderObj = tempfile.TemporaryDirectory()
|
||||
utils_tempFolder = utils_tempFolderObj.name
|
||||
utils_tempTextureFolder = os.path.join(utils_tempFolder, "Texture")
|
||||
os.makedirs(utils_tempTextureFolder)
|
||||
|
||||
# ============================================
|
||||
# find export target.
|
||||
# do not need check them validation in there.
|
||||
# just collect them.
|
||||
if opts_exportMode== "COLLECTION":
|
||||
objectList = opts_exportTarget.objects
|
||||
else:
|
||||
objectList = [opts_exportTarget, ]
|
||||
|
||||
# try get fncg collection
|
||||
# fncg stands with forced non-component group
|
||||
try:
|
||||
object_fncgCollection = bpy.data.collections[prefs_fncg]
|
||||
except:
|
||||
object_fncgCollection = None
|
||||
|
||||
# ============================================ export
|
||||
with open(os.path.join(utils_tempFolder, "index.bm"), "wb") as finfo:
|
||||
UTILS_file_io.write_uint32(finfo, UTILS_constants.bmfile_currentVersion)
|
||||
|
||||
# ====================== export object
|
||||
meshSet = set()
|
||||
meshList = []
|
||||
meshCount = 0
|
||||
with open(os.path.join(utils_tempFolder, "object.bm"), "wb") as fobject:
|
||||
for obj in objectList:
|
||||
# only export mesh object
|
||||
if obj.type != 'MESH':
|
||||
continue
|
||||
|
||||
# clean no mesh object
|
||||
object_blenderMesh = obj.data
|
||||
if object_blenderMesh is None:
|
||||
continue
|
||||
|
||||
# check component
|
||||
if (object_fncgCollection is not None) and (obj.name in object_fncgCollection.objects):
|
||||
# it should be set as normal object forcely
|
||||
object_isComponent = False
|
||||
else:
|
||||
# check isComponent normally
|
||||
object_isComponent = UTILS_functions.is_component(obj.name)
|
||||
|
||||
# triangle first and then group
|
||||
if not object_isComponent:
|
||||
if object_blenderMesh not in meshSet:
|
||||
_mesh_triangulate(object_blenderMesh)
|
||||
meshSet.add(object_blenderMesh)
|
||||
meshList.append(object_blenderMesh)
|
||||
object_meshIndex = meshCount
|
||||
meshCount += 1
|
||||
else:
|
||||
object_meshIndex = meshList.index(object_blenderMesh)
|
||||
else:
|
||||
object_meshIndex = UTILS_functions.get_component_id(obj.name)
|
||||
|
||||
# get visibility
|
||||
object_isHidden = not obj.visible_get()
|
||||
|
||||
# try get grouping data
|
||||
object_groupList = UTILS_virtools_prop.get_virtools_group_data(obj)
|
||||
|
||||
# =======================
|
||||
# write to files
|
||||
# write finfo first
|
||||
UTILS_file_io.write_string(finfo, obj.name)
|
||||
UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.OBJECT)
|
||||
UTILS_file_io.write_uint64(finfo, fobject.tell())
|
||||
|
||||
# write fobject
|
||||
UTILS_file_io.write_bool(fobject, object_isComponent)
|
||||
UTILS_file_io.write_bool(fobject, object_isHidden)
|
||||
UTILS_file_io.write_world_matrix(fobject, obj.matrix_world)
|
||||
UTILS_file_io.write_uint32(fobject, len(object_groupList))
|
||||
for item in object_groupList:
|
||||
UTILS_file_io.write_string(fobject, item)
|
||||
UTILS_file_io.write_uint32(fobject, object_meshIndex)
|
||||
|
||||
# ====================== export mesh
|
||||
materialSet = set()
|
||||
materialList = []
|
||||
with open(os.path.join(utils_tempFolder, "mesh.bm"), "wb") as fmesh:
|
||||
for mesh in meshList:
|
||||
# split normals
|
||||
mesh.calc_normals_split()
|
||||
|
||||
# write finfo first
|
||||
UTILS_file_io.write_string(finfo, mesh.name)
|
||||
UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.MESH)
|
||||
UTILS_file_io.write_uint64(finfo, fmesh.tell())
|
||||
|
||||
# write fmesh
|
||||
# vertices
|
||||
mesh_vecList = mesh.vertices[:]
|
||||
UTILS_file_io.write_uint32(fmesh, len(mesh_vecList))
|
||||
for vec in mesh_vecList:
|
||||
#swap yz
|
||||
UTILS_file_io.write_3vector(fmesh,vec.co[0],vec.co[2],vec.co[1])
|
||||
|
||||
# uv
|
||||
mesh_faceIndexPairs = [(face, index) for index, face in enumerate(mesh.polygons)]
|
||||
UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs) * 3)
|
||||
if mesh.uv_layers.active is not None:
|
||||
uv_layer = mesh.uv_layers.active.data[:]
|
||||
for f, f_index in mesh_faceIndexPairs:
|
||||
# it should be triangle face, otherwise throw a error
|
||||
if (f.loop_total != 3):
|
||||
raise Exception("Not a triangle", f.poly.loop_total)
|
||||
|
||||
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
|
||||
uv = uv_layer[loop_index].uv
|
||||
# reverse v
|
||||
UTILS_file_io.write_2vector(fmesh, uv[0], -uv[1])
|
||||
else:
|
||||
# no uv data. write garbage
|
||||
for i in range(len(mesh_faceIndexPairs) * 3):
|
||||
UTILS_file_io.write_2vector(fmesh, 0.0, 0.0)
|
||||
|
||||
# normals
|
||||
UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs) * 3)
|
||||
for f, f_index in mesh_faceIndexPairs:
|
||||
# no need to check triangle again
|
||||
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
|
||||
nml = mesh.loops[loop_index].normal
|
||||
# swap yz
|
||||
UTILS_file_io.write_3vector(fmesh, nml[0], nml[2], nml[1])
|
||||
|
||||
# face
|
||||
# get material first
|
||||
mesh_usedBlenderMtl = mesh.materials[:]
|
||||
mesh_noMaterial = len(mesh_usedBlenderMtl) == 0
|
||||
for mat in mesh_usedBlenderMtl:
|
||||
if mat not in materialSet:
|
||||
materialSet.add(mat)
|
||||
materialList.append(mat)
|
||||
|
||||
UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs))
|
||||
mesh_vtIndex = []
|
||||
mesh_vnIndex = []
|
||||
mesh_vIndex = []
|
||||
for f, f_index in mesh_faceIndexPairs:
|
||||
# confirm material use
|
||||
if mesh_noMaterial:
|
||||
mesh_materialIndex = 0
|
||||
else:
|
||||
mesh_materialIndex = materialList.index(mesh_usedBlenderMtl[f.material_index])
|
||||
|
||||
# export face
|
||||
mesh_vtIndex.clear()
|
||||
mesh_vnIndex.clear()
|
||||
mesh_vIndex.clear()
|
||||
|
||||
counter = 0
|
||||
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
|
||||
mesh_vIndex.append(mesh.loops[loop_index].vertex_index)
|
||||
mesh_vnIndex.append(f_index * 3 + counter)
|
||||
mesh_vtIndex.append(f_index * 3 + counter)
|
||||
counter += 1
|
||||
# reverse vertices sort
|
||||
UTILS_file_io.write_face(fmesh,
|
||||
mesh_vIndex[2], mesh_vtIndex[2], mesh_vnIndex[2],
|
||||
mesh_vIndex[1], mesh_vtIndex[1], mesh_vnIndex[1],
|
||||
mesh_vIndex[0], mesh_vtIndex[0], mesh_vnIndex[0])
|
||||
|
||||
# set used material
|
||||
UTILS_file_io.write_bool(fmesh, not mesh_noMaterial)
|
||||
UTILS_file_io.write_uint32(fmesh, mesh_materialIndex)
|
||||
|
||||
# free splited normals
|
||||
mesh.free_normals_split()
|
||||
|
||||
# ====================== export material
|
||||
textureSet = set()
|
||||
textureList = []
|
||||
textureCount = 0
|
||||
with open(os.path.join(utils_tempFolder, "material.bm"), "wb") as fmaterial:
|
||||
for material in materialList:
|
||||
# write finfo first
|
||||
UTILS_file_io.write_string(finfo, material.name)
|
||||
UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.MATERIAL)
|
||||
UTILS_file_io.write_uint64(finfo, fmaterial.tell())
|
||||
|
||||
# try get original written data
|
||||
(material_enableVirtoolsMat,
|
||||
material_colAmbient, material_colDiffuse, material_colSpecular, material_colEmissive, material_specularPower,
|
||||
material_alphaTest, material_alphaBlend, material_zBuffer, material_twoSided,
|
||||
material_texture) = UTILS_virtools_prop.get_virtools_material_data(material)
|
||||
|
||||
# only try get from Principled BSDF when we couldn't get from virtools_material props
|
||||
if not material_enableVirtoolsMat:
|
||||
v = UTILS_functions.parse_material_nodes(material)
|
||||
if v is not None:
|
||||
(material_enableVirtoolsMat,
|
||||
material_colAmbient, material_colDiffuse, material_colSpecular, material_colEmissive, material_specularPower,
|
||||
material_alphaTest, material_alphaBlend, material_zBuffer, material_twoSided,
|
||||
material_texture) = v
|
||||
|
||||
# check texture index
|
||||
if material_texture is None:
|
||||
material_useTexture = False
|
||||
material_textureIndex = 0
|
||||
else:
|
||||
# add into texture list
|
||||
if material_texture not in textureSet:
|
||||
textureSet.add(material_texture)
|
||||
textureList.append(material_texture)
|
||||
textureIndex = textureCount
|
||||
textureCount += 1
|
||||
else:
|
||||
textureIndex = textureList.index(material_texture)
|
||||
|
||||
material_useTexture = True
|
||||
material_textureIndex = textureIndex
|
||||
|
||||
UTILS_file_io.write_color(fmaterial, material_colAmbient)
|
||||
UTILS_file_io.write_color(fmaterial, material_colDiffuse)
|
||||
UTILS_file_io.write_color(fmaterial, material_colSpecular)
|
||||
UTILS_file_io.write_color(fmaterial, material_colEmissive)
|
||||
UTILS_file_io.write_float(fmaterial, material_specularPower)
|
||||
UTILS_file_io.write_bool(fmaterial, material_alphaTest)
|
||||
UTILS_file_io.write_bool(fmaterial, material_alphaBlend)
|
||||
UTILS_file_io.write_bool(fmaterial, material_zBuffer)
|
||||
UTILS_file_io.write_bool(fmaterial, material_twoSided)
|
||||
UTILS_file_io.write_bool(fmaterial, material_useTexture)
|
||||
UTILS_file_io.write_uint32(fmaterial, material_textureIndex)
|
||||
|
||||
|
||||
# ====================== export texture
|
||||
texture_blenderFilePath = os.path.dirname(bpy.data.filepath)
|
||||
texture_existedTextureFilepath = set()
|
||||
with open(os.path.join(utils_tempFolder, "texture.bm"), "wb") as ftexture:
|
||||
for texture in textureList:
|
||||
# write finfo first
|
||||
UTILS_file_io.write_string(finfo, texture.name)
|
||||
UTILS_file_io.write_uint8(finfo, UTILS_constants.BmfileInfoType.TEXTURE)
|
||||
UTILS_file_io.write_uint64(finfo, ftexture.tell())
|
||||
|
||||
# confirm whether it is internal texture
|
||||
# get absolute texture path
|
||||
texture_filepath = io_utils.path_reference(texture.filepath, texture_blenderFilePath, utils_tempTextureFolder,
|
||||
'ABSOLUTE', "", None, texture.library)
|
||||
# get file name and write it
|
||||
texture_filename = os.path.basename(texture_filepath)
|
||||
UTILS_file_io.write_string(ftexture, texture_filename)
|
||||
|
||||
if (_is_external_texture(texture_filename)):
|
||||
# write directly, use Ballance texture
|
||||
UTILS_file_io.write_bool(ftexture, True)
|
||||
else:
|
||||
# copy internal texture, if this file is copied, do not copy it again
|
||||
UTILS_file_io.write_bool(ftexture, False)
|
||||
if texture_filename not in texture_existedTextureFilepath:
|
||||
shutil.copy(texture_filepath, os.path.join(utils_tempTextureFolder, texture_filename))
|
||||
texture_existedTextureFilepath.add(texture_filename)
|
||||
|
||||
|
||||
# ============================================
|
||||
# save zip and clean up folder
|
||||
UTILS_zip_helper.compress(utils_tempFolder, bmx_filepath)
|
||||
utils_tempFolderObj.cleanup()
|
||||
|
||||
# ==========================================
|
||||
# blender related functions
|
||||
|
||||
def _is_external_texture(name):
|
||||
if name in UTILS_constants.bmfile_externalTextureSet:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def _mesh_triangulate(me):
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(me)
|
||||
bmesh.ops.triangulate(bm, faces=bm.faces)
|
||||
bm.to_mesh(me)
|
||||
bm.free()
|
||||
|
||||
def _set_value_when_none(obj, newValue):
|
||||
if obj is None:
|
||||
return newValue
|
||||
else:
|
||||
return obj
|
||||
|
@ -1,372 +0,0 @@
|
||||
import bpy,bmesh,bpy_extras,mathutils
|
||||
import pathlib,zipfile,time,os,tempfile,math
|
||||
import struct, shutil
|
||||
from bpy_extras import io_utils,node_shader_utils
|
||||
from bpy_extras.io_utils import unpack_list
|
||||
from bpy_extras.image_utils import load_image
|
||||
from . import UTILS_constants, UTILS_functions, UTILS_file_io, UTILS_zip_helper, UTILS_virtools_prop
|
||||
|
||||
class BALLANCE_OT_import_bm(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
|
||||
"""Load a Ballance Map File (BM file spec 1.4)"""
|
||||
bl_idname = "ballance.import_bm"
|
||||
bl_label = "Import BM "
|
||||
bl_options = {'PRESET', 'UNDO'}
|
||||
|
||||
# ImportHelper mixin class uses this
|
||||
filename_ext = ".bmx"
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default="*.bmx",
|
||||
options={'HIDDEN'},
|
||||
maxlen=255, # Max internal buffer length, longer would be clamped.
|
||||
)
|
||||
|
||||
texture_conflict_strategy: bpy.props.EnumProperty(
|
||||
name="Texture name conflict",
|
||||
items=(('NEW', "New instance", "Create a new instance"),
|
||||
('CURRENT', "Use current", "Use current"),),
|
||||
description="Define how to process texture name conflict",
|
||||
default='CURRENT',
|
||||
)
|
||||
|
||||
material_conflict_strategy: bpy.props.EnumProperty(
|
||||
name="Material name conflict",
|
||||
items=(('RENAME', "Rename", "Rename the new one"),
|
||||
('CURRENT', "Use current", "Use current"),),
|
||||
description="Define how to process material name conflict",
|
||||
default='RENAME',
|
||||
)
|
||||
|
||||
mesh_conflict_strategy: bpy.props.EnumProperty(
|
||||
name="Mesh name conflict",
|
||||
items=(('RENAME', "Rename", "Rename the new one"),
|
||||
('CURRENT', "Use current", "Use current"),),
|
||||
description="Define how to process mesh name conflict",
|
||||
default='RENAME',
|
||||
)
|
||||
|
||||
object_conflict_strategy: bpy.props.EnumProperty(
|
||||
name="Object name conflict",
|
||||
items=(('RENAME', "Rename", "Rename the new one"),
|
||||
('CURRENT', "Use current", "Use current"),),
|
||||
description="Define how to process object name conflict",
|
||||
default='RENAME',
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||
return (os.path.isdir(prefs.temp_texture_folder) and os.path.isdir(prefs.external_folder))
|
||||
|
||||
def execute(self, context):
|
||||
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||
import_bm(context, self.filepath,
|
||||
prefs.no_component_collection, prefs.external_folder, prefs.temp_texture_folder,
|
||||
self.texture_conflict_strategy, self.material_conflict_strategy,
|
||||
self.mesh_conflict_strategy, self.object_conflict_strategy)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def import_bm(context, bmx_filepath, prefs_fncg, prefs_externalTexture, prefs_tempTextureFolder, opts_texture, opts_material, opts_mesh, opts_object):
|
||||
# ============================================
|
||||
# alloc a temp folder for decompress
|
||||
utils_tempFolderObj = tempfile.TemporaryDirectory()
|
||||
utils_tempFolder = utils_tempFolderObj.name
|
||||
utils_tempTextureFolder = os.path.join(utils_tempFolder, "Texture")
|
||||
# decompress
|
||||
UTILS_zip_helper.decompress(utils_tempFolder, bmx_filepath)
|
||||
|
||||
# ============================================
|
||||
# read bmx file officially
|
||||
# index.bm
|
||||
objectList = []
|
||||
meshList = []
|
||||
materialList = []
|
||||
textureList = []
|
||||
with open(os.path.join(utils_tempFolder, "index.bm"), "rb") as findex:
|
||||
# check version first
|
||||
index_gottenVersion = UTILS_file_io.read_uint32(findex)
|
||||
if (index_gottenVersion != UTILS_constants.bmfile_currentVersion):
|
||||
# clean temp folder, output error
|
||||
UTILS_functions.show_message_box(
|
||||
("Unsupported BM spec. Expect: {} Gotten: {}".format(UTILS_constants.bmfile_currentVersion, index_gottenVersion), ),
|
||||
"Unsupported BM spec", 'ERROR')
|
||||
findex.close()
|
||||
utils_tempFolderObj.cleanup()
|
||||
return
|
||||
|
||||
# collect block header data
|
||||
while len(UTILS_file_io.peek_stream(findex)) != 0:
|
||||
# read
|
||||
index_name = UTILS_file_io.read_string(findex)
|
||||
index_type = UTILS_file_io.read_uint8(findex)
|
||||
index_offset = UTILS_file_io.read_uint64(findex)
|
||||
index_blockCache = _InfoBlockHelper(index_name, index_offset)
|
||||
|
||||
# grouping into list
|
||||
if index_type == UTILS_constants.BmfileInfoType.OBJECT:
|
||||
objectList.append(index_blockCache)
|
||||
elif index_type == UTILS_constants.BmfileInfoType.MESH:
|
||||
meshList.append(index_blockCache)
|
||||
elif index_type == UTILS_constants.BmfileInfoType.MATERIAL:
|
||||
materialList.append(index_blockCache)
|
||||
elif index_type == UTILS_constants.BmfileInfoType.TEXTURE:
|
||||
textureList.append(index_blockCache)
|
||||
else:
|
||||
pass
|
||||
|
||||
|
||||
# texture.bm
|
||||
with open(os.path.join(utils_tempFolder, "texture.bm"), "rb") as ftexture:
|
||||
for item in textureList:
|
||||
# seek to block
|
||||
ftexture.seek(item.offset, os.SEEK_SET)
|
||||
|
||||
# read data
|
||||
texture_filename = UTILS_file_io.read_string(ftexture)
|
||||
texture_isExternal = UTILS_file_io.read_bool(ftexture)
|
||||
if texture_isExternal:
|
||||
(texture_target, skip_init) = UTILS_functions.create_instance_with_option(
|
||||
UTILS_constants.BmfileInfoType.TEXTURE, item.name, opts_texture,
|
||||
extra_texture_filename= texture_filename, extra_texture_path= prefs_externalTexture)
|
||||
else:
|
||||
# not external. copy temp file into blender temp. then use it.
|
||||
# try copy. if fail, don't need to do more
|
||||
try:
|
||||
shutil.copy(os.path.join(utils_tempTextureFolder, texture_filename),
|
||||
os.path.join(prefs_tempTextureFolder, texture_filename))
|
||||
except:
|
||||
pass
|
||||
|
||||
(texture_target, skip_init) = UTILS_functions.create_instance_with_option(
|
||||
UTILS_constants.BmfileInfoType.TEXTURE, item.name, opts_texture,
|
||||
extra_texture_filename= texture_filename, extra_texture_path= prefs_tempTextureFolder)
|
||||
|
||||
# setup name and blender data for header
|
||||
item.blender_data = texture_target
|
||||
|
||||
# material.bm
|
||||
# WARNING: this code is shared with add_floor - create_or_get_material()
|
||||
with open(os.path.join(utils_tempFolder, "material.bm"), "rb") as fmaterial:
|
||||
for item in materialList:
|
||||
# seek to block
|
||||
fmaterial.seek(item.offset, os.SEEK_SET)
|
||||
|
||||
# read data
|
||||
material_colAmbient = UTILS_file_io.read_3vector(fmaterial)
|
||||
material_colDiffuse = UTILS_file_io.read_3vector(fmaterial)
|
||||
material_colSpecular = UTILS_file_io.read_3vector(fmaterial)
|
||||
material_colEmissive = UTILS_file_io.read_3vector(fmaterial)
|
||||
material_specularPower = UTILS_file_io.read_float(fmaterial)
|
||||
material_alphaTest = UTILS_file_io.read_bool(fmaterial)
|
||||
material_alphaBlend = UTILS_file_io.read_bool(fmaterial)
|
||||
material_zBuffer = UTILS_file_io.read_bool(fmaterial)
|
||||
material_twoSided = UTILS_file_io.read_bool(fmaterial)
|
||||
material_useTexture = UTILS_file_io.read_bool(fmaterial)
|
||||
material_texture = UTILS_file_io.read_uint32(fmaterial)
|
||||
|
||||
# alloc basic material
|
||||
(material_target, skip_init) = UTILS_functions.create_instance_with_option(
|
||||
UTILS_constants.BmfileInfoType.MATERIAL, item.name, opts_material)
|
||||
item.blender_data = material_target
|
||||
if skip_init:
|
||||
continue
|
||||
|
||||
# try create material nodes
|
||||
UTILS_functions.create_blender_material(material_target,
|
||||
(True,
|
||||
material_colAmbient, material_colDiffuse, material_colSpecular, material_colEmissive, material_specularPower,
|
||||
material_alphaTest, material_alphaBlend, material_zBuffer, material_twoSided,
|
||||
textureList[material_texture].blender_data if material_useTexture else None)
|
||||
)
|
||||
|
||||
# mesh.bm
|
||||
# WARNING: this code is shared with add_floor
|
||||
with open(os.path.join(utils_tempFolder, "mesh.bm"), "rb") as fmesh:
|
||||
mesh_vList=[]
|
||||
mesh_vtList=[]
|
||||
mesh_vnList=[]
|
||||
mesh_faceList=[]
|
||||
mesh_materialSolt = []
|
||||
for item in meshList:
|
||||
fmesh.seek(item.offset, os.SEEK_SET)
|
||||
|
||||
# create real mesh
|
||||
(mesh_target, skip_init) = UTILS_functions.create_instance_with_option(
|
||||
UTILS_constants.BmfileInfoType.MESH, item.name, opts_mesh)
|
||||
item.blender_data = mesh_target
|
||||
if skip_init:
|
||||
continue
|
||||
|
||||
mesh_vList.clear()
|
||||
mesh_vtList.clear()
|
||||
mesh_vnList.clear()
|
||||
mesh_faceList.clear()
|
||||
mesh_materialSolt.clear()
|
||||
# in first read, store all data into list
|
||||
mesh_listCount = UTILS_file_io.read_uint32(fmesh)
|
||||
for i in range(mesh_listCount):
|
||||
cache = UTILS_file_io.read_3vector(fmesh)
|
||||
# switch yz
|
||||
mesh_vList.append((cache[0], cache[2], cache[1]))
|
||||
mesh_listCount = UTILS_file_io.read_uint32(fmesh)
|
||||
for i in range(mesh_listCount):
|
||||
cache = UTILS_file_io.read_2vector(fmesh)
|
||||
# reverse v
|
||||
mesh_vtList.append((cache[0], -cache[1]))
|
||||
mesh_listCount = UTILS_file_io.read_uint32(fmesh)
|
||||
for i in range(mesh_listCount):
|
||||
cache = UTILS_file_io.read_3vector(fmesh)
|
||||
# switch yz
|
||||
mesh_vnList.append((cache[0], cache[2], cache[1]))
|
||||
|
||||
mesh_listCount = UTILS_file_io.read_uint32(fmesh)
|
||||
for i in range(mesh_listCount):
|
||||
mesh_faceData = UTILS_file_io.read_face(fmesh)
|
||||
mesh_useMaterial = UTILS_file_io.read_bool(fmesh)
|
||||
mesh_materialIndex = UTILS_file_io.read_uint32(fmesh)
|
||||
|
||||
if mesh_useMaterial:
|
||||
mesh_neededMaterial = materialList[mesh_materialIndex].blender_data
|
||||
if mesh_neededMaterial in mesh_materialSolt:
|
||||
mesh_blenderMtlIndex = mesh_materialSolt.index(mesh_neededMaterial)
|
||||
else:
|
||||
mesh_blenderMtlIndex = len(mesh_materialSolt)
|
||||
mesh_materialSolt.append(mesh_neededMaterial)
|
||||
else:
|
||||
mesh_blenderMtlIndex = -1
|
||||
|
||||
# we need invert triangle sort
|
||||
mesh_faceList.append((
|
||||
mesh_faceData[6], mesh_faceData[7], mesh_faceData[8],
|
||||
mesh_faceData[3], mesh_faceData[4], mesh_faceData[5],
|
||||
mesh_faceData[0], mesh_faceData[1], mesh_faceData[2],
|
||||
mesh_blenderMtlIndex
|
||||
))
|
||||
|
||||
# and then we need add material solt for this mesh
|
||||
for mat in mesh_materialSolt:
|
||||
mesh_target.materials.append(mat)
|
||||
|
||||
# then, we need add correspond count for vertices
|
||||
mesh_target.vertices.add(len(mesh_vList))
|
||||
mesh_target.loops.add(len(mesh_faceList)*3) # triangle face confirm
|
||||
mesh_target.polygons.add(len(mesh_faceList))
|
||||
mesh_target.uv_layers.new(do_init=False)
|
||||
mesh_target.create_normals_split()
|
||||
|
||||
# add vertices data
|
||||
mesh_target.vertices.foreach_set("co", unpack_list(mesh_vList))
|
||||
mesh_target.loops.foreach_set("vertex_index", unpack_list(_flat_vertices_index(mesh_faceList)))
|
||||
mesh_target.loops.foreach_set("normal", unpack_list(_flat_vertices_normal(mesh_faceList, mesh_vnList)))
|
||||
mesh_target.uv_layers[0].data.foreach_set("uv", unpack_list(_flat_vertices_uv(mesh_faceList, mesh_vtList)))
|
||||
for i in range(len(mesh_faceList)):
|
||||
mesh_target.polygons[i].loop_start = i * 3
|
||||
mesh_target.polygons[i].loop_total = 3
|
||||
if mesh_faceList[i][9] != -1:
|
||||
mesh_target.polygons[i].material_index = mesh_faceList[i][9]
|
||||
|
||||
mesh_target.polygons[i].use_smooth = True
|
||||
|
||||
mesh_target.validate(clean_customdata=False)
|
||||
mesh_target.update(calc_edges=False, calc_edges_loose=False)
|
||||
|
||||
|
||||
# object
|
||||
with open(os.path.join(utils_tempFolder, "object.bm"), "rb") as fobject:
|
||||
|
||||
# we need get needed collection first
|
||||
blender_viewLayer = context.view_layer
|
||||
blender_collection = blender_viewLayer.active_layer_collection.collection
|
||||
if prefs_fncg == "":
|
||||
# fncg stands with Forced Non-Component Group
|
||||
object_fncgCollection = None
|
||||
else:
|
||||
try:
|
||||
# try get collection
|
||||
object_fncgCollection = bpy.data.collections[prefs_fncg]
|
||||
except:
|
||||
# fail to get, create new one under active collection instead
|
||||
object_fncgCollection = bpy.data.collections.new(prefs_fncg)
|
||||
blender_collection.children.link(object_fncgCollection)
|
||||
|
||||
# start process it
|
||||
object_groupList = []
|
||||
for item in objectList:
|
||||
fobject.seek(item.offset, os.SEEK_SET)
|
||||
|
||||
# read data
|
||||
object_isComponent = UTILS_file_io.read_bool(fobject)
|
||||
#object_isForcedNoComponent = UTILS_file_io.read_bool(fobject)
|
||||
object_isHidden = UTILS_file_io.read_bool(fobject)
|
||||
object_worldMatrix = UTILS_file_io.read_world_materix(fobject)
|
||||
object_groupListCount = UTILS_file_io.read_uint32(fobject)
|
||||
object_groupList.clear()
|
||||
for i in range(object_groupListCount):
|
||||
object_groupList.append(UTILS_file_io.read_string(fobject))
|
||||
object_meshIndex = UTILS_file_io.read_uint32(fobject)
|
||||
|
||||
# got mesh first
|
||||
if object_isComponent:
|
||||
object_neededMesh = UTILS_functions.load_component(object_meshIndex)
|
||||
else:
|
||||
object_neededMesh = meshList[object_meshIndex].blender_data
|
||||
|
||||
# create real object
|
||||
(object_target, skip_init) = UTILS_functions.create_instance_with_option(
|
||||
UTILS_constants.BmfileInfoType.OBJECT, item.name, opts_object,
|
||||
extra_mesh=object_neededMesh)
|
||||
if skip_init:
|
||||
continue
|
||||
|
||||
# link to correct collection
|
||||
if (object_fncgCollection is not None) and (not object_isComponent) and UTILS_functions.is_component(item.name):
|
||||
# a object should be grouped into fncg should check following requirements
|
||||
# fncg is not null
|
||||
# this object is a normal object
|
||||
# but its name match component format
|
||||
object_fncgCollection.objects.link(object_target)
|
||||
else:
|
||||
# otherwise, group it into normal collection
|
||||
blender_collection.objects.link(object_target)
|
||||
object_target.matrix_world = object_worldMatrix
|
||||
object_target.hide_set(object_isHidden)
|
||||
|
||||
# write custom property
|
||||
if len(object_groupList) != 0:
|
||||
UTILS_virtools_prop.fill_virtools_group_data(object_target, tuple(object_groupList))
|
||||
else:
|
||||
UTILS_virtools_prop.fill_virtools_group_data(object_target, None)
|
||||
|
||||
# update view layer after all objects has been imported
|
||||
blender_viewLayer.update()
|
||||
|
||||
# release temp folder
|
||||
utils_tempFolderObj.cleanup()
|
||||
|
||||
|
||||
# ==========================================
|
||||
# blender related functions
|
||||
|
||||
class _InfoBlockHelper():
|
||||
def __init__(self, name, offset):
|
||||
self.name = name
|
||||
self.offset = offset
|
||||
self.blender_data = None
|
||||
|
||||
def _flat_vertices_index(faceList):
|
||||
for item in faceList:
|
||||
yield (item[0], )
|
||||
yield (item[3], )
|
||||
yield (item[6], )
|
||||
|
||||
def _flat_vertices_normal(faceList, vnList):
|
||||
for item in faceList:
|
||||
yield vnList[item[2]]
|
||||
yield vnList[item[5]]
|
||||
yield vnList[item[8]]
|
||||
|
||||
def _flat_vertices_uv(faceList, vtList):
|
||||
for item in faceList:
|
||||
yield vtList[item[1]]
|
||||
yield vtList[item[4]]
|
||||
yield vtList[item[7]]
|
@ -1,128 +0,0 @@
|
||||
import bpy, mathutils
|
||||
from . import UTILS_functions
|
||||
|
||||
class BALLANCE_OT_super_align(bpy.types.Operator):
|
||||
"""Align object with 3ds Max style"""
|
||||
bl_idname = "ballance.super_align"
|
||||
bl_label = "3ds Max Align"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
align_x: bpy.props.BoolProperty(name="X position")
|
||||
align_y: bpy.props.BoolProperty(name="Y position")
|
||||
align_z: bpy.props.BoolProperty(name="Z position")
|
||||
|
||||
current_references: bpy.props.EnumProperty(
|
||||
name="Reference (Active Object)",
|
||||
items=(('MIN', "Min", ""),
|
||||
('CENTER', "Center (bound box)", ""),
|
||||
('POINT', "Center (axis)", ""),
|
||||
('MAX', "Max", "")
|
||||
),
|
||||
)
|
||||
|
||||
target_references: bpy.props.EnumProperty(
|
||||
name="Target (Other Objects)",
|
||||
items=(('MIN', "Min", ""),
|
||||
('CENTER', "Center (bound box)", ""),
|
||||
('POINT', "Center (axis)", ""),
|
||||
('MAX', "Max", "")
|
||||
),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return _check_align_target()
|
||||
|
||||
def execute(self, context):
|
||||
_align_object(self.align_x, self.align_y, self.align_z, self.current_references, self.target_references)
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
col.label(text="Align axis")
|
||||
|
||||
row = col.row()
|
||||
row.prop(self, "align_x")
|
||||
row.prop(self, "align_y")
|
||||
row.prop(self, "align_z")
|
||||
|
||||
col.prop(self, "current_references")
|
||||
col.prop(self, "target_references")
|
||||
|
||||
# ============================== method
|
||||
|
||||
def _check_align_target():
|
||||
if bpy.context.active_object is None:
|
||||
return False
|
||||
|
||||
selected = bpy.context.selected_objects[:]
|
||||
length = len(selected)
|
||||
if bpy.context.active_object in selected:
|
||||
length -= 1
|
||||
if length == 0:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _align_object(use_x, use_y, use_z, currentMode, targetMode):
|
||||
if not (use_x or use_y or use_z):
|
||||
return
|
||||
|
||||
# calc active object data
|
||||
currentObj = bpy.context.active_object
|
||||
currentObjBbox = [currentObj.matrix_world @ mathutils.Vector(corner) for corner in currentObj.bound_box]
|
||||
currentObjRef = _provide_obj_reference_point(currentObj, currentObjBbox, currentMode)
|
||||
|
||||
# calc target
|
||||
targetObjList = bpy.context.selected_objects[:]
|
||||
if currentObj in targetObjList:
|
||||
targetObjList.remove(currentObj)
|
||||
|
||||
# process each obj
|
||||
for targetObj in targetObjList:
|
||||
targetObjBbox = [targetObj.matrix_world @ mathutils.Vector(corner) for corner in targetObj.bound_box]
|
||||
targetObjRef = _provide_obj_reference_point(targetObj, targetObjBbox, targetMode)
|
||||
|
||||
if use_x:
|
||||
targetObj.location.x += currentObjRef.x - targetObjRef.x
|
||||
if use_y:
|
||||
targetObj.location.y += currentObjRef.y - targetObjRef.y
|
||||
if use_z:
|
||||
targetObj.location.z += currentObjRef.z - targetObjRef.z
|
||||
|
||||
def _provide_obj_reference_point(obj, vecList, mode):
|
||||
refPoint = mathutils.Vector((0, 0, 0))
|
||||
|
||||
if (mode == 'MIN'):
|
||||
refPoint.x = min([vec.x for vec in vecList])
|
||||
refPoint.y = min([vec.y for vec in vecList])
|
||||
refPoint.z = min([vec.z for vec in vecList])
|
||||
elif (mode == 'MAX'):
|
||||
refPoint.x = max([vec.x for vec in vecList])
|
||||
refPoint.y = max([vec.y for vec in vecList])
|
||||
refPoint.z = max([vec.z for vec in vecList])
|
||||
elif (mode == 'CENTER'):
|
||||
maxVecCache = mathutils.Vector((0, 0, 0))
|
||||
minVecCache = mathutils.Vector((0, 0, 0))
|
||||
|
||||
minVecCache.x = min([vec.x for vec in vecList])
|
||||
minVecCache.y = min([vec.y for vec in vecList])
|
||||
minVecCache.z = min([vec.z for vec in vecList])
|
||||
maxVecCache.x = max([vec.x for vec in vecList])
|
||||
maxVecCache.y = max([vec.y for vec in vecList])
|
||||
maxVecCache.z = max([vec.z for vec in vecList])
|
||||
|
||||
refPoint.x = (maxVecCache.x + minVecCache.x) / 2
|
||||
refPoint.y = (maxVecCache.y + minVecCache.y) / 2
|
||||
refPoint.z = (maxVecCache.z + minVecCache.z) / 2
|
||||
else:
|
||||
refPoint.x = obj.location.x
|
||||
refPoint.y = obj.location.y
|
||||
refPoint.z = obj.location.z
|
||||
|
||||
return refPoint
|
@ -1,120 +0,0 @@
|
||||
import bpy,mathutils
|
||||
import bmesh
|
||||
from . import UTILS_functions
|
||||
|
||||
class BALLANCE_OT_flatten_uv(bpy.types.Operator):
|
||||
"""Flatten selected face UV. Only works for convex face"""
|
||||
bl_idname = "ballance.flatten_uv"
|
||||
bl_label = "Flatten UV"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
reference_edge : bpy.props.IntProperty(
|
||||
name="Reference edge",
|
||||
description="The references edge of UV. It will be placed in V axis.",
|
||||
min=0,
|
||||
soft_min=0,
|
||||
soft_max=3,
|
||||
default=0,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
obj = bpy.context.active_object
|
||||
if obj == None:
|
||||
return False
|
||||
if obj.type != 'MESH':
|
||||
return False
|
||||
if obj.mode != 'EDIT':
|
||||
return False
|
||||
return True
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
no_processed_count = _real_flatten_uv(bpy.context.active_object.data, self.reference_edge)
|
||||
if no_processed_count != 0:
|
||||
UTILS_functions.show_message_box(
|
||||
("{} faces may not be processed correctly because they have problem.".format(no_processed_count), ),
|
||||
"Warning", 'ERROR'
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "reference_edge")
|
||||
|
||||
def _real_flatten_uv(mesh, reference_edge):
|
||||
no_processed_count = 0
|
||||
|
||||
if mesh.uv_layers.active is None:
|
||||
# if no uv, create it
|
||||
mesh.uv_layers.new(do_init=False)
|
||||
|
||||
bm = bmesh.from_edit_mesh(mesh)
|
||||
uv_lay = bm.loops.layers.uv.active
|
||||
for face in bm.faces:
|
||||
if not face.select:
|
||||
continue
|
||||
|
||||
allPoint = len(face.loops)
|
||||
|
||||
if allPoint <= reference_edge:
|
||||
no_processed_count+=1
|
||||
continue
|
||||
|
||||
# get correct new corrdinate system
|
||||
p1Relative = reference_edge
|
||||
p2Relative = reference_edge + 1
|
||||
p3Relative = reference_edge + 2
|
||||
if p2Relative >= allPoint:
|
||||
p2Relative -= allPoint
|
||||
if p3Relative >= allPoint:
|
||||
p3Relative -= allPoint
|
||||
|
||||
p1=mathutils.Vector(tuple(face.loops[p1Relative].vert.co[x] for x in range(3)))
|
||||
p2=mathutils.Vector(tuple(face.loops[p2Relative].vert.co[x] for x in range(3)))
|
||||
p3=mathutils.Vector(tuple(face.loops[p3Relative].vert.co[x] for x in range(3)))
|
||||
|
||||
new_y_axis = p2 - p1
|
||||
new_y_axis.normalize()
|
||||
vec1 = p3 - p2
|
||||
vec1.normalize()
|
||||
|
||||
new_z_axis = new_y_axis.cross(vec1)
|
||||
new_z_axis.normalize()
|
||||
new_x_axis = new_y_axis.cross(new_z_axis)
|
||||
new_x_axis.normalize()
|
||||
|
||||
# construct rebase matrix
|
||||
origin_base = mathutils.Matrix((
|
||||
(1.0, 0, 0),
|
||||
(0, 1.0, 0),
|
||||
(0, 0, 1.0)
|
||||
))
|
||||
origin_base.invert()
|
||||
new_base = mathutils.Matrix((
|
||||
(new_x_axis.x, new_y_axis.x, new_z_axis.x),
|
||||
(new_x_axis.y, new_y_axis.y, new_z_axis.y),
|
||||
(new_x_axis.z, new_y_axis.z, new_z_axis.z)
|
||||
))
|
||||
transition_matrix = origin_base @ new_base
|
||||
transition_matrix.invert()
|
||||
|
||||
# process each face
|
||||
for loop_index in range(allPoint):
|
||||
pp = mathutils.Vector(tuple(face.loops[loop_index].vert.co[x] for x in range(3)))
|
||||
vec = pp-p1
|
||||
new_vec = transition_matrix @ vec
|
||||
|
||||
face.loops[loop_index][uv_lay].uv = (
|
||||
(new_vec.x if new_vec.x >=0 else -new_vec.x) / 5,
|
||||
(new_vec.y) / 5
|
||||
)
|
||||
|
||||
# Show the updates in the viewport
|
||||
bmesh.update_edit_mesh(mesh)
|
||||
|
||||
return no_processed_count
|
||||
|
@ -1,191 +0,0 @@
|
||||
import bpy,bmesh
|
||||
import mathutils
|
||||
import bpy.types
|
||||
from . import UTILS_functions
|
||||
|
||||
class BALLANCE_OT_rail_uv(bpy.types.Operator):
|
||||
"""Create a UV for rail"""
|
||||
bl_idname = "ballance.rail_uv"
|
||||
bl_label = "Create Rail UV"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
uv_type: bpy.props.EnumProperty(
|
||||
name="Type",
|
||||
description="Define how to create UV",
|
||||
items=(
|
||||
("POINT", "Point", "All UV will be created in a specific point"),
|
||||
("UNIFORM", "Uniform", "All UV will be created within 1x1"),
|
||||
("SCALE", "Scale", "Give a scale number to scale UV"),
|
||||
("TT", "TT_ReflectionMapping", "The real internal process of Ballance rail")
|
||||
),
|
||||
)
|
||||
|
||||
projection_axis: bpy.props.EnumProperty(
|
||||
name="Projection axis",
|
||||
description="Projection axis",
|
||||
items=(
|
||||
("X", "X axis", "X axis"),
|
||||
("Y", "Y axis", "Y axis"),
|
||||
("Z", "Z axis", "Z axis")
|
||||
),
|
||||
)
|
||||
|
||||
uv_scale : bpy.props.FloatProperty(
|
||||
name="Scale",
|
||||
description="The scale of UV",
|
||||
min=0.0,
|
||||
default=1.0,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return _check_rail_target()
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
if context.scene.BallanceBlenderPluginProperty.material_picker == None:
|
||||
UTILS_functions.show_message_box(("No specific material", ), "Lost parameter", 'ERROR')
|
||||
else:
|
||||
_create_rail_uv(self.uv_type, context.scene.BallanceBlenderPluginProperty.material_picker, self.uv_scale, self.projection_axis)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "uv_type")
|
||||
layout.prop(context.scene.BallanceBlenderPluginProperty, "material_picker")
|
||||
if self.uv_type == 'SCALE' or self.uv_type == 'UNIFORM':
|
||||
layout.prop(self, "projection_axis")
|
||||
if self.uv_type == 'SCALE':
|
||||
layout.prop(self, "uv_scale")
|
||||
|
||||
# ====================== method
|
||||
|
||||
def _check_rail_target():
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj.type != 'MESH':
|
||||
continue
|
||||
if obj.mode != 'OBJECT':
|
||||
continue
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_distance(iterator):
|
||||
is_first_min = True
|
||||
is_first_max = True
|
||||
max_value = 0.0
|
||||
min_value = 0.0
|
||||
|
||||
for item in iterator:
|
||||
if is_first_max:
|
||||
is_first_max = False
|
||||
max_value = item
|
||||
else:
|
||||
if item > max_value:
|
||||
max_value = item
|
||||
if is_first_min:
|
||||
is_first_min = False
|
||||
min_value = item
|
||||
else:
|
||||
if item < min_value:
|
||||
min_value = item
|
||||
|
||||
return max_value - min_value
|
||||
|
||||
def _create_rail_uv(rail_type, material_pointer, scale_size, projection_axis):
|
||||
objList = []
|
||||
ignoredObj = []
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj.type != 'MESH':
|
||||
ignoredObj.append(obj.name)
|
||||
continue
|
||||
if obj.mode != 'OBJECT':
|
||||
ignoredObj.append(obj.name)
|
||||
continue
|
||||
if obj.data.uv_layers.active is None:
|
||||
# create a empty uv for it.
|
||||
obj.data.uv_layers.new(do_init=False)
|
||||
|
||||
objList.append(obj)
|
||||
|
||||
for obj in objList:
|
||||
mesh = obj.data
|
||||
|
||||
# clean it material and set rail first
|
||||
obj.data.materials.clear()
|
||||
obj.data.materials.append(material_pointer)
|
||||
|
||||
# copy mesh vec for scale or uniform mode
|
||||
vecList = mesh.vertices[:]
|
||||
real_scale = 1.0
|
||||
if rail_type == 'SCALE':
|
||||
real_scale = scale_size
|
||||
elif rail_type == 'UNIFORM':
|
||||
# calc proper scale
|
||||
if projection_axis == 'X':
|
||||
maxLength = max(
|
||||
_get_distance(vec.co[1] for vec in vecList),
|
||||
_get_distance(vec.co[2] for vec in vecList)
|
||||
)
|
||||
elif projection_axis == 'Y':
|
||||
maxLength = max(
|
||||
_get_distance(vec.co[0] for vec in vecList),
|
||||
_get_distance(vec.co[2] for vec in vecList)
|
||||
)
|
||||
elif projection_axis == 'Z':
|
||||
maxLength = max(
|
||||
_get_distance(vec.co[0] for vec in vecList),
|
||||
_get_distance(vec.co[1] for vec in vecList)
|
||||
)
|
||||
real_scale = 1.0 / maxLength
|
||||
|
||||
uv_layer = mesh.uv_layers.active.data
|
||||
for poly in mesh.polygons:
|
||||
for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total):
|
||||
# get correspond vec index
|
||||
index = mesh.loops[loop_index].vertex_index
|
||||
if rail_type == 'POINT':
|
||||
# set to 1 point
|
||||
uv_layer[loop_index].uv[0] = 0
|
||||
uv_layer[loop_index].uv[1] = 1
|
||||
elif rail_type == 'SCALE' or rail_type == 'UNIFORM':
|
||||
# following xy -> uv scale
|
||||
#
|
||||
# use Z axis: X->U Y->V
|
||||
# use X axis: Y->U Z->V
|
||||
# use Y axis: X->U Z->V
|
||||
if projection_axis == 'X':
|
||||
uv_layer[loop_index].uv[0] = vecList[index].co[1] * real_scale
|
||||
uv_layer[loop_index].uv[1] = vecList[index].co[2] * real_scale
|
||||
elif projection_axis == 'Y':
|
||||
uv_layer[loop_index].uv[0] = vecList[index].co[0] * real_scale
|
||||
uv_layer[loop_index].uv[1] = vecList[index].co[2] * real_scale
|
||||
elif projection_axis == 'Z':
|
||||
uv_layer[loop_index].uv[0] = vecList[index].co[0] * real_scale
|
||||
uv_layer[loop_index].uv[1] = vecList[index].co[1] * real_scale
|
||||
elif rail_type == 'TT':
|
||||
(uv_layer[loop_index].uv[0], uv_layer[loop_index].uv[1]) = _tt_reflection_mapping_compute(
|
||||
vecList[index].co,
|
||||
mesh.loops[loop_index].normal,
|
||||
(0.0, 0.0, 0.0)
|
||||
)
|
||||
|
||||
if len(ignoredObj) != 0:
|
||||
UTILS_functions.show_message_box(
|
||||
("Following objects are not processed due to they are not suit for this function now: ", ) + tuple(ignoredObj),
|
||||
"Execution result", 'INFO'
|
||||
)
|
||||
|
||||
def _tt_reflection_mapping_compute(_point, _n, _refobj):
|
||||
# switch blender coord to virtools coord for convenient calc
|
||||
point = mathutils.Vector((_point[0], _point[2], _point[1]))
|
||||
n = mathutils.Vector((_n[0], _n[2], _n[1])).normalized()
|
||||
refobj = mathutils.Vector((_refobj[0], _refobj[2], _refobj[1]))
|
||||
|
||||
p = (refobj - point).normalized()
|
||||
b=(((2*(p*n))*n)-p).normalized()
|
||||
|
||||
# convert back to blender coord
|
||||
return ((b.x + 1.0) / 2.0, -(b.z + 1.0) / 2.0)
|
@ -1,496 +0,0 @@
|
||||
import bpy
|
||||
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop
|
||||
|
||||
class rename_system_props(bpy.types.Operator):
|
||||
name_standard: bpy.props.EnumProperty(
|
||||
name="Name Standard",
|
||||
description="Choose your name standard",
|
||||
items=(
|
||||
("YYC", "YYC Tools Chains", "YYC Tools Chains name standard."),
|
||||
("IMENGYU", "Imengyu Ballance", "Auto grouping name standard for Imengyu/Ballance")
|
||||
),
|
||||
)
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "name_standard")
|
||||
|
||||
class BALLANCE_OT_rename_by_group(rename_system_props):
|
||||
"""Rename object by Virtools groups"""
|
||||
bl_idname = "ballance.rename_by_group"
|
||||
bl_label = "Rename by Group"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
_rename_core(_NameStandard.CKGROUP, _NameStandard.cvt_std_from_str_to_int(self.name_standard))
|
||||
return {'FINISHED'}
|
||||
|
||||
class BALLANCE_OT_convert_name(rename_system_props):
|
||||
"""Convert name from one name standard to another one."""
|
||||
bl_idname = "ballance.convert_name"
|
||||
bl_label = "Convert Name"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
dest_name_standard: bpy.props.EnumProperty(
|
||||
name="Destination Name Standard",
|
||||
description="Choose your name standard",
|
||||
items=(
|
||||
("YYC", "YYC Tools Chains", "YYC Tools Chains name standard."),
|
||||
("IMENGYU", "Imengyu Ballance", "Auto grouping name standard for Imengyu/Ballance")
|
||||
),
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
_rename_core(
|
||||
_NameStandard.cvt_std_from_str_to_int(self.name_standard),
|
||||
_NameStandard.cvt_std_from_str_to_int(self.dest_name_standard))
|
||||
return {'FINISHED'}
|
||||
|
||||
# rewrite draw func
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "name_standard")
|
||||
layout.prop(self, "dest_name_standard")
|
||||
|
||||
class BALLANCE_OT_auto_grouping(rename_system_props):
|
||||
"""Auto Grouping object according to specific name standard."""
|
||||
bl_idname = "ballance.auto_grouping"
|
||||
bl_label = "Auto Grouping"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
_rename_core(_NameStandard.cvt_std_from_str_to_int(self.name_standard), _NameStandard.CKGROUP)
|
||||
return {'FINISHED'}
|
||||
|
||||
# ==========================================
|
||||
# rename misc funcs
|
||||
|
||||
class _ObjectBasicType():
|
||||
COMPONENT = 0
|
||||
|
||||
FLOOR = 1
|
||||
RAIL = 2
|
||||
WOOD = 3
|
||||
STOPPER = 4
|
||||
|
||||
DEPTH_CUBE = 5
|
||||
|
||||
DECORATION = 6
|
||||
|
||||
LEVEL_START = 7
|
||||
LEVEL_END = 8
|
||||
CHECKPOINT = 9
|
||||
RESETPOINT = 10
|
||||
|
||||
class _NameStandard():
|
||||
CKGROUP = 0
|
||||
YYC = 1
|
||||
IMENGYU = 2
|
||||
|
||||
@staticmethod
|
||||
def cvt_std_from_str_to_int(std_str):
|
||||
if std_str == "YYC":
|
||||
return _NameStandard.YYC
|
||||
elif std_str == "IMENGYU":
|
||||
return _NameStandard.IMENGYU
|
||||
else:
|
||||
raise Exception("Unknow name standard.")
|
||||
|
||||
class _NameInfoHelper():
|
||||
def __init__(self, _basic_type):
|
||||
self.basic_type = _basic_type
|
||||
|
||||
# extra field notes:
|
||||
# COMPONENT:
|
||||
# component_type(string)
|
||||
# sector(int)
|
||||
# CHECKPOINT, RESETPOINT:
|
||||
# sector(int)(following Ballance index, checkpoint starts with 1)
|
||||
|
||||
def _get_selected_objects():
|
||||
return bpy.context.view_layer.active_layer_collection.collection.objects
|
||||
|
||||
def _get_sector_from_ckgroup(group_set):
|
||||
# this counter is served for stupid
|
||||
# multi-sector-grouping accident.
|
||||
counter = 0
|
||||
last_matched_sector = ''
|
||||
for i in group_set:
|
||||
regex_result = UTILS_constants.rename_regexCKGroupSector.match(i)
|
||||
if regex_result is not None:
|
||||
last_matched_sector = regex_result.group(1)
|
||||
counter += 1
|
||||
|
||||
if counter != 1:
|
||||
return None
|
||||
else:
|
||||
return last_matched_sector
|
||||
|
||||
|
||||
# ==========================================
|
||||
# rename core funcs
|
||||
|
||||
# NOTE: the implement of this function are copied from
|
||||
# BallanceVirtoolsHelper/bvh/features/mapping/grouping.cpp
|
||||
# ---
|
||||
# YYC Tools Chains name standard is Ballance-compatible name standard.
|
||||
# So this functions also serving for `_get_name_info_from_group` function
|
||||
# to help get sector field from PC/PR elements. In ordinary call(external call)
|
||||
# The final error output should be outputed nromally. But in the call from
|
||||
# `_get_name_info_from_group`, this function should not output any error.
|
||||
# So parameter `call_internal` is served for this work. In common it is False
|
||||
# to let function output error str normally. But only set it to True in
|
||||
# the call from `_get_name_info_from_group` to disable error output.
|
||||
def _get_name_info_from_yyc_name(obj_name, call_internal = False):
|
||||
|
||||
# check component first
|
||||
regex_result = UTILS_constants.rename_regexYYCComponent.match(obj_name)
|
||||
if regex_result is not None:
|
||||
data = _NameInfoHelper(_ObjectBasicType.COMPONENT)
|
||||
data.component_type = regex_result.group(1)
|
||||
data.sector = int(regex_result.group(2))
|
||||
return data
|
||||
|
||||
# check PC PR elements
|
||||
regex_result = UTILS_constants.rename_regexYYCPC.match(obj_name)
|
||||
if regex_result is not None:
|
||||
data = _NameInfoHelper(_ObjectBasicType.CHECKPOINT)
|
||||
data.sector = int(regex_result.group(1))
|
||||
return data
|
||||
regex_result = UTILS_constants.rename_regexYYCPR.match(obj_name)
|
||||
if regex_result is not None:
|
||||
data = _NameInfoHelper(_ObjectBasicType.RESETPOINT)
|
||||
data.sector = int(regex_result.group(1))
|
||||
return data
|
||||
|
||||
# check other unique elements
|
||||
if obj_name == "PS_FourFlames_01":
|
||||
return _NameInfoHelper(_ObjectBasicType.LEVEL_START)
|
||||
if obj_name == "PE_Balloon_01":
|
||||
return _NameInfoHelper(_ObjectBasicType.LEVEL_END)
|
||||
|
||||
# process floors
|
||||
if obj_name.startswith("A_Floor"):
|
||||
return _NameInfoHelper(_ObjectBasicType.FLOOR)
|
||||
if obj_name.startswith("A_Wood"):
|
||||
return _NameInfoHelper(_ObjectBasicType.WOOD)
|
||||
if obj_name.startswith("A_Rail"):
|
||||
return _NameInfoHelper(_ObjectBasicType.RAIL)
|
||||
if obj_name.startswith("A_Stopper"):
|
||||
return _NameInfoHelper(_ObjectBasicType.STOPPER)
|
||||
|
||||
# process others
|
||||
if obj_name.startswith("DepthCubes"):
|
||||
return _NameInfoHelper(_ObjectBasicType.DEPTH_CUBE)
|
||||
if obj_name.startswith("D_"):
|
||||
return _NameInfoHelper(_ObjectBasicType.DECORATION)
|
||||
|
||||
# only output in external calling
|
||||
if not call_internal:
|
||||
print("[ERROR]\t{}:\tName match lost.".format(obj_name))
|
||||
|
||||
return None
|
||||
|
||||
def _get_name_info_from_imengyu_name(obj_name):
|
||||
|
||||
# check component first
|
||||
regex_result = UTILS_constants.rename_regexImengyuComponent.match(obj_name)
|
||||
if regex_result is not None:
|
||||
data = _NameInfoHelper(_ObjectBasicType.COMPONENT)
|
||||
data.component_type = regex_result.group(1)
|
||||
data.sector = int(regex_result.group(2))
|
||||
return data
|
||||
|
||||
# check PC PR elements
|
||||
regex_result = UTILS_constants.rename_regexImengyuPCRComp.match(obj_name)
|
||||
if regex_result is not None:
|
||||
eles_name = regex_result.group(1)
|
||||
if eles_name == 'PC_CheckPoint':
|
||||
data = _NameInfoHelper(_ObjectBasicType.CHECKPOINT)
|
||||
elif eles_name == 'PR_ResetPoint':
|
||||
data = _NameInfoHelper(_ObjectBasicType.RESETPOINT)
|
||||
data.sector = int(regex_result.group(2))
|
||||
return data
|
||||
|
||||
# check other unique elements
|
||||
if obj_name == "PS_LevelStart":
|
||||
return _NameInfoHelper(_ObjectBasicType.LEVEL_START)
|
||||
if obj_name == "PE_LevelEnd":
|
||||
return _NameInfoHelper(_ObjectBasicType.LEVEL_END)
|
||||
|
||||
# process floors
|
||||
if obj_name.startswith("S_Floors"):
|
||||
return _NameInfoHelper(_ObjectBasicType.FLOOR)
|
||||
if obj_name.startswith("S_FloorWoods"):
|
||||
return _NameInfoHelper(_ObjectBasicType.WOOD)
|
||||
if obj_name.startswith("S_FloorRails"):
|
||||
return _NameInfoHelper(_ObjectBasicType.RAIL)
|
||||
if obj_name.startswith("S_FloorStopper"):
|
||||
return _NameInfoHelper(_ObjectBasicType.STOPPER)
|
||||
|
||||
# process others
|
||||
if obj_name.startswith("DepthTestCubes"):
|
||||
return _NameInfoHelper(_ObjectBasicType.DEPTH_CUBE)
|
||||
if obj_name.startswith("O_"):
|
||||
return _NameInfoHelper(_ObjectBasicType.DECORATION)
|
||||
|
||||
print("[ERROR]\t{}:\tName match lost.".format(obj_name))
|
||||
return None
|
||||
|
||||
def _get_name_info_from_group(obj):
|
||||
group_list = UTILS_virtools_prop.get_virtools_group_data(obj)
|
||||
if len(group_list) == 0:
|
||||
# name it as a decoration
|
||||
return _NameInfoHelper(_ObjectBasicType.DECORATION)
|
||||
|
||||
group_set = set(group_list)
|
||||
|
||||
# try to filter unique elements first
|
||||
set_result = UTILS_constants.rename_uniqueComponentsGroupName.intersection(group_set)
|
||||
if len(set_result) == 1:
|
||||
# get it
|
||||
gotten_group_name = (list(set_result))[0]
|
||||
if gotten_group_name == 'PS_Levelstart':
|
||||
return _NameInfoHelper(_ObjectBasicType.LEVEL_START)
|
||||
elif gotten_group_name == 'PE_Levelende':
|
||||
return _NameInfoHelper(_ObjectBasicType.LEVEL_END)
|
||||
elif gotten_group_name == 'PC_Checkpoints' or gotten_group_name == 'PR_Resetpoints':
|
||||
# these type's data should be gotten from its name
|
||||
# use _get_name_info_from_yyc_name to get it
|
||||
# _get_name_info_from_yyc_name is Ballance-compatible name standard
|
||||
data = _get_name_info_from_yyc_name(obj.name, call_internal=True)
|
||||
if data is None:
|
||||
print("[ERROR]\t{}:\tPC_Checkpoints or PR_Resetpoints detected. But couldn't get sector from name.".format(obj.name))
|
||||
return None
|
||||
if data.basic_type != _ObjectBasicType.CHECKPOINT and data.basic_type != _ObjectBasicType.RESETPOINT:
|
||||
# check whether it is checkpoint or resetpoint
|
||||
# if not, it mean that we got error data from name
|
||||
# return None instead
|
||||
print("[ERROR]\t{}:\tPC_Checkpoints or PR_Resetpoints detected. But name is illegal.".format(obj.name))
|
||||
return None
|
||||
# otherwise return data
|
||||
return data
|
||||
else:
|
||||
print("[ERROR]\t{}:\tThe match of Unique Component lost.".format(obj.name))
|
||||
return None
|
||||
elif len(set_result) != 0:
|
||||
# must be a weird grouping, report it
|
||||
print("[ERROR]\t{}:\tA Multi-grouping Unique Component.".format(obj.name))
|
||||
return None
|
||||
|
||||
# distinguish normal elements
|
||||
set_result = UTILS_constants.rename_normalComponentsGroupName.intersection(group_set)
|
||||
if len(set_result) == 1:
|
||||
# get it
|
||||
# now try get its sector
|
||||
gotten_elements = (tuple(set_result))[0]
|
||||
gotten_sector = _get_sector_from_ckgroup(group_set)
|
||||
if gotten_sector is None:
|
||||
# fail to get sector
|
||||
print("[ERROR]\t{}:\tComponent detected. But couldn't get sector from CKGroup data.".format(obj.name))
|
||||
return None
|
||||
|
||||
data = _NameInfoHelper(_ObjectBasicType.COMPONENT)
|
||||
data.component_type = gotten_elements
|
||||
data.sector = int(gotten_sector)
|
||||
return data
|
||||
elif len(set_result) != 0:
|
||||
# must be a weird grouping, report it
|
||||
print("[ERROR]\t{}:\tA Multi-grouping Component.".format(obj.name))
|
||||
return None
|
||||
|
||||
# distinguish road
|
||||
if 'Phys_FloorRails' in group_set:
|
||||
# rail
|
||||
return _NameInfoHelper(_ObjectBasicType.RAIL)
|
||||
elif 'Phys_Floors' in group_set:
|
||||
# distinguish it between Floor and Wood
|
||||
floor_result =UTILS_constants.rename_floorGroupTester.intersection(group_set)
|
||||
rail_result = UTILS_constants.rename_woodGroupTester.intersection(group_set)
|
||||
if len(floor_result) > 0 and len(rail_result) == 0:
|
||||
return _NameInfoHelper(_ObjectBasicType.FLOOR)
|
||||
elif len(floor_result) == 0 and len(rail_result) > 0:
|
||||
return _NameInfoHelper(_ObjectBasicType.WOOD)
|
||||
else:
|
||||
print("[WARNING]\t{}:\tCan't distinguish between Floors and Rails. Suppose it is Floors".format(obj.name))
|
||||
return _NameInfoHelper(_ObjectBasicType.FLOOR)
|
||||
elif 'Phys_FloorStopper' in group_set:
|
||||
return _NameInfoHelper(_ObjectBasicType.STOPPER)
|
||||
elif 'DepthTestCubes' in group_set:
|
||||
return _NameInfoHelper(_ObjectBasicType.DEPTH_CUBE)
|
||||
|
||||
# no matched
|
||||
print("[ERROR]\t{}:\tGroup match lost.".format(obj.name))
|
||||
return None
|
||||
|
||||
def _set_for_yyc_name(obj, name_info):
|
||||
basic_type = name_info.basic_type
|
||||
if basic_type == _ObjectBasicType.DECORATION:
|
||||
obj.name = "D_"
|
||||
|
||||
elif basic_type == _ObjectBasicType.LEVEL_START:
|
||||
obj.name = "PS_FourFlames_01"
|
||||
elif basic_type == _ObjectBasicType.LEVEL_END:
|
||||
obj.name = "PE_Balloon_01"
|
||||
elif basic_type == _ObjectBasicType.RESETPOINT:
|
||||
obj.name = "PR_Resetpoint_{:0>2d}".format(name_info.sector)
|
||||
elif basic_type == _ObjectBasicType.CHECKPOINT:
|
||||
obj.name = "PC_TwoFlames_{:0>2d}".format(name_info.sector)
|
||||
|
||||
elif basic_type == _ObjectBasicType.DEPTH_CUBE:
|
||||
obj.name = "DepthCubes_"
|
||||
|
||||
elif basic_type == _ObjectBasicType.FLOOR:
|
||||
obj.name = "A_Floor_"
|
||||
elif basic_type == _ObjectBasicType.WOOD:
|
||||
obj.name = "A_Wood_"
|
||||
elif basic_type == _ObjectBasicType.RAIL:
|
||||
obj.name = "A_Rail_"
|
||||
elif basic_type == _ObjectBasicType.STOPPER:
|
||||
obj.name = "A_Stopper_"
|
||||
|
||||
elif basic_type == _ObjectBasicType.COMPONENT:
|
||||
obj.name = "{}_{:0>2d}_".format(name_info.component_type, name_info.sector)
|
||||
|
||||
|
||||
def _set_for_imengyu_name(obj, name_info):
|
||||
basic_type = name_info.basic_type
|
||||
if basic_type == _ObjectBasicType.DECORATION:
|
||||
obj.name = "O_"
|
||||
|
||||
elif basic_type == _ObjectBasicType.LEVEL_START:
|
||||
obj.name = "PS_LevelStart"
|
||||
elif basic_type == _ObjectBasicType.LEVEL_END:
|
||||
obj.name = "PE_LevelEnd"
|
||||
elif basic_type == _ObjectBasicType.RESETPOINT:
|
||||
obj.name = "PR_ResetPoint:{:d}".format(name_info.sector)
|
||||
elif basic_type == _ObjectBasicType.CHECKPOINT:
|
||||
obj.name = "PC_CheckPoint:{:d}".format(name_info.sector + 1)
|
||||
|
||||
elif basic_type == _ObjectBasicType.DEPTH_CUBE:
|
||||
obj.name = "DepthTestCubes"
|
||||
|
||||
elif basic_type == _ObjectBasicType.FLOOR:
|
||||
obj.name = "S_Floors"
|
||||
elif basic_type == _ObjectBasicType.WOOD:
|
||||
obj.name = "S_FloorWoods"
|
||||
elif basic_type == _ObjectBasicType.RAIL:
|
||||
obj.name = "S_FloorRails"
|
||||
elif basic_type == _ObjectBasicType.STOPPER:
|
||||
obj.name = "S_FloorStopper"
|
||||
|
||||
elif basic_type == _ObjectBasicType.COMPONENT:
|
||||
obj.name = "{}:{}:{:d}".format(name_info.component_type, obj.name.replace(":", "_"), name_info.sector)
|
||||
|
||||
# NOTE: the implement of this function are copied from
|
||||
# BallanceVirtoolsHelper/bvh/features/mapping/grouping.cpp
|
||||
def _set_for_group(obj, name_info):
|
||||
gps = []
|
||||
basic_type = name_info.basic_type
|
||||
|
||||
if basic_type == _ObjectBasicType.DECORATION:
|
||||
# decoration do not need grouping
|
||||
pass
|
||||
|
||||
elif basic_type == _ObjectBasicType.LEVEL_START:
|
||||
gps.append("PS_Levelstart")
|
||||
elif basic_type == _ObjectBasicType.LEVEL_END:
|
||||
gps.append("PE_Levelende")
|
||||
elif basic_type == _ObjectBasicType.RESETPOINT:
|
||||
gps.append("PC_Checkpoints")
|
||||
elif basic_type == _ObjectBasicType.CHECKPOINT:
|
||||
gps.append("PR_Resetpoints")
|
||||
|
||||
elif basic_type == _ObjectBasicType.DEPTH_CUBE:
|
||||
gps.append("DepthTestCubes")
|
||||
|
||||
elif basic_type == _ObjectBasicType.FLOOR:
|
||||
gps.append("Phys_Floors")
|
||||
gps.append("Sound_HitID_01")
|
||||
gps.append("Sound_RollID_01")
|
||||
gps.append("Shadow")
|
||||
elif basic_type == _ObjectBasicType.WOOD:
|
||||
gps.append("Phys_FloorRails")
|
||||
gps.append("Sound_HitID_03")
|
||||
gps.append("Sound_RollID_03")
|
||||
elif basic_type == _ObjectBasicType.RAIL:
|
||||
gps.append("Phys_Floors")
|
||||
gps.append("Sound_HitID_02")
|
||||
gps.append("Sound_RollID_02")
|
||||
gps.append("Shadow")
|
||||
elif basic_type == _ObjectBasicType.STOPPER:
|
||||
gps.append("Phys_FloorStopper")
|
||||
|
||||
elif basic_type == _ObjectBasicType.COMPONENT:
|
||||
gps.append(name_info.component_type)
|
||||
|
||||
# set compabitility for 999 sector loader
|
||||
if (name_info.sector == 9):
|
||||
gps.append("Sector_9")
|
||||
else:
|
||||
gps.append("Sector_{:0>2d}".format(name_info.sector))
|
||||
|
||||
|
||||
# apply to custom property
|
||||
UTILS_virtools_prop.fill_virtools_group_data(obj, tuple(gps))
|
||||
|
||||
# ==========================================
|
||||
# assemble funcs
|
||||
|
||||
def _get_data(obj, standard):
|
||||
if standard == _NameStandard.YYC:
|
||||
return _get_name_info_from_yyc_name(obj.name)
|
||||
elif standard == _NameStandard.IMENGYU:
|
||||
return _get_name_info_from_imengyu_name(obj.name)
|
||||
elif standard == _NameStandard.CKGROUP:
|
||||
return _get_name_info_from_group(obj)
|
||||
else:
|
||||
raise Exception("Unknow standard")
|
||||
|
||||
def _set_data(obj, name_info, standard):
|
||||
if standard == _NameStandard.YYC:
|
||||
return _set_for_yyc_name(obj, name_info)
|
||||
elif standard == _NameStandard.IMENGYU:
|
||||
return _set_for_imengyu_name(obj, name_info)
|
||||
elif standard == _NameStandard.CKGROUP:
|
||||
return _set_for_group(obj, name_info)
|
||||
else:
|
||||
raise Exception("Unknow standard")
|
||||
|
||||
def _rename_core(source_std, dest_std):
|
||||
if source_std == dest_std:
|
||||
# if source == dest
|
||||
# we do not to do anything
|
||||
return
|
||||
|
||||
failed_obj_counter = 0
|
||||
all_obj_counter = 0
|
||||
|
||||
print('============')
|
||||
print('Rename system report')
|
||||
print('------------')
|
||||
for obj in _get_selected_objects():
|
||||
all_obj_counter += 1
|
||||
info = _get_data(obj, source_std)
|
||||
if info is None:
|
||||
failed_obj_counter += 1
|
||||
continue
|
||||
|
||||
_set_data(obj, info, dest_std)
|
||||
|
||||
print('------------')
|
||||
print('All/failed - {}/{}'.format(all_obj_counter, failed_obj_counter))
|
||||
print('============')
|
||||
|
||||
UTILS_functions.show_message_box(
|
||||
('Rename system report',
|
||||
'View console to get more detail',
|
||||
'All: {}'.format(all_obj_counter),
|
||||
'Failed: {}'.format(failed_obj_counter)),
|
||||
"Info",
|
||||
"INFO"
|
||||
)
|
@ -1,56 +0,0 @@
|
||||
import bpy, mathutils
|
||||
from . import UTILS_constants, UTILS_functions
|
||||
|
||||
# ================================================= actual add
|
||||
|
||||
class BALLANCE_OT_add_components(bpy.types.Operator):
|
||||
"""Add sector related elements"""
|
||||
bl_idname = "ballance.add_components"
|
||||
bl_label = "Add elements"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
elements_type: bpy.props.EnumProperty(
|
||||
name="Type",
|
||||
description="This element type",
|
||||
items=tuple(map(lambda x: (x, x, ""), UTILS_constants.bmfile_componentList)),
|
||||
)
|
||||
|
||||
attentionElements = ["PC_TwoFlames", "PR_Resetpoint"]
|
||||
uniqueElements = ["PS_FourFlames", "PE_Balloon"]
|
||||
|
||||
elements_sector: bpy.props.IntProperty(
|
||||
name="Sector",
|
||||
description="Define which sector the object will be grouped in",
|
||||
min=1,
|
||||
max=8,
|
||||
default=1,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
# get name
|
||||
if self.elements_type in self.uniqueElements:
|
||||
finalObjectName = self.elements_type + "_01"
|
||||
elif self.elements_type in self.attentionElements:
|
||||
finalObjectName = self.elements_type + "_0" + str(self.elements_sector)
|
||||
else:
|
||||
finalObjectName = self.elements_type + "_0" + str(self.elements_sector) + "_"
|
||||
|
||||
# create object
|
||||
loadedMesh = UTILS_functions.load_component(
|
||||
UTILS_constants.bmfile_componentList.index(self.elements_type))
|
||||
obj = bpy.data.objects.new(finalObjectName, loadedMesh)
|
||||
UTILS_functions.add_into_scene_and_move_to_cursor(obj)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "elements_type")
|
||||
if self.elements_type not in self.uniqueElements:
|
||||
layout.prop(self, "elements_sector")
|
||||
if self.elements_type in self.attentionElements:
|
||||
layout.label(text="Please note that sector is suffix.")
|
@ -1,502 +0,0 @@
|
||||
import bpy,mathutils
|
||||
import os, math
|
||||
import ast
|
||||
from bpy_extras import io_utils,node_shader_utils
|
||||
# from bpy_extras.io_utils import unpack_list
|
||||
from bpy_extras.image_utils import load_image
|
||||
from . import UTILS_constants, UTILS_functions, UTILS_safe_eval
|
||||
|
||||
class BALLANCE_OT_add_floors(bpy.types.Operator):
|
||||
"""Add Ballance floor"""
|
||||
bl_idname = "ballance.add_floors"
|
||||
bl_label = "Add floor"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
floor_type: bpy.props.EnumProperty(
|
||||
name="Type",
|
||||
description="Floor type",
|
||||
items=tuple((x, x, "") for x in UTILS_constants.floor_blockDict.keys()),
|
||||
)
|
||||
|
||||
expand_length_1 : bpy.props.IntProperty(
|
||||
name="D1 length",
|
||||
description="The length of expand direction 1",
|
||||
min=0,
|
||||
default=0,
|
||||
)
|
||||
|
||||
expand_length_2 : bpy.props.IntProperty(
|
||||
name="D2 length",
|
||||
description="The length of expand direction 2",
|
||||
min=0,
|
||||
default=0,
|
||||
)
|
||||
|
||||
height_multiplier : bpy.props.FloatProperty(
|
||||
name="Height",
|
||||
description="The multiplier for height. Default height is 5",
|
||||
min=0.0,
|
||||
default=1.0,
|
||||
)
|
||||
|
||||
use_2d_top : bpy.props.BoolProperty(
|
||||
name="Top edge"
|
||||
)
|
||||
use_2d_right : bpy.props.BoolProperty(
|
||||
name="Right edge"
|
||||
)
|
||||
use_2d_bottom : bpy.props.BoolProperty(
|
||||
name="Bottom edge"
|
||||
)
|
||||
use_2d_left : bpy.props.BoolProperty(
|
||||
name="Left edge"
|
||||
)
|
||||
use_3d_top : bpy.props.BoolProperty(
|
||||
name="Top face"
|
||||
)
|
||||
use_3d_bottom : bpy.props.BoolProperty(
|
||||
name="Bottom face"
|
||||
)
|
||||
|
||||
previous_floor_type = ''
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||
return os.path.isdir(prefs.external_folder)
|
||||
|
||||
def execute(self, context):
|
||||
# get prefs
|
||||
prefs = bpy.context.preferences.addons[__package__].preferences
|
||||
prefs_externalTexture = prefs.external_folder
|
||||
|
||||
# load mesh
|
||||
objmesh = bpy.data.meshes.new('done_')
|
||||
if self.floor_type in UTILS_constants.floor_basicBlockList:
|
||||
_load_basic_floor(
|
||||
objmesh,
|
||||
self.floor_type,
|
||||
'R0',
|
||||
self.height_multiplier,
|
||||
self.expand_length_1,
|
||||
self.expand_length_2,
|
||||
(self.use_2d_top,
|
||||
self.use_2d_right,
|
||||
self.use_2d_bottom,
|
||||
self.use_2d_left,
|
||||
self.use_3d_top,
|
||||
self.use_3d_bottom),
|
||||
(0.0, 0.0, 0.0),
|
||||
prefs_externalTexture)
|
||||
elif self.floor_type in UTILS_constants.floor_derivedBlockList:
|
||||
_load_derived_floor(
|
||||
objmesh,
|
||||
self.floor_type,
|
||||
self.height_multiplier,
|
||||
self.expand_length_1,
|
||||
self.expand_length_2,
|
||||
(self.use_2d_top,
|
||||
self.use_2d_right,
|
||||
self.use_2d_bottom,
|
||||
self.use_2d_left,
|
||||
self.use_3d_top,
|
||||
self.use_3d_bottom),
|
||||
prefs_externalTexture)
|
||||
else:
|
||||
raise Exception("Fatal error: unknow floor type.")
|
||||
|
||||
# normalization mesh
|
||||
objmesh.validate(clean_customdata=False)
|
||||
objmesh.update(calc_edges=False, calc_edges_loose=False)
|
||||
|
||||
# create object and link it
|
||||
obj=bpy.data.objects.new('A_Floor_BMERevenge_', objmesh)
|
||||
UTILS_functions.add_into_scene_and_move_to_cursor(obj)
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
# get floor prototype
|
||||
floor_prototype = UTILS_constants.floor_blockDict[self.floor_type]
|
||||
|
||||
# try sync default value
|
||||
if self.previous_floor_type != self.floor_type:
|
||||
self.previous_floor_type = self.floor_type
|
||||
|
||||
default_sides = floor_prototype['DefaultSideConfig']
|
||||
self.use_2d_top = default_sides['UseTwoDTop']
|
||||
self.use_2d_right = default_sides['UseTwoDRight']
|
||||
self.use_2d_bottom = default_sides['UseTwoDBottom']
|
||||
self.use_2d_left = default_sides['UseTwoDLeft']
|
||||
self.use_3d_top = default_sides['UseThreeDTop']
|
||||
self.use_3d_bottom = default_sides['UseThreeDBottom']
|
||||
|
||||
# show property
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
col.label(text="Basic param")
|
||||
col.prop(self, "floor_type")
|
||||
col.prop(self, "height_multiplier")
|
||||
|
||||
col.separator()
|
||||
col.label(text="Expand")
|
||||
if floor_prototype['ExpandType'] == 'Column' or floor_prototype['ExpandType'] == 'Freedom':
|
||||
col.prop(self, "expand_length_1")
|
||||
if floor_prototype['ExpandType'] == 'Freedom':
|
||||
col.prop(self, "expand_length_2")
|
||||
col.label(text="Unit size: " + floor_prototype['UnitSize'])
|
||||
col.label(text="Expand mode: " + floor_prototype['ExpandType'])
|
||||
grids = col.grid_flow(row_major=True, columns=3)
|
||||
grids.separator()
|
||||
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][0])
|
||||
grids.separator()
|
||||
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][3])
|
||||
grids.template_icon(icon_value = UTILS_constants.icons_floorDict[self.floor_type])
|
||||
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][1])
|
||||
grids.separator()
|
||||
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][2])
|
||||
grids.separator()
|
||||
|
||||
col.separator()
|
||||
col.label(text="Faces")
|
||||
row = col.row()
|
||||
row.prop(self, "use_3d_top")
|
||||
row.prop(self, "use_3d_bottom")
|
||||
|
||||
col.separator()
|
||||
col.label(text="Sides")
|
||||
grids = col.grid_flow(row_major=True, columns=3)
|
||||
grids.separator()
|
||||
grids.prop(self, "use_2d_top")
|
||||
grids.separator()
|
||||
grids.prop(self, "use_2d_left")
|
||||
grids.template_icon(icon_value = UTILS_constants.icons_floorDict[self.floor_type])
|
||||
grids.prop(self, "use_2d_right")
|
||||
grids.separator()
|
||||
grids.prop(self, "use_2d_bottom")
|
||||
grids.separator()
|
||||
|
||||
def _face_fallback(normal_face, expand_face, height):
|
||||
if expand_face == None:
|
||||
return normal_face
|
||||
|
||||
if height <= 1.0:
|
||||
return normal_face
|
||||
else:
|
||||
return expand_face
|
||||
|
||||
def _create_or_get_material(material_name, prefs_externalTexture):
|
||||
# WARNING: this code is shared with bm_import_export
|
||||
deconflict_mtl_name = "BMERevenge_" + material_name
|
||||
|
||||
# create or get material
|
||||
(mtl, skip_init) = UTILS_functions.create_instance_with_option(
|
||||
UTILS_constants.BmfileInfoType.MATERIAL,
|
||||
deconflict_mtl_name, 'CURRENT'
|
||||
)
|
||||
if skip_init:
|
||||
return mtl
|
||||
|
||||
# initialize material parameter
|
||||
# load texture first
|
||||
texture_filename = UTILS_constants.floor_textureReflactionMap[material_name]
|
||||
deconflict_texture_name = "BMERevenge_" + texture_filename
|
||||
(texture, skip_init) = UTILS_functions.create_instance_with_option(
|
||||
UTILS_constants.BmfileInfoType.TEXTURE,
|
||||
deconflict_texture_name, 'CURRENT',
|
||||
extra_texture_path = prefs_externalTexture, extra_texture_filename = texture_filename
|
||||
)
|
||||
|
||||
# iterate material statistic to get corresponding mtl data
|
||||
for try_item in UTILS_constants.floor_materialStatistic:
|
||||
if material_name in try_item['member']:
|
||||
# got it
|
||||
# set material data
|
||||
# all floor do not have any transparency props, so we provide 4 False value.
|
||||
UTILS_functions.create_blender_material(mtl,
|
||||
(True,
|
||||
try_item['data']['ambient'], try_item['data']['diffuse'],
|
||||
try_item['data']['specular'], try_item['data']['emissive'],
|
||||
try_item['data']['power'],
|
||||
False, False, False, False,
|
||||
texture)
|
||||
)
|
||||
break
|
||||
|
||||
# return mtl
|
||||
return mtl
|
||||
|
||||
def _solve_vec_data(str_data, d1, d2, d3, unit):
|
||||
(cmd_x, cmd_y, cmd_z) = map(lambda x: x.strip(), str_data.split(','))
|
||||
|
||||
# calc raw expand data
|
||||
raw_d1 = d1 * unit
|
||||
raw_d2 = d2 * unit
|
||||
raw_d3 = (d3 - 1) * 5.0 # the 3d heigh unit of ballance floor is always 5.0
|
||||
|
||||
# do safe eval
|
||||
return (
|
||||
UTILS_safe_eval.do_vec_calc(cmd_x, raw_d1, raw_d2, raw_d3),
|
||||
UTILS_safe_eval.do_vec_calc(cmd_y, raw_d1, raw_d2, raw_d3),
|
||||
UTILS_safe_eval.do_vec_calc(cmd_z, raw_d1, raw_d2, raw_d3)
|
||||
)
|
||||
|
||||
def _rotate_translate_vec(_vec, rotation, unit, extra_translate):
|
||||
vec = (
|
||||
_vec[0] - unit / 2,
|
||||
_vec[1] - unit / 2,
|
||||
_vec[2]
|
||||
)
|
||||
|
||||
if rotation == 'R0':
|
||||
coso=1
|
||||
sino=0
|
||||
elif rotation == 'R90':
|
||||
coso=0
|
||||
sino=1
|
||||
elif rotation == 'R180':
|
||||
coso=-1
|
||||
sino=0
|
||||
elif rotation == 'R270':
|
||||
coso=0
|
||||
sino=-1
|
||||
|
||||
return (
|
||||
coso * vec[0] - sino * vec[1] + unit / 2 + extra_translate[0],
|
||||
sino * vec[0] + coso * vec[1] + unit / 2 + extra_translate[1],
|
||||
vec[2] + extra_translate[2]
|
||||
)
|
||||
|
||||
|
||||
def _solve_uv_data(str_data, d1, d2, d3, unit):
|
||||
(cmd_u, cmd_v) = map(lambda x: x.strip(), str_data.split(','))
|
||||
|
||||
# calc raw expand data
|
||||
raw_d1 = d1 * unit
|
||||
raw_d2 = d2 * unit
|
||||
raw_d3 = float(d3 - 1) # the uv heigh unit of ballance floor is always 1.0
|
||||
|
||||
# do safe eval
|
||||
return (
|
||||
UTILS_safe_eval.do_vec_calc(cmd_u, raw_d1, raw_d2, raw_d3),
|
||||
UTILS_safe_eval.do_vec_calc(cmd_v, raw_d1, raw_d2, raw_d3)
|
||||
)
|
||||
|
||||
def _solve_normal_data(point1, point2, point3):
|
||||
p1 = mathutils.Vector(point1)
|
||||
p2 = mathutils.Vector(point2)
|
||||
p3 = mathutils.Vector(point3)
|
||||
|
||||
vector1 = p2 - p1
|
||||
vector2 = p3 - p2
|
||||
|
||||
# do vector x mutiply
|
||||
# vector1 x vector2
|
||||
corss_mul = vector1.cross(vector2)
|
||||
|
||||
# do a normalization
|
||||
corss_mul.normalize()
|
||||
|
||||
return (corss_mul[0], corss_mul[1], corss_mul[2])
|
||||
|
||||
def _solve_expand_param(str_data, d1, d2):
|
||||
(cmd_d1, cmd_d2) = map(lambda x: x.strip(), str_data.split(','))
|
||||
|
||||
# do safe eval
|
||||
return (
|
||||
UTILS_safe_eval.do_expand_calc(cmd_d1, d1, d2),
|
||||
UTILS_safe_eval.do_expand_calc(cmd_d2, d1, d2)
|
||||
)
|
||||
|
||||
def _virtual_foreach_set(collection, field, base_num, data):
|
||||
counter = 0
|
||||
for i in data:
|
||||
exec("a[j]." + field + "=q", {}, {
|
||||
'a': collection,
|
||||
'j': counter + base_num,
|
||||
'q': i
|
||||
})
|
||||
counter+=1
|
||||
|
||||
|
||||
|
||||
'''
|
||||
sides_struct should be a tuple and it always have 6 bool items
|
||||
|
||||
(use_2d_top, use_2d_right, use_2d_bottom, use_2d_left, use_3d_top, use_3d_bottom)
|
||||
|
||||
WARNING: this code is shared with bm import export
|
||||
|
||||
'''
|
||||
def _load_basic_floor(mesh, floor_type, rotation, height_multiplier, d1, d2, sides_struct, extra_translate, prefs_externalTexture):
|
||||
floor_prototype = UTILS_constants.floor_blockDict[floor_type]
|
||||
|
||||
# set some unit
|
||||
if floor_prototype['UnitSize'] == 'Small':
|
||||
block_3dworld_unit = 2.5
|
||||
block_uvworld_unit = 0.5
|
||||
elif floor_prototype['UnitSize'] == 'Large':
|
||||
block_3dworld_unit = 5.0
|
||||
block_uvworld_unit = 1.0
|
||||
|
||||
# got all needed faces
|
||||
needCreatedFaces = []
|
||||
if sides_struct[0]:
|
||||
needCreatedFaces.append(_face_fallback(floor_prototype['TwoDTopSide'], floor_prototype['TwoDTopSideExpand'], height_multiplier))
|
||||
if sides_struct[1]:
|
||||
needCreatedFaces.append(_face_fallback(floor_prototype['TwoDRightSide'], floor_prototype['TwoDRightSideExpand'], height_multiplier))
|
||||
if sides_struct[2]:
|
||||
needCreatedFaces.append(_face_fallback(floor_prototype['TwoDBottomSide'], floor_prototype['TwoDBottomSideExpand'], height_multiplier))
|
||||
if sides_struct[3]:
|
||||
needCreatedFaces.append(_face_fallback(floor_prototype['TwoDLeftSide'], floor_prototype['TwoDLeftSideExpand'], height_multiplier))
|
||||
if sides_struct[4]:
|
||||
needCreatedFaces.append(floor_prototype['ThreeDTopFace'])
|
||||
if sides_struct[5]:
|
||||
needCreatedFaces.append(floor_prototype['ThreeDBottomFace'])
|
||||
|
||||
# resolve face
|
||||
# solve material first
|
||||
materialDict = {}
|
||||
allmat = mesh.materials[:]
|
||||
counter = len(allmat)
|
||||
for face_define in needCreatedFaces:
|
||||
for face in face_define['Faces']:
|
||||
new_texture = face['Textures']
|
||||
if new_texture not in materialDict.keys():
|
||||
# try get from existed solt
|
||||
pending_material = _create_or_get_material(new_texture, prefs_externalTexture)
|
||||
if pending_material not in allmat:
|
||||
# no matched. add it
|
||||
mesh.materials.append(pending_material)
|
||||
materialDict[new_texture] = counter
|
||||
counter += 1
|
||||
else:
|
||||
# use existed index
|
||||
materialDict[new_texture] = allmat.index(pending_material)
|
||||
|
||||
# now, we can process real mesh
|
||||
# load existed base count
|
||||
global_offset_vec = len(mesh.vertices)
|
||||
global_offset_polygons = len(mesh.polygons)
|
||||
global_offset_loops = len(mesh.loops)
|
||||
vecList = []
|
||||
uvList = []
|
||||
normalList = []
|
||||
faceList = []
|
||||
faceIndList = []
|
||||
faceMatList = []
|
||||
for face_define in needCreatedFaces:
|
||||
base_indices = len(vecList)
|
||||
for vec in face_define['Vertices']:
|
||||
vecList.append(_rotate_translate_vec(
|
||||
_solve_vec_data(vec, d1, d2, height_multiplier, block_3dworld_unit),
|
||||
rotation, block_3dworld_unit, extra_translate))
|
||||
|
||||
for face in face_define['Faces']:
|
||||
if face['Type'] == 'RECTANGLE':
|
||||
# rectangle
|
||||
vec_indices = tuple(map(lambda x: x + base_indices, face['Indices']))
|
||||
indCount = 4
|
||||
elif face['Type'] == 'TRIANGLE':
|
||||
# triangle
|
||||
vec_indices = tuple(map(lambda x: x + base_indices, face['Indices']))
|
||||
indCount = 3
|
||||
|
||||
# we need calc normal and push it into list
|
||||
point_normal = _solve_normal_data(vecList[vec_indices[0]], vecList[vec_indices[1]], vecList[vec_indices[2]])
|
||||
for i in range(indCount):
|
||||
normalList.append(point_normal)
|
||||
|
||||
# push indices into list
|
||||
for i in range(indCount):
|
||||
faceList.append(vec_indices[i] + global_offset_vec)
|
||||
|
||||
# push uvs into list
|
||||
for i in range(indCount):
|
||||
uvList.append(_solve_uv_data(face['UVs'][i], d1, d2, height_multiplier, block_uvworld_unit))
|
||||
|
||||
# push material into list
|
||||
faceMatList.append(materialDict[face['Textures']])
|
||||
|
||||
# push face vec count into list
|
||||
faceIndList.append(indCount)
|
||||
|
||||
# push data into blender struct
|
||||
mesh.vertices.add(len(vecList))
|
||||
mesh.loops.add(len(faceList))
|
||||
mesh.polygons.add(len(faceMatList))
|
||||
mesh.create_normals_split()
|
||||
if mesh.uv_layers.active is None:
|
||||
# if no uv, create it
|
||||
mesh.uv_layers.new(do_init=False)
|
||||
|
||||
_virtual_foreach_set(mesh.vertices, "co", global_offset_vec, vecList)
|
||||
_virtual_foreach_set(mesh.loops, "vertex_index", global_offset_loops, faceList)
|
||||
_virtual_foreach_set(mesh.loops, "normal", global_offset_loops, normalList)
|
||||
_virtual_foreach_set(mesh.uv_layers[0].data, "uv", global_offset_loops, uvList)
|
||||
|
||||
cache_counter = 0
|
||||
for i in range(len(faceMatList)):
|
||||
indCount = faceIndList[i]
|
||||
mesh.polygons[i + global_offset_polygons].loop_start = global_offset_loops + cache_counter
|
||||
mesh.polygons[i + global_offset_polygons].loop_total = indCount
|
||||
mesh.polygons[i + global_offset_polygons].material_index = faceMatList[i]
|
||||
mesh.polygons[i + global_offset_polygons].use_smooth = True
|
||||
cache_counter += indCount
|
||||
|
||||
|
||||
def _load_derived_floor(mesh, floor_type, height_multiplier, d1, d2, sides_struct, prefs_externalTexture):
|
||||
floor_prototype = UTILS_constants.floor_blockDict[floor_type]
|
||||
|
||||
# set some unit
|
||||
if floor_prototype['UnitSize'] == 'Small':
|
||||
block_3dworld_unit = 2.5
|
||||
elif floor_prototype['UnitSize'] == 'Large':
|
||||
block_3dworld_unit = 5.0
|
||||
|
||||
# construct face dict
|
||||
sides_dict = {
|
||||
'True': True,
|
||||
'False': False,
|
||||
'2dTop': sides_struct[0],
|
||||
'2dRight': sides_struct[1],
|
||||
'2dBottom': sides_struct[2],
|
||||
'2dLeft': sides_struct[3],
|
||||
'3dTop': sides_struct[4],
|
||||
'3dBottom': sides_struct[5]
|
||||
}
|
||||
|
||||
# iterate smahsed blocks
|
||||
for blk in floor_prototype['SmashedBlocks']:
|
||||
# calc basic parameter
|
||||
start_pos = _solve_vec_data(blk['StartPosition'], d1, d2, height_multiplier, block_3dworld_unit)
|
||||
expand_param = _solve_expand_param(blk['ExpandParam'], d1, d2)
|
||||
|
||||
# re-calc height multiply for this block.
|
||||
# if the translation of Z is not zero
|
||||
if start_pos[2] != 0.0:
|
||||
raw_height = (height_multiplier - 1) * 5.0 + start_pos[2] # test height
|
||||
blk_height = max(0, (raw_height / 5.0) + 1) # prevent minus height
|
||||
else:
|
||||
blk_height = height_multiplier
|
||||
|
||||
# get face data
|
||||
sides_data = tuple(sides_dict[x] for x in blk['SideSync'].split(';'))
|
||||
|
||||
# call basic floor creator
|
||||
_load_basic_floor(
|
||||
mesh,
|
||||
blk['Type'],
|
||||
blk['Rotation'],
|
||||
blk_height,
|
||||
expand_param[0],
|
||||
expand_param[1],
|
||||
sides_data,
|
||||
start_pos,
|
||||
prefs_externalTexture
|
||||
)
|
||||
|
||||
|
@ -1,77 +0,0 @@
|
||||
import bpy, mathutils
|
||||
from . import UTILS_functions
|
||||
|
||||
class BALLANCE_OT_add_rails(bpy.types.Operator):
|
||||
"""Add rail"""
|
||||
bl_idname = "ballance.add_rails"
|
||||
bl_label = "Add rail section"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
rail_type: bpy.props.EnumProperty(
|
||||
name="Type",
|
||||
description="Rail type",
|
||||
items=(('MONO', "Monorail", ""),
|
||||
('DOUBLE', "Rail", ""),
|
||||
),
|
||||
)
|
||||
|
||||
rail_radius: bpy.props.FloatProperty(
|
||||
name="Rail radius",
|
||||
description="Define rail section radius",
|
||||
default=0.375,
|
||||
)
|
||||
|
||||
rail_span: bpy.props.FloatProperty(
|
||||
name="Rail span",
|
||||
description="Define rail span",
|
||||
default=3.75,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
# create one first
|
||||
bpy.ops.mesh.primitive_circle_add(vertices=8,
|
||||
radius=self.rail_radius,
|
||||
fill_type='NOTHING',
|
||||
calc_uvs=False,
|
||||
enter_editmode=False,
|
||||
align='WORLD',
|
||||
location=(0.0, 0.0, 0.0))
|
||||
|
||||
firstObj = bpy.context.selected_objects[0]
|
||||
|
||||
# for double rail
|
||||
if self.rail_type == 'DOUBLE':
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
bpy.ops.mesh.primitive_circle_add(vertices=8,
|
||||
radius=self.rail_radius,
|
||||
fill_type='NOTHING',
|
||||
calc_uvs=False,
|
||||
enter_editmode=False,
|
||||
align='WORLD',
|
||||
location=(self.rail_span, 0.0, 0.0))
|
||||
secondObj = bpy.context.selected_objects[0]
|
||||
|
||||
# merge
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
bpy.context.view_layer.objects.active = firstObj
|
||||
firstObj.select_set(True)
|
||||
secondObj.select_set(True)
|
||||
bpy.ops.object.join()
|
||||
|
||||
# apply 3d cursor
|
||||
UTILS_functions.move_to_cursor(firstObj)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.prop(self, "rail_type")
|
||||
layout.prop(self, "rail_radius")
|
||||
if self.rail_type == 'DOUBLE':
|
||||
layout.prop(self, "rail_span")
|
||||
|
@ -1,179 +0,0 @@
|
||||
import bpy
|
||||
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop
|
||||
|
||||
class BALLANCE_OT_select_virtools_group(UTILS_virtools_prop.common_group_name_props):
|
||||
"""Select objects by Virtools Group."""
|
||||
bl_idname = "ballance.select_virtools_group"
|
||||
bl_label = "Select by Virtools Group"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
merge_selection: bpy.props.BoolProperty(
|
||||
name="Merge Selection",
|
||||
description="Merge selection, rather than re-select them.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
ignore_hide: bpy.props.BoolProperty(
|
||||
name="Ignore Hide Property",
|
||||
description="Select objects without considering visibility.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
# iterate object
|
||||
for obj in bpy.context.scene.objects:
|
||||
# ignore hidden objects
|
||||
if (not self.ignore_hide) and obj.hide_get() == True:
|
||||
continue
|
||||
|
||||
# check group
|
||||
if UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string()):
|
||||
# select object
|
||||
obj.select_set(True)
|
||||
else:
|
||||
# if not in merge mode, deselect them
|
||||
if not self.merge_selection:
|
||||
obj.select_set(False)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row()
|
||||
row.prop(self, 'ignore_hide')
|
||||
row.prop(self, 'merge_selection')
|
||||
|
||||
layout.separator()
|
||||
self.parent_draw(layout)
|
||||
|
||||
class BALLANCE_OT_filter_virtools_group(UTILS_virtools_prop.common_group_name_props):
|
||||
"""Filter objects by Virtools Group."""
|
||||
bl_idname = "ballance.filter_virtools_group"
|
||||
bl_label = "Filter by Virtools Group"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
reverse_selection: bpy.props.BoolProperty(
|
||||
name="Reverse",
|
||||
description="Reverse operation. Remove matched objects.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
ignore_hide: bpy.props.BoolProperty(
|
||||
name="Ignore Hide Property",
|
||||
description="Select objects without considering visibility.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
def execute(self, context):
|
||||
# make a copy for all objects, to ensure it is not viotile
|
||||
# becuase we need deselect some objects in for statement
|
||||
selected = bpy.context.selected_objects[:]
|
||||
# iterate object
|
||||
for obj in selected:
|
||||
# ignore hidden objects
|
||||
if (not self.ignore_hide) and obj.hide_get() == True:
|
||||
continue
|
||||
|
||||
# check group and decide select
|
||||
is_selected = UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string())
|
||||
if self.reverse_selection:
|
||||
is_selected = not is_selected
|
||||
|
||||
# select object
|
||||
obj.select_set(is_selected)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row()
|
||||
row.prop(self, 'ignore_hide')
|
||||
row.prop(self, 'reverse_selection')
|
||||
|
||||
layout.separator()
|
||||
self.parent_draw(layout)
|
||||
|
||||
|
||||
|
||||
class BALLANCE_OT_ctx_set_group(UTILS_virtools_prop.common_group_name_props):
|
||||
"""Grouping selected objects"""
|
||||
bl_idname = "ballance.ctx_set_group"
|
||||
bl_label = "Grouping Objects"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return len(bpy.context.selected_objects) != 0
|
||||
|
||||
def execute(self, context):
|
||||
has_duplicated = False
|
||||
|
||||
# iterate object
|
||||
for obj in bpy.context.selected_objects:
|
||||
# try setting
|
||||
if not UTILS_virtools_prop.add_virtools_group_data(obj, self.get_group_name_string()):
|
||||
has_duplicated = True
|
||||
|
||||
# throw a warning if some objects have duplicated group
|
||||
if has_duplicated:
|
||||
UTILS_functions.show_message_box(("Some objects have duplicated group name.", "These objects have been omitted.", ), "Duplicated Group", 'ERROR')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
self.parent_draw(layout)
|
||||
|
||||
class BALLANCE_OT_ctx_unset_group(UTILS_virtools_prop.common_group_name_props):
|
||||
"""Ungrouping selected objects"""
|
||||
bl_idname = "ballance.ctx_unset_group"
|
||||
bl_label = "Ungrouping Objects"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return len(bpy.context.selected_objects) != 0
|
||||
|
||||
def execute(self, context):
|
||||
lack_group = False
|
||||
|
||||
# iterate object
|
||||
for obj in bpy.context.selected_objects:
|
||||
# try unsetting
|
||||
if not UTILS_virtools_prop.remove_virtools_group_data(obj, self.get_group_name_string()):
|
||||
lack_group = True
|
||||
|
||||
# throw a warning if some objects have duplicated group
|
||||
if lack_group:
|
||||
UTILS_functions.show_message_box(("Some objects lack specified group name.", "These objects have been omitted.", ), "Lack Group", 'ERROR')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
self.parent_draw(layout)
|
||||
|
||||
class BALLANCE_OT_ctx_clear_group(bpy.types.Operator):
|
||||
"""Clear Virtools Groups for selected objects"""
|
||||
bl_idname = "ballance.ctx_clear_group"
|
||||
bl_label = "Clear Grouping"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return len(bpy.context.selected_objects) != 0
|
||||
|
||||
def execute(self, context):
|
||||
# iterate object
|
||||
for obj in bpy.context.selected_objects:
|
||||
UTILS_virtools_prop.clear_virtools_group_data(obj)
|
||||
|
||||
|
||||
return {'FINISHED'}
|
||||
|
@ -1,73 +0,0 @@
|
||||
import bpy
|
||||
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop
|
||||
|
||||
class BALLANCE_OT_add_virtools_group(UTILS_virtools_prop.common_group_name_props):
|
||||
"""Add a Virtools Group for Active Object."""
|
||||
bl_idname = "ballance.add_virtools_group"
|
||||
bl_label = "Add Virtools Group"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return context.object is not None
|
||||
|
||||
def execute(self, context):
|
||||
# try adding
|
||||
obj = context.object
|
||||
if not UTILS_virtools_prop.add_virtools_group_data(obj, self.get_group_name_string()):
|
||||
UTILS_functions.show_message_box(("Group name is duplicated!", ), "Duplicated Name", 'ERROR')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
self.parent_draw(self.layout)
|
||||
|
||||
|
||||
class BALLANCE_OT_rm_virtools_group(bpy.types.Operator):
|
||||
"""Remove a Virtools Group for Active Object."""
|
||||
bl_idname = "ballance.rm_virtools_group"
|
||||
bl_label = "Remove Virtools Group"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
if context.object is None:
|
||||
return False
|
||||
|
||||
obj = context.object
|
||||
gp = UTILS_virtools_prop.get_virtools_group(obj)
|
||||
active_gp = UTILS_virtools_prop.get_active_virtools_group(obj)
|
||||
return int(active_gp) >= 0 and int(active_gp) < len(gp)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.object
|
||||
UTILS_virtools_prop.remove_virtools_group_data_by_index(obj, int(UTILS_virtools_prop.get_active_virtools_group(obj)))
|
||||
return {'FINISHED'}
|
||||
|
||||
class BALLANCE_UL_virtools_group(bpy.types.UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
|
||||
layout.prop(item, 'group_name', icon='GROUP', emboss=False, text="")
|
||||
|
||||
class BALLANCE_PT_virtools_group(bpy.types.Panel):
|
||||
"""Show Virtools Group Properties."""
|
||||
bl_label = "Virtools Group"
|
||||
bl_idname = "BALLANCE_PT_virtools_group"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object is not None
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
target = bpy.context.active_object
|
||||
|
||||
row = layout.row()
|
||||
row.template_list("BALLANCE_UL_virtools_group", "", target, "virtools_group",
|
||||
target, "active_virtools_group")
|
||||
|
||||
col = row.column(align=True)
|
||||
col.operator(BALLANCE_OT_add_virtools_group.bl_idname, icon='ADD', text="")
|
||||
col.operator(BALLANCE_OT_rm_virtools_group.bl_idname, icon='REMOVE', text="")
|
@ -1,91 +0,0 @@
|
||||
import bpy
|
||||
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop
|
||||
|
||||
class BALLANCE_OT_apply_virtools_material(bpy.types.Operator):
|
||||
"""Apply Virtools Material to Blender Material."""
|
||||
bl_idname = "ballance.apply_virtools_material"
|
||||
bl_label = "Apply Virtools Material"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.material is not None
|
||||
|
||||
def execute(self, context):
|
||||
mtl = context.material
|
||||
mtl_data = UTILS_virtools_prop.get_virtools_material_data(mtl)
|
||||
|
||||
# check enable, [0] is enable_virtools_material
|
||||
if mtl_data[0]:
|
||||
UTILS_functions.create_material_nodes(mtl, mtl_data)
|
||||
else:
|
||||
UTILS_functions.show_message_box(("Virtools Material is not enabled.", ), "Apply Failed", 'ERROR')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class BALLANCE_OT_parse_virtools_material(bpy.types.Operator):
|
||||
"""Apply Virtools Material to Blender Material."""
|
||||
bl_idname = "ballance.parse_virtools_material"
|
||||
bl_label = "Parse from Blender Principled BSDF"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.material is not None
|
||||
|
||||
def execute(self, context):
|
||||
mtl = context.material
|
||||
mtl_data = UTILS_functions.parse_material_nodes(mtl)
|
||||
if mtl_data is None:
|
||||
UTILS_functions.show_message_box(("Fail to parse Principled BSDF.", ), "Parsing Failed", 'ERROR')
|
||||
else:
|
||||
UTILS_virtools_prop.set_virtools_material_data(mtl, mtl_data)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class BALLANCE_PT_virtools_material(bpy.types.Panel):
|
||||
"""Show Virtools Material Properties."""
|
||||
bl_label = "Virtools Material"
|
||||
bl_idname = "BALLANCE_PT_virtools_material"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "material"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.material is not None
|
||||
|
||||
def draw_header(self, context):
|
||||
# draw a checkbox in header
|
||||
target = UTILS_virtools_prop.get_virtools_material(context.material)
|
||||
self.layout.prop(target, "enable_virtools_material", text="")
|
||||
|
||||
def draw(self, context):
|
||||
# get layout and target
|
||||
layout = self.layout
|
||||
target = UTILS_virtools_prop.get_virtools_material(context.material)
|
||||
|
||||
# decide visible
|
||||
layout.enabled = target.enable_virtools_material
|
||||
|
||||
# draw layout
|
||||
layout.label(text="Basic Parameters")
|
||||
layout.prop(target, 'ambient')
|
||||
layout.prop(target, 'diffuse')
|
||||
layout.prop(target, 'specular')
|
||||
layout.prop(target, 'emissive')
|
||||
layout.prop(target, 'specular_power')
|
||||
layout.prop(target, 'texture', emboss=True)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text="Advanced Parameters")
|
||||
layout.prop(target, 'alpha_test')
|
||||
layout.prop(target, 'alpha_blend')
|
||||
layout.prop(target, 'z_buffer')
|
||||
layout.prop(target, 'two_sided')
|
||||
|
||||
layout.separator()
|
||||
layout.label(text="Operations")
|
||||
layout.operator("ballance.apply_virtools_material", icon="NODETREE")
|
||||
layout.operator("ballance.parse_virtools_material", icon="HIDE_OFF")
|
||||
|
@ -1,361 +0,0 @@
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
bmfile_currentVersion = 14
|
||||
bmfile_flagUnicode = 0x800
|
||||
bmfile_flagDeflatedMaximum = 0x2
|
||||
bmfile_globalComment = 'Use BM Spec 1.4'.encode('utf-8')
|
||||
|
||||
class BmfileInfoType():
|
||||
OBJECT = 0
|
||||
MESH = 1
|
||||
MATERIAL = 2
|
||||
TEXTURE = 3
|
||||
|
||||
bmfile_externalTextureSet = set([
|
||||
"atari.avi",
|
||||
"atari.bmp",
|
||||
"Ball_LightningSphere1.bmp",
|
||||
"Ball_LightningSphere2.bmp",
|
||||
"Ball_LightningSphere3.bmp",
|
||||
"Ball_Paper.bmp",
|
||||
"Ball_Stone.bmp",
|
||||
"Ball_Wood.bmp",
|
||||
"Brick.bmp",
|
||||
"Button01_deselect.tga",
|
||||
"Button01_select.tga",
|
||||
"Button01_special.tga",
|
||||
"Column_beige.bmp",
|
||||
"Column_beige_fade.tga",
|
||||
"Column_blue.bmp",
|
||||
"Cursor.tga",
|
||||
"Dome.bmp",
|
||||
"DomeEnvironment.bmp",
|
||||
"DomeShadow.tga",
|
||||
"ExtraBall.bmp",
|
||||
"ExtraParticle.bmp",
|
||||
"E_Holzbeschlag.bmp",
|
||||
"FloorGlow.bmp",
|
||||
"Floor_Side.bmp",
|
||||
"Floor_Top_Border.bmp",
|
||||
"Floor_Top_Borderless.bmp",
|
||||
"Floor_Top_Checkpoint.bmp",
|
||||
"Floor_Top_Flat.bmp",
|
||||
"Floor_Top_Profil.bmp",
|
||||
"Floor_Top_ProfilFlat.bmp",
|
||||
"Font_1.tga",
|
||||
"Gravitylogo_intro.bmp",
|
||||
"HardShadow.bmp",
|
||||
"Laterne_Glas.bmp",
|
||||
"Laterne_Schatten.tga",
|
||||
"Laterne_Verlauf.tga",
|
||||
"Logo.bmp",
|
||||
"Metal_stained.bmp",
|
||||
"Misc_Ufo.bmp",
|
||||
"Misc_UFO_Flash.bmp",
|
||||
"Modul03_Floor.bmp",
|
||||
"Modul03_Wall.bmp",
|
||||
"Modul11_13_Wood.bmp",
|
||||
"Modul11_Wood.bmp",
|
||||
"Modul15.bmp",
|
||||
"Modul16.bmp",
|
||||
"Modul18.bmp",
|
||||
"Modul18_Gitter.tga",
|
||||
"Modul30_d_Seiten.bmp",
|
||||
"Particle_Flames.bmp",
|
||||
"Particle_Smoke.bmp",
|
||||
"PE_Bal_balloons.bmp",
|
||||
"PE_Bal_platform.bmp",
|
||||
"PE_Ufo_env.bmp",
|
||||
"Pfeil.tga",
|
||||
"P_Extra_Life_Oil.bmp",
|
||||
"P_Extra_Life_Particle.bmp",
|
||||
"P_Extra_Life_Shadow.bmp",
|
||||
"Rail_Environment.bmp",
|
||||
"sandsack.bmp",
|
||||
"SkyLayer.bmp",
|
||||
"Sky_Vortex.bmp",
|
||||
"Stick_Bottom.tga",
|
||||
"Stick_Stripes.bmp",
|
||||
"Target.bmp",
|
||||
"Tower_Roof.bmp",
|
||||
"Trafo_Environment.bmp",
|
||||
"Trafo_FlashField.bmp",
|
||||
"Trafo_Shadow_Big.tga",
|
||||
"Tut_Pfeil01.tga",
|
||||
"Tut_Pfeil_Hoch.tga",
|
||||
"Wolken_intro.tga",
|
||||
"Wood_Metal.bmp",
|
||||
"Wood_MetalStripes.bmp",
|
||||
"Wood_Misc.bmp",
|
||||
"Wood_Nailed.bmp",
|
||||
"Wood_Old.bmp",
|
||||
"Wood_Panel.bmp",
|
||||
"Wood_Plain.bmp",
|
||||
"Wood_Plain2.bmp",
|
||||
"Wood_Raft.bmp"
|
||||
])
|
||||
|
||||
bmfile_componentList = [
|
||||
"P_Extra_Life",
|
||||
"P_Extra_Point",
|
||||
"P_Trafo_Paper",
|
||||
"P_Trafo_Stone",
|
||||
"P_Trafo_Wood",
|
||||
"P_Ball_Paper",
|
||||
"P_Ball_Stone",
|
||||
"P_Ball_Wood",
|
||||
"P_Box",
|
||||
"P_Dome",
|
||||
"P_Modul_01",
|
||||
"P_Modul_03",
|
||||
"P_Modul_08",
|
||||
"P_Modul_17",
|
||||
"P_Modul_18",
|
||||
"P_Modul_19",
|
||||
"P_Modul_25",
|
||||
"P_Modul_26",
|
||||
"P_Modul_29",
|
||||
"P_Modul_30",
|
||||
"P_Modul_34",
|
||||
"P_Modul_37",
|
||||
"P_Modul_41",
|
||||
"PC_TwoFlames",
|
||||
"PE_Balloon",
|
||||
"PR_Resetpoint",
|
||||
"PS_FourFlames"
|
||||
]
|
||||
|
||||
'''
|
||||
format: key is diection, value is a dict
|
||||
dict's key is expand mode, value is a tuple
|
||||
tuple always have 4 items, it means (TOP_STR, RIGHT_STR, BOTTOM_STR, LEFT_STR)
|
||||
'''
|
||||
floor_expandDirectionMap = {
|
||||
"PositiveX": {
|
||||
"Static": ("X", "X", "X", "X"),
|
||||
"Column": ("X", "X", "D1", "X"),
|
||||
"Freedom": ("X", "X", "D1", "D2"),
|
||||
},
|
||||
"NegativeX": {
|
||||
"Static": ("X", "X", "X", "X"),
|
||||
"Column": ("D1", "X", "X", "X"),
|
||||
"Freedom": ("D1", "D2", "X", "X"),
|
||||
},
|
||||
"PositiveY": {
|
||||
"Static": ("X", "X", "X", "X"),
|
||||
"Column": ("X", "D1", "X", "X"),
|
||||
"Freedom": ("X", "D1", "D2", "X"),
|
||||
},
|
||||
"NegativeY": {
|
||||
"Static": ("X", "X", "X", "X"),
|
||||
"Column": ("X", "X", "X", "D1"),
|
||||
"Freedom": ("D2", "X", "X", "D1"),
|
||||
}
|
||||
}
|
||||
|
||||
floor_textureReflactionMap = {
|
||||
"FloorSide": "Floor_Side.bmp",
|
||||
"FloorTopBorder": "Floor_Top_Border.bmp",
|
||||
"FloorTopBorder_ForSide": "Floor_Top_Border.bmp",
|
||||
"FloorTopBorderless": "Floor_Top_Borderless.bmp",
|
||||
"FloorTopBorderless_ForSide": "Floor_Top_Borderless.bmp",
|
||||
"FloorTopFlat": "Floor_Top_Flat.bmp",
|
||||
"FloorTopProfil": "Floor_Top_Profil.bmp",
|
||||
"FloorTopProfilFlat": "Floor_Top_ProfilFlat.bmp",
|
||||
"BallWood": "Ball_Wood.bmp",
|
||||
"BallPaper": "Ball_Paper.bmp",
|
||||
"BallStone": "Ball_Stone.bmp"
|
||||
}
|
||||
|
||||
# WARNING: this data is shared with `BallanceVirtoolsPlugin/bvh/features/mapping/fix_texture.cpp`
|
||||
floor_materialStatistic = [
|
||||
{
|
||||
"member": [
|
||||
"FloorSide",
|
||||
"FloorTopBorder_ForSide",
|
||||
"FloorTopBorderless_ForSide"
|
||||
],
|
||||
"data": {
|
||||
"ambient": (0, 0, 0),
|
||||
"diffuse": (122 / 255.0, 122 / 255.0, 122 / 255.0),
|
||||
"specular": (0.0, 0.0, 0.0),
|
||||
"emissive": (104 / 255.0, 104 / 255.0, 104 / 255.0),
|
||||
"power": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"member": [
|
||||
"FloorTopBorder",
|
||||
"FloorTopBorderless",
|
||||
"FloorTopFlat",
|
||||
"FloorTopProfil",
|
||||
"FloorTopProfilFlat"
|
||||
],
|
||||
"data": {
|
||||
"ambient": (0, 0, 0),
|
||||
"diffuse": (1.0, 1.0, 1.0),
|
||||
"specular": (80 / 255.0, 80 / 255.0, 80 / 255.0),
|
||||
"emissive": (0.0, 0.0, 0.0),
|
||||
"power": 100
|
||||
}
|
||||
},
|
||||
{
|
||||
"member": [
|
||||
"BallPaper"
|
||||
],
|
||||
"data": {
|
||||
"ambient": (25 / 255.0, 25 / 255.0, 25 / 255.0),
|
||||
"diffuse": (1.0, 1.0, 1.0),
|
||||
"specular": (0.0, 0.0, 0.0),
|
||||
"emissive": (100 / 255.0, 100 / 255.0, 100 / 255.0),
|
||||
"power": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"member": [
|
||||
"BallStone",
|
||||
"BallWood"
|
||||
],
|
||||
"data": {
|
||||
"ambient": (25 / 255.0, 25 / 255.0, 25 / 255.0),
|
||||
"diffuse": (1.0, 1.0, 1.0),
|
||||
"specular": (229 / 255.0, 229 / 255.0, 229 / 255.0),
|
||||
"emissive": (60 / 255.0, 60 / 255.0, 60 / 255.0),
|
||||
"power": 0
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
floor_blockDict = {}
|
||||
floor_basicBlockList = []
|
||||
floor_derivedBlockList = []
|
||||
# read from json
|
||||
for walk_root, walk_dirs, walk_files in os.walk(os.path.join(os.path.dirname(__file__), "json", "basic_blocks")):
|
||||
for relfile in walk_files:
|
||||
if not relfile.endswith('.json'): continue
|
||||
with open(os.path.join(walk_root, relfile)) as fp:
|
||||
for item in json.load(fp):
|
||||
floor_basicBlockList.append(item["Type"])
|
||||
floor_blockDict[item["Type"]] = item
|
||||
for walk_root, walk_dirs, walk_files in os.walk(os.path.join(os.path.dirname(__file__), "json", "derived_blocks")):
|
||||
for relfile in walk_files:
|
||||
if not relfile.endswith('.json'): continue
|
||||
with open(os.path.join(walk_root, relfile)) as fp:
|
||||
for item in json.load(fp):
|
||||
floor_derivedBlockList.append(item["Type"])
|
||||
floor_blockDict[item["Type"]] = item
|
||||
|
||||
icons_floor = None
|
||||
icons_floorDict = {}
|
||||
# blenderIcon_elements = None
|
||||
# blenderIcon_elements_dict = {}
|
||||
|
||||
rename_normalComponentsGroupName = set([
|
||||
"P_Extra_Life",
|
||||
"P_Extra_Point",
|
||||
"P_Trafo_Paper",
|
||||
"P_Trafo_Stone",
|
||||
"P_Trafo_Wood",
|
||||
"P_Ball_Paper",
|
||||
"P_Ball_Stone",
|
||||
"P_Ball_Wood",
|
||||
"P_Box",
|
||||
"P_Dome",
|
||||
"P_Modul_01",
|
||||
"P_Modul_03",
|
||||
"P_Modul_08",
|
||||
"P_Modul_17",
|
||||
"P_Modul_18",
|
||||
"P_Modul_19",
|
||||
"P_Modul_25",
|
||||
"P_Modul_26",
|
||||
"P_Modul_29",
|
||||
"P_Modul_30",
|
||||
"P_Modul_34",
|
||||
"P_Modul_37",
|
||||
"P_Modul_41"
|
||||
])
|
||||
|
||||
rename_uniqueComponentsGroupName = set([
|
||||
"PS_Levelstart",
|
||||
"PE_Levelende",
|
||||
"PC_Checkpoints",
|
||||
"PR_Resetpoints"
|
||||
])
|
||||
|
||||
rename_floorGroupTester = set([
|
||||
"Sound_HitID_01",
|
||||
"Sound_RollID_01"
|
||||
])
|
||||
|
||||
rename_woodGroupTester = set([
|
||||
"Sound_HitID_02",
|
||||
"Sound_RollID_02"
|
||||
])
|
||||
|
||||
# 61 mark: Sector_(0[1-8]|[1-9][0-9]{1,2}|9) may also work
|
||||
rename_regexCKGroupSector = re.compile('^Sector_([123456789]{1}[0123456789]{1}[0123456789]{1}|[123456789]{1}[0123456789]{1}|0[12345678]{1}|9)$')
|
||||
rename_regexYYCComponent = re.compile('^(' + '|'.join(rename_normalComponentsGroupName) + ')_(0[1-9]|[1-9][0-9])_.*$')
|
||||
rename_regexYYCPC = re.compile('^PC_TwoFlames_(0[1-7])$')
|
||||
rename_regexYYCPR = re.compile('^PR_Resetpoint_(0[1-8])$')
|
||||
rename_regexImengyuComponent = re.compile('^(' + '|'.join(rename_normalComponentsGroupName) + '):[^:]*:([1-9]|[1-9][0-9])$')
|
||||
rename_regexImengyuPCRComp = re.compile('^(PC_CheckPoint|PR_ResetPoint):([0-9]+)$')
|
||||
|
||||
propsVtGroups_availableGroups = (
|
||||
"Sector_01",
|
||||
"Sector_02",
|
||||
"Sector_03",
|
||||
"Sector_04",
|
||||
"Sector_05",
|
||||
"Sector_06",
|
||||
"Sector_07",
|
||||
"Sector_08",
|
||||
|
||||
"P_Extra_Life",
|
||||
"P_Extra_Point",
|
||||
"P_Trafo_Paper",
|
||||
"P_Trafo_Stone",
|
||||
"P_Trafo_Wood",
|
||||
"P_Ball_Paper",
|
||||
"P_Ball_Stone",
|
||||
"P_Ball_Wood",
|
||||
"P_Box",
|
||||
"P_Dome",
|
||||
"P_Modul_01",
|
||||
"P_Modul_03",
|
||||
"P_Modul_08",
|
||||
"P_Modul_17",
|
||||
"P_Modul_18",
|
||||
"P_Modul_19",
|
||||
"P_Modul_25",
|
||||
"P_Modul_26",
|
||||
"P_Modul_29",
|
||||
"P_Modul_30",
|
||||
"P_Modul_34",
|
||||
"P_Modul_37",
|
||||
"P_Modul_41",
|
||||
|
||||
"PS_Levelstart",
|
||||
"PE_Levelende",
|
||||
"PC_Checkpoints",
|
||||
"PR_Resetpoints",
|
||||
|
||||
"Sound_HitID_01",
|
||||
"Sound_RollID_01",
|
||||
"Sound_HitID_02",
|
||||
"Sound_RollID_02",
|
||||
"Sound_HitID_03",
|
||||
"Sound_RollID_03",
|
||||
|
||||
"DepthTestCubes",
|
||||
|
||||
"Phys_Floors",
|
||||
"Phys_FloorRails",
|
||||
"Phys_FloorStopper",
|
||||
|
||||
"Shadow"
|
||||
)
|
||||
|
||||
|
@ -1,96 +0,0 @@
|
||||
import bpy, bmesh, bpy_extras, mathutils
|
||||
import struct, shutil, os
|
||||
|
||||
# writer
|
||||
|
||||
def write_string(fs,str):
|
||||
count=len(str)
|
||||
write_uint32(fs,count)
|
||||
fs.write(str.encode("utf_32_le"))
|
||||
|
||||
def write_uint8(fs,num):
|
||||
fs.write(struct.pack("B", num))
|
||||
|
||||
def write_uint32(fs,num):
|
||||
fs.write(struct.pack("I", num))
|
||||
|
||||
def write_uint64(fs,num):
|
||||
fs.write(struct.pack("Q", num))
|
||||
|
||||
def write_bool(fs,boolean):
|
||||
if boolean:
|
||||
write_uint8(fs, 1)
|
||||
else:
|
||||
write_uint8(fs, 0)
|
||||
|
||||
def write_float(fs,fl):
|
||||
fs.write(struct.pack("f", fl))
|
||||
|
||||
def write_world_matrix(fs, matt):
|
||||
mat = matt.transposed()
|
||||
fs.write(struct.pack("ffffffffffffffff",
|
||||
mat[0][0],mat[0][2], mat[0][1], mat[0][3],
|
||||
mat[2][0],mat[2][2], mat[2][1], mat[2][3],
|
||||
mat[1][0],mat[1][2], mat[1][1], mat[1][3],
|
||||
mat[3][0],mat[3][2], mat[3][1], mat[3][3]))
|
||||
|
||||
def write_3vector(fs, x, y ,z):
|
||||
fs.write(struct.pack("fff", x, y ,z))
|
||||
|
||||
def write_color(fs, colors):
|
||||
write_3vector(fs, colors[0], colors[1], colors[2])
|
||||
|
||||
def write_2vector(fs, u, v):
|
||||
fs.write(struct.pack("ff", u, v))
|
||||
|
||||
def write_face(fs, v1, vt1, vn1, v2, vt2, vn2, v3, vt3, vn3):
|
||||
fs.write(struct.pack("IIIIIIIII", v1, vt1, vn1, v2, vt2, vn2, v3, vt3, vn3))
|
||||
|
||||
# reader
|
||||
|
||||
def peek_stream(fs):
|
||||
res = fs.read(1)
|
||||
fs.seek(-1, os.SEEK_CUR)
|
||||
return res
|
||||
|
||||
def read_float(fs):
|
||||
return struct.unpack("f", fs.read(4))[0]
|
||||
|
||||
def read_uint8(fs):
|
||||
return struct.unpack("B", fs.read(1))[0]
|
||||
|
||||
def read_uint32(fs):
|
||||
return struct.unpack("I", fs.read(4))[0]
|
||||
|
||||
def read_uint64(fs):
|
||||
return struct.unpack("Q", fs.read(8))[0]
|
||||
|
||||
def read_string(fs):
|
||||
count = read_uint32(fs)
|
||||
return fs.read(count*4).decode("utf_32_le")
|
||||
|
||||
def read_bool(fs):
|
||||
return read_uint8(fs) != 0
|
||||
|
||||
def read_world_materix(fs):
|
||||
p = struct.unpack("ffffffffffffffff", fs.read(4*4*4))
|
||||
res = mathutils.Matrix((
|
||||
(p[0], p[2], p[1], p[3]),
|
||||
(p[8], p[10], p[9], p[11]),
|
||||
(p[4], p[6], p[5], p[7]),
|
||||
(p[12], p[14], p[13], p[15])))
|
||||
return res.transposed()
|
||||
|
||||
def read_3vector(fs):
|
||||
return struct.unpack("fff", fs.read(3*4))
|
||||
|
||||
def read_2vector(fs):
|
||||
return struct.unpack("ff", fs.read(2*4))
|
||||
|
||||
def read_face(fs):
|
||||
return struct.unpack("IIIIIIIII", fs.read(4*9))
|
||||
|
||||
def read_component_face(fs):
|
||||
return struct.unpack("IIIIII", fs.read(4*6))
|
||||
|
||||
|
@ -1,265 +0,0 @@
|
||||
import bpy, bmesh, bpy_extras, mathutils
|
||||
import struct, shutil, os
|
||||
from bpy_extras.io_utils import unpack_list
|
||||
from bpy_extras.image_utils import load_image
|
||||
from bpy_extras import io_utils, node_shader_utils
|
||||
from . import UTILS_file_io, UTILS_constants, UTILS_virtools_prop
|
||||
|
||||
# =================================
|
||||
# scene operation
|
||||
|
||||
def show_message_box(message, title, icon):
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
for item in message:
|
||||
layout.label(text=item, translate=False)
|
||||
|
||||
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
|
||||
|
||||
def add_into_scene_and_move_to_cursor(obj):
|
||||
move_to_cursor(obj)
|
||||
|
||||
view_layer = bpy.context.view_layer
|
||||
collection = view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
|
||||
def move_to_cursor(obj):
|
||||
obj.location = bpy.context.scene.cursor.location
|
||||
|
||||
# =================================
|
||||
# is compoent
|
||||
|
||||
def is_component(name):
|
||||
return get_component_id(name) != -1
|
||||
|
||||
def get_component_id(name):
|
||||
for ind, comp in enumerate(UTILS_constants.bmfile_componentList):
|
||||
if name.startswith(comp):
|
||||
return ind
|
||||
return -1
|
||||
|
||||
# =================================
|
||||
# create / parse material
|
||||
|
||||
def create_blender_material(input_mtl, packed_data):
|
||||
# adding material nodes
|
||||
create_material_nodes(input_mtl, packed_data)
|
||||
|
||||
# write custom property
|
||||
UTILS_virtools_prop.set_virtools_material_data(input_mtl, packed_data)
|
||||
|
||||
def create_material_nodes(input_mtl, packed_data):
|
||||
(enable_virtools_material,
|
||||
ambient, diffuse, specular, emissive, specular_power,
|
||||
alpha_test, alpha_blend, z_buffer, two_sided,
|
||||
texture) = packed_data
|
||||
|
||||
# enable nodes mode
|
||||
input_mtl.use_nodes=True
|
||||
# delete all existed nodes
|
||||
for node in input_mtl.node_tree.nodes:
|
||||
input_mtl.node_tree.nodes.remove(node)
|
||||
|
||||
# create ballance-style blender material
|
||||
bnode = input_mtl.node_tree.nodes.new(type="ShaderNodeBsdfPrincipled")
|
||||
mnode = input_mtl.node_tree.nodes.new(type="ShaderNodeOutputMaterial")
|
||||
input_mtl.node_tree.links.new(bnode.outputs[0],mnode.inputs[0])
|
||||
|
||||
input_mtl.metallic = sum(ambient) / 3
|
||||
input_mtl.diffuse_color = [i for i in diffuse] + [1]
|
||||
input_mtl.specular_color = specular
|
||||
input_mtl.specular_intensity = specular_power
|
||||
|
||||
# adding a texture
|
||||
if texture is not None:
|
||||
inode = input_mtl.node_tree.nodes.new(type="ShaderNodeTexImage")
|
||||
inode.image = texture
|
||||
input_mtl.node_tree.links.new(inode.outputs[0], bnode.inputs[0])
|
||||
|
||||
# return None if fail to parse
|
||||
def parse_material_nodes(mtl):
|
||||
# get node
|
||||
mat_wrap = node_shader_utils.PrincipledBSDFWrapper(mtl)
|
||||
# check existence of Principled BSDF
|
||||
if mat_wrap:
|
||||
# we trying get texture data from Principled BSDF
|
||||
use_mirror = mat_wrap.metallic != 0.0
|
||||
if use_mirror:
|
||||
mtl_ambient = (mat_wrap.metallic, mat_wrap.metallic, mat_wrap.metallic)
|
||||
else:
|
||||
mtl_ambient = (1.0, 1.0, 1.0)
|
||||
mtl_diffuse = (mat_wrap.base_color[0], mat_wrap.base_color[1], mat_wrap.base_color[2])
|
||||
mtl_specular = (mat_wrap.specular, mat_wrap.specular, mat_wrap.specular)
|
||||
mtl_emissive = mat_wrap.emission_color[:3]
|
||||
mtl_specularPower = 0.0
|
||||
|
||||
# confirm texture
|
||||
mtl_texture = None
|
||||
tex_wrap = getattr(mat_wrap, "base_color_texture", None)
|
||||
if tex_wrap:
|
||||
image = tex_wrap.image
|
||||
if image:
|
||||
mtl_texture = image
|
||||
|
||||
# return value
|
||||
return (True,
|
||||
mtl_ambient, mtl_diffuse, mtl_specular, mtl_emissive, mtl_specularPower,
|
||||
False, False, False, False,
|
||||
mtl_texture
|
||||
)
|
||||
|
||||
else:
|
||||
return None
|
||||
|
||||
# =================================
|
||||
# load component
|
||||
|
||||
def load_component(component_id):
|
||||
# get file first
|
||||
component_name = UTILS_constants.bmfile_componentList[component_id]
|
||||
selected_file = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
'meshes',
|
||||
component_name + '.bin'
|
||||
)
|
||||
|
||||
# read file. please note this sector is sync with import_bm's mesh's code. when something change, please change each other.
|
||||
fmesh = open(selected_file, 'rb')
|
||||
|
||||
# create real mesh, we don't need to consider name. blender will solve duplicated name
|
||||
mesh = bpy.data.meshes.new('mesh_' + component_name)
|
||||
|
||||
vList = []
|
||||
vnList = []
|
||||
faceList = []
|
||||
# in first read, store all data into list
|
||||
listCount = UTILS_file_io.read_uint32(fmesh)
|
||||
for i in range(listCount):
|
||||
cache = UTILS_file_io.read_3vector(fmesh)
|
||||
# switch yz
|
||||
vList.append((cache[0], cache[2], cache[1]))
|
||||
listCount = UTILS_file_io.read_uint32(fmesh)
|
||||
for i in range(listCount):
|
||||
cache = UTILS_file_io.read_3vector(fmesh)
|
||||
# switch yz
|
||||
vnList.append((cache[0], cache[2], cache[1]))
|
||||
|
||||
listCount = UTILS_file_io.read_uint32(fmesh)
|
||||
for i in range(listCount):
|
||||
faceData = UTILS_file_io.read_component_face(fmesh)
|
||||
|
||||
# we need invert triangle sort
|
||||
faceList.append((
|
||||
faceData[4], faceData[5],
|
||||
faceData[2], faceData[3],
|
||||
faceData[0], faceData[1]
|
||||
))
|
||||
|
||||
# then, we need add correspond count for vertices
|
||||
mesh.vertices.add(len(vList))
|
||||
mesh.loops.add(len(faceList)*3) # triangle face confirmed
|
||||
mesh.polygons.add(len(faceList))
|
||||
mesh.create_normals_split()
|
||||
|
||||
# add vertices data
|
||||
mesh.vertices.foreach_set("co", unpack_list(vList))
|
||||
mesh.loops.foreach_set("vertex_index", unpack_list(_flat_component_vertices_index(faceList)))
|
||||
mesh.loops.foreach_set("normal", unpack_list(_flat_component_vertices_normal(faceList, vnList)))
|
||||
for i in range(len(faceList)):
|
||||
mesh.polygons[i].loop_start = i * 3
|
||||
mesh.polygons[i].loop_total = 3
|
||||
|
||||
mesh.polygons[i].use_smooth = True
|
||||
|
||||
mesh.validate(clean_customdata=False)
|
||||
mesh.update(calc_edges=False, calc_edges_loose=False)
|
||||
|
||||
fmesh.close()
|
||||
|
||||
return mesh
|
||||
|
||||
|
||||
def _flat_component_vertices_index(faceList):
|
||||
for item in faceList:
|
||||
yield (item[0], )
|
||||
yield (item[2], )
|
||||
yield (item[4], )
|
||||
|
||||
def _flat_component_vertices_normal(faceList, vnList):
|
||||
for item in faceList:
|
||||
yield vnList[item[1]]
|
||||
yield vnList[item[3]]
|
||||
yield vnList[item[5]]
|
||||
|
||||
# =================================
|
||||
# create instance with option
|
||||
|
||||
def create_instance_with_option(instance_type, instance_name, instance_opt,
|
||||
extra_mesh = None, extra_texture_path = None, extra_texture_filename = None):
|
||||
"""
|
||||
Create instance with opetions
|
||||
|
||||
`instance_type`, `instance_name`, `instance_opt` are essential for each type instances.
|
||||
For object, you should provide `extra_mesh`.
|
||||
For texture, you should provide `extra_texture_path` and `extra_texture_filename`.
|
||||
|
||||
"""
|
||||
|
||||
def get_instance():
|
||||
try:
|
||||
if instance_type == UTILS_constants.BmfileInfoType.OBJECT:
|
||||
temp_instance = bpy.data.objects[instance_name]
|
||||
elif instance_type == UTILS_constants.BmfileInfoType.MESH:
|
||||
temp_instance = bpy.data.meshes[instance_name]
|
||||
elif instance_type == UTILS_constants.BmfileInfoType.MATERIAL:
|
||||
temp_instance = bpy.data.materials[instance_name]
|
||||
elif instance_type == UTILS_constants.BmfileInfoType.TEXTURE:
|
||||
temp_instance = bpy.data.images[instance_name]
|
||||
|
||||
temp_is_existed = True
|
||||
except:
|
||||
temp_instance = None
|
||||
temp_is_existed = False
|
||||
|
||||
return (temp_instance, temp_is_existed)
|
||||
|
||||
def create_instance():
|
||||
if instance_type == UTILS_constants.BmfileInfoType.OBJECT:
|
||||
instance_obj = bpy.data.objects.new(instance_name, extra_mesh)
|
||||
#instance_obj.name = instance_name
|
||||
elif instance_type == UTILS_constants.BmfileInfoType.MESH:
|
||||
instance_obj = bpy.data.meshes.new(instance_name)
|
||||
#instance_obj.name = instance_name
|
||||
elif instance_type == UTILS_constants.BmfileInfoType.MATERIAL:
|
||||
instance_obj = bpy.data.materials.new(instance_name)
|
||||
#instance_obj.name = instance_name
|
||||
elif instance_type == UTILS_constants.BmfileInfoType.TEXTURE:
|
||||
# this command will also check current available texture
|
||||
# because `get_instance()` only check texture name
|
||||
# but this strategy is not based on texture filepath, so this create method will
|
||||
# correct this problem
|
||||
instance_obj = load_image(extra_texture_filename, extra_texture_path, check_existing=(instance_opt == 'CURRENT'))
|
||||
instance_obj.name = instance_name
|
||||
|
||||
return instance_obj
|
||||
|
||||
# analyze options
|
||||
if (not isinstance(instance_opt, str)) or instance_opt == 'RENAME':
|
||||
# create new instance
|
||||
# or always create new instance if opts is not string
|
||||
temp_instance = create_instance()
|
||||
temp_skip_init = False
|
||||
elif instance_opt == 'CURRENT':
|
||||
# try get instance
|
||||
(temp_instance, temp_is_existed) = get_instance()
|
||||
# if got instance successfully, we do not create new one, otherwise, we should
|
||||
# create new instance
|
||||
if not temp_is_existed:
|
||||
temp_instance = create_instance()
|
||||
temp_skip_init = False
|
||||
else:
|
||||
temp_skip_init = True
|
||||
|
||||
return (temp_instance, temp_skip_init)
|
||||
|
@ -1,49 +0,0 @@
|
||||
import bpy
|
||||
import bpy.types
|
||||
|
||||
class MyPropertyGroup(bpy.types.PropertyGroup):
|
||||
material_picker : bpy.props.PointerProperty(
|
||||
type=bpy.types.Material,
|
||||
name="Material",
|
||||
description="The material used for rail"
|
||||
)
|
||||
|
||||
collection_picker : bpy.props.PointerProperty(
|
||||
type=bpy.types.Collection,
|
||||
name="Collection",
|
||||
description="The collection which will be exported"
|
||||
)
|
||||
|
||||
object_picker : bpy.props.PointerProperty(
|
||||
type=bpy.types.Object,
|
||||
name="Object",
|
||||
description="The object which will be exported"
|
||||
)
|
||||
|
||||
class BallanceBlenderPluginPreferences(bpy.types.AddonPreferences):
|
||||
bl_idname = __package__
|
||||
|
||||
external_folder: bpy.props.StringProperty(
|
||||
name="External texture folder",
|
||||
description="The Ballance texture folder which will be used by this plugin to get external texture.",
|
||||
)
|
||||
|
||||
no_component_collection: bpy.props.StringProperty(
|
||||
name="No component collection",
|
||||
description="(Import) The object which stored in this collectiion will not be saved as component. (Export) All forced no component objects will be stored in this collection",
|
||||
)
|
||||
|
||||
temp_texture_folder: bpy.props.StringProperty(
|
||||
name="Temp texture folder",
|
||||
description="The folder which will temporarily store the textures which are extracted from bm. Due to system temp folder will be deleted after decoding of bm, so this path should not be blank.",
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row()
|
||||
col = row.column()
|
||||
|
||||
col.prop(self, "external_folder")
|
||||
col.prop(self, "no_component_collection")
|
||||
col.prop(self, "temp_texture_folder")
|
@ -1,68 +0,0 @@
|
||||
import ast
|
||||
|
||||
class SimpleCalcEnsurance(ast.NodeVisitor):
|
||||
def __init__(self):
|
||||
self.is_illegal_syntax: bool = False
|
||||
self.allow_float: bool = True
|
||||
self.param_name: tuple = tuple()
|
||||
|
||||
def wrapper_visit(self, node: ast.AST, allow_float: bool, param_name: tuple) -> bool:
|
||||
self.is_illegal_syntax = False
|
||||
self.allow_float = allow_float
|
||||
self.param_name = param_name
|
||||
|
||||
self.visit(node)
|
||||
return self.is_illegal_syntax
|
||||
|
||||
def generic_visit(node):
|
||||
self.is_illegal_syntax = True
|
||||
|
||||
def visit_Expression(self, node: ast.Expression):
|
||||
self.visit(node.body)
|
||||
|
||||
def visit_BinOp(self, node: ast.BinOp):
|
||||
if isinstance(node.op, ast.Add) or isinstance(node.op, ast.Sub) or isinstance(node.op, ast.Mult) or isinstance(node.op, ast.Div):
|
||||
self.visit(node.left)
|
||||
self.visit(node.right)
|
||||
else:
|
||||
self.is_illegal_syntax = True
|
||||
|
||||
def visit_UnaryOp(self, node: ast.UnaryOp):
|
||||
if isinstance(node.op, ast.USub):
|
||||
self.visit(node.operand)
|
||||
else:
|
||||
self.is_illegal_syntax = True
|
||||
|
||||
def visit_Constant(self, node: ast.Constant):
|
||||
if (self.allow_float and isinstance(node.value, float)) or isinstance(node.value, int):
|
||||
pass
|
||||
else:
|
||||
self.is_illegal_syntax = True
|
||||
|
||||
|
||||
def visit_Name(self, node: ast.Name):
|
||||
if node.id in self.param_name and isinstance(node.ctx, ast.Load):
|
||||
pass
|
||||
else:
|
||||
self.is_illegal_syntax = True
|
||||
|
||||
def _do_calc(szEval: str, allow_float: bool, d: dict):
|
||||
ast_tree = ast.parse(szEval, mode='eval')
|
||||
walker = SimpleCalcEnsurance()
|
||||
if walker.wrapper_visit(ast_tree, allow_float, d.keys()):
|
||||
raise Exception("Illegal AST Tree. Tree contain illegal syntax. Please check BMERevenge.")
|
||||
|
||||
return eval(compile(ast_tree, '', mode='eval'), {}, d)
|
||||
|
||||
def do_vec_calc(szEval: str, raw_d1: float, raw_d2: float, raw_d3: float) -> float:
|
||||
return float(_do_calc(szEval, True, {
|
||||
"d1": raw_d1,
|
||||
"d2": raw_d2,
|
||||
"d3": raw_d3
|
||||
}))
|
||||
|
||||
def do_expand_calc(szEval: str, d1: int, d2: int) -> int:
|
||||
return int(_do_calc(szEval, False, {
|
||||
"d1": d1,
|
||||
"d2": d2
|
||||
}))
|
@ -1,228 +0,0 @@
|
||||
import bpy
|
||||
from . import UTILS_constants, UTILS_functions
|
||||
|
||||
class BALLANCE_PG_virtools_material(bpy.types.PropertyGroup):
|
||||
enable_virtools_material: bpy.props.BoolProperty(
|
||||
name="Enable Virtools Material",
|
||||
default=False,
|
||||
)
|
||||
|
||||
ambient: bpy.props.FloatVectorProperty(name="Ambient",
|
||||
subtype='COLOR',
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
default=[0.0,0.0,0.0])
|
||||
|
||||
diffuse: bpy.props.FloatVectorProperty(name="Diffuse",
|
||||
subtype='COLOR',
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
default=[0.0,0.0,0.0])
|
||||
|
||||
specular: bpy.props.FloatVectorProperty(name="Specular",
|
||||
subtype='COLOR',
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
default=[0.0,0.0,0.0])
|
||||
|
||||
emissive: bpy.props.FloatVectorProperty(name="Emissive",
|
||||
subtype='COLOR',
|
||||
min=0.0,
|
||||
max=1.0,
|
||||
default=[0.0,0.0,0.0])
|
||||
|
||||
specular_power: bpy.props.FloatProperty(
|
||||
name="Specular Power",
|
||||
min=0.0,
|
||||
max=100.0,
|
||||
default=0.0,
|
||||
)
|
||||
|
||||
alpha_test: bpy.props.BoolProperty(
|
||||
name="Alpha Test",
|
||||
description="Alpha Func: VXCMP_GREATER. Alpha Ref: 1.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
alpha_blend: bpy.props.BoolProperty(
|
||||
name="Alpha Blend",
|
||||
description="Source Blend: VXBLEND_SRCALPHA. Dest Blend: VXBLEND_INVSRCALPHA.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
z_buffer: bpy.props.BoolProperty(
|
||||
name="Z Buffer",
|
||||
description="ZFunc: VXCMP_LESSEQUAL.",
|
||||
default=False,
|
||||
)
|
||||
|
||||
two_sided: bpy.props.BoolProperty(
|
||||
name="Two Sided",
|
||||
default=False,
|
||||
)
|
||||
|
||||
texture: bpy.props.PointerProperty(
|
||||
type=bpy.types.Image,
|
||||
name="Texture",
|
||||
description="The texture used for Virtools material"
|
||||
)
|
||||
|
||||
class BALLANCE_PG_virtools_group(bpy.types.PropertyGroup):
|
||||
group_name: bpy.props.StringProperty(
|
||||
name="Group Name",
|
||||
default=""
|
||||
)
|
||||
|
||||
class common_group_name_props(bpy.types.Operator):
|
||||
group_name_source: bpy.props.EnumProperty(
|
||||
name="Group Name Source",
|
||||
items=(('DEFINED', "Predefined", "Pre-defined group name."),
|
||||
('CUSTOM', "Custom", "User specified group name."),
|
||||
),
|
||||
)
|
||||
|
||||
group_name: bpy.props.EnumProperty(
|
||||
name="Group Name",
|
||||
description="Pick vanilla Ballance group name.",
|
||||
items=tuple((x, x, "") for x in UTILS_constants.propsVtGroups_availableGroups),
|
||||
)
|
||||
|
||||
custom_group_name: bpy.props.StringProperty(
|
||||
name="Custom Group Name",
|
||||
description="Input your custom group name.",
|
||||
default="",
|
||||
)
|
||||
|
||||
def parent_draw(self, parent_layout):
|
||||
parent_layout.prop(self, 'group_name_source', expand=True)
|
||||
if (self.group_name_source == 'CUSTOM'):
|
||||
parent_layout.prop(self, 'custom_group_name')
|
||||
else:
|
||||
parent_layout.prop(self, 'group_name')
|
||||
|
||||
def get_group_name_string(self):
|
||||
return str(self.custom_group_name if self.group_name_source == 'CUSTOM' else self.group_name)
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def get_virtools_material(mtl):
|
||||
return mtl.virtools_material
|
||||
|
||||
def get_virtools_material_data(mtl):
|
||||
data = get_virtools_material(mtl)
|
||||
return (
|
||||
data.enable_virtools_material,
|
||||
data.ambient,
|
||||
data.diffuse,
|
||||
data.specular,
|
||||
data.emissive,
|
||||
data.specular_power,
|
||||
data.alpha_test,
|
||||
data.alpha_blend,
|
||||
data.z_buffer,
|
||||
data.two_sided,
|
||||
data.texture
|
||||
)
|
||||
|
||||
def set_virtools_material_data(mtl, packed_data):
|
||||
data = get_virtools_material(mtl)
|
||||
# packed_data have the same order with the return value of `get_virtools_material_data`
|
||||
(data.enable_virtools_material,
|
||||
data.ambient, data.diffuse, data.specular, data.emissive, data.specular_power,
|
||||
data.alpha_test, data.alpha_blend, data.z_buffer, data.two_sided,
|
||||
data.texture) = packed_data
|
||||
|
||||
def get_active_virtools_group(obj):
|
||||
return obj.active_virtools_group
|
||||
def get_virtools_group(obj):
|
||||
return obj.virtools_group
|
||||
|
||||
def check_virtools_group_data(obj, probe):
|
||||
for item in get_virtools_group(obj):
|
||||
if probe == str(item.group_name):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def add_virtools_group_data(obj, new_data):
|
||||
# check exist
|
||||
if check_virtools_group_data(obj, new_data):
|
||||
# existed, give up
|
||||
return False
|
||||
|
||||
# "add" do not need operate active_virtools_group
|
||||
data = get_virtools_group(obj)
|
||||
it = data.add()
|
||||
it.name = ""
|
||||
it.group_name = new_data
|
||||
|
||||
return True
|
||||
|
||||
def remove_virtools_group_data(obj, rm_data):
|
||||
gp = get_virtools_group(obj)
|
||||
active_gp = get_active_virtools_group(obj)
|
||||
|
||||
for idx, item in enumerate(gp):
|
||||
if rm_data == str(item.group_name):
|
||||
# decrease active group if removed item is ahead of active group
|
||||
if idx <= active_gp:
|
||||
active_gp -= 1
|
||||
# remove
|
||||
gp.remove(idx)
|
||||
# indicate success
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def remove_virtools_group_data_by_index(obj, rm_idx):
|
||||
gp = get_virtools_group(obj)
|
||||
active_gp = get_active_virtools_group(obj)
|
||||
|
||||
# report error
|
||||
if rm_idx >= len(gp):
|
||||
return False
|
||||
|
||||
# remove
|
||||
if rm_idx <= active_gp:
|
||||
active_gp -= 1
|
||||
gp.remove(rm_idx)
|
||||
return True
|
||||
|
||||
def clear_virtools_group_data(obj):
|
||||
gp = get_virtools_group(obj)
|
||||
active_gp = get_active_virtools_group(obj)
|
||||
|
||||
gp.clear()
|
||||
active_gp = 0
|
||||
|
||||
def fill_virtools_group_data(obj, data_list):
|
||||
# clear first
|
||||
clear_virtools_group_data(obj)
|
||||
|
||||
# if no data to add, return
|
||||
if data_list is None:
|
||||
return
|
||||
|
||||
# add one by one after check duplication
|
||||
data = get_virtools_group(obj)
|
||||
for item in set(data_list):
|
||||
it = data.add()
|
||||
it.name = ""
|
||||
it.group_name = item
|
||||
|
||||
def get_virtools_group_data(obj):
|
||||
return tuple(str(item.group_name) for item in get_virtools_group(obj))
|
||||
|
||||
def register_props():
|
||||
bpy.types.Object.virtools_group = bpy.props.CollectionProperty(type=BALLANCE_PG_virtools_group)
|
||||
bpy.types.Object.active_virtools_group = bpy.props.IntProperty()
|
||||
bpy.types.Material.virtools_material = bpy.props.PointerProperty(type=BALLANCE_PG_virtools_material)
|
||||
|
||||
def unregister_props():
|
||||
del bpy.types.Material.virtools_material
|
||||
del bpy.types.Object.virtools_group
|
||||
del bpy.types.Object.active_virtools_group
|
||||
|
||||
|
@ -1,48 +0,0 @@
|
||||
import pathlib, zipfile, os, shutil
|
||||
from . import UTILS_constants
|
||||
|
||||
def compress(folder, zip_file):
|
||||
# remove target file first
|
||||
if os.path.isfile(zip_file):
|
||||
os.remove(zip_file)
|
||||
|
||||
# compress data
|
||||
with zipfile.ZipFile(zip_file, mode= 'w', compression= zipfile.ZIP_DEFLATED, compresslevel= 9, allowZip64= False) as zip_obj:
|
||||
# set global comment
|
||||
zip_obj.comment = UTILS_constants.bmfile_globalComment
|
||||
|
||||
# iterate folder and add files
|
||||
for folder_name, subfolders, filenames in os.walk(folder):
|
||||
for filename in filenames:
|
||||
# construct zip_entry
|
||||
abstract_filepath = os.path.join(folder_name, filename)
|
||||
relative_filepath = os.path.relpath(abstract_filepath, folder)
|
||||
zip_entry = zipfile.ZipInfo.from_file(abstract_filepath, arcname= relative_filepath)
|
||||
zip_entry.compress_type = zipfile.ZIP_DEFLATED
|
||||
|
||||
# compress file
|
||||
with open(abstract_filepath, 'rb') as fs_in:
|
||||
with zip_obj.open(zip_entry, mode= 'w') as zip_in:
|
||||
# References
|
||||
# https://stackoverflow.com/questions/53254622/zipfile-header-language-encoding-bit-set-differently-between-python2-and-python3
|
||||
# set unicode flag after opening internal file.
|
||||
# for the shit implement of python module zipfile, we need set Deflated:Maximum manually
|
||||
zip_entry.flag_bits |= UTILS_constants.bmfile_flagUnicode
|
||||
zip_entry.flag_bits |= UTILS_constants.bmfile_flagDeflatedMaximum
|
||||
# copy file
|
||||
shutil.copyfileobj(fs_in, zip_in)
|
||||
|
||||
|
||||
def decompress(folder, zip_file):
|
||||
with zipfile.ZipFile(zip_file, mode= 'r', compression= zipfile.ZIP_DEFLATED, compresslevel= 9, allowZip64= False) as zip_obj:
|
||||
for zip_entry in zip_obj.infolist():
|
||||
if (zip_entry.flag_bits & UTILS_constants.bmfile_flagUnicode) == 0:
|
||||
# lost unicode flag, throw error
|
||||
raise Exception("Zip Entry lost UNICODE flag.")
|
||||
|
||||
# decompress file
|
||||
zip_obj.extract(zip_entry, path= folder)
|
||||
|
||||
|
||||
|
||||
|
@ -1,247 +0,0 @@
|
||||
bl_info={
|
||||
"name":"Ballance Blender Plugin",
|
||||
"description":"Ballance mapping tools for Blender",
|
||||
"author":"yyc12345",
|
||||
"version":(3,0),
|
||||
"blender":(3,3,0),
|
||||
"category":"Object",
|
||||
"support":"TESTING",
|
||||
"warning": "Please read document before using this plugin.",
|
||||
"wiki_url": "https://github.com/yyc12345/BallanceBlenderHelper",
|
||||
"tracker_url": "https://github.com/yyc12345/BallanceBlenderHelper/issues"
|
||||
}
|
||||
|
||||
# =============================================
|
||||
# import system
|
||||
import bpy, bpy_extras
|
||||
import bpy.utils.previews
|
||||
import os
|
||||
# import my code (with reload)
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
if "UTILS_constants" in locals():
|
||||
importlib.reload(UTILS_constants)
|
||||
if "UTILS_functions" in locals():
|
||||
importlib.reload(UTILS_functions)
|
||||
if "UTILS_preferences" in locals():
|
||||
importlib.reload(UTILS_preferences)
|
||||
if "UTILS_file_io" in locals():
|
||||
importlib.reload(UTILS_file_io)
|
||||
if "UTILS_zip_helper" in locals():
|
||||
importlib.reload(UTILS_zip_helper)
|
||||
if "UTILS_virtools_prop" in locals():
|
||||
importlib.reload(UTILS_virtools_prop)
|
||||
if "UTILS_safe_eval" in locals():
|
||||
importlib.reload(UTILS_safe_eval)
|
||||
|
||||
if "BMFILE_export" in locals():
|
||||
importlib.reload(BMFILE_export)
|
||||
if "BMFILE_import" in locals():
|
||||
importlib.reload(BMFILE_import)
|
||||
|
||||
if "MODS_rail_uv" in locals():
|
||||
importlib.reload(MODS_rail_uv)
|
||||
if "MODS_3dsmax_align" in locals():
|
||||
importlib.reload(MODS_3dsmax_align)
|
||||
if "MODS_flatten_uv" in locals():
|
||||
importlib.reload(MODS_flatten_uv)
|
||||
|
||||
if "OBJS_add_components" in locals():
|
||||
importlib.reload(OBJS_add_components)
|
||||
if "OBJS_add_floors" in locals():
|
||||
importlib.reload(OBJS_add_floors)
|
||||
if "OBJS_add_rails" in locals():
|
||||
importlib.reload(OBJS_add_rails)
|
||||
if "OBJS_group_opers" in locals():
|
||||
importlib.reload(OBJS_group_opers)
|
||||
|
||||
if "NAMES_rename_system" in locals():
|
||||
importlib.reload(NAMES_rename_system)
|
||||
|
||||
if "PROPS_virtools_group" in locals():
|
||||
importlib.reload(PROPS_virtools_group)
|
||||
if "PROPS_virtools_material" in locals():
|
||||
importlib.reload(PROPS_virtools_material)
|
||||
|
||||
from . import UTILS_constants, UTILS_functions, UTILS_preferences, UTILS_virtools_prop, UTILS_safe_eval
|
||||
from . import BMFILE_export, BMFILE_import
|
||||
from . import MODS_3dsmax_align, MODS_flatten_uv, MODS_rail_uv
|
||||
from . import OBJS_add_components, OBJS_add_floors, OBJS_add_rails, OBJS_group_opers
|
||||
from . import NAMES_rename_system
|
||||
from . import PROPS_virtools_group, PROPS_virtools_material
|
||||
|
||||
# =============================================
|
||||
# menu system
|
||||
|
||||
class BALLANCE_MT_ThreeDViewerMenu(bpy.types.Menu):
|
||||
"""Ballance 3D operators"""
|
||||
bl_idname = "BALLANCE_MT_ThreeDViewerMenu"
|
||||
bl_label = "Ballance"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.operator(MODS_3dsmax_align.BALLANCE_OT_super_align.bl_idname)
|
||||
layout.operator(MODS_rail_uv.BALLANCE_OT_rail_uv.bl_idname)
|
||||
layout.operator(MODS_flatten_uv.BALLANCE_OT_flatten_uv.bl_idname)
|
||||
|
||||
class BALLANCE_MT_AddFloorMenu(bpy.types.Menu):
|
||||
"""Add Ballance floor"""
|
||||
bl_idname = "BALLANCE_MT_AddFloorMenu"
|
||||
bl_label = "Floors"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.label(text="Basic floor")
|
||||
for item in UTILS_constants.floor_basicBlockList:
|
||||
cop = layout.operator(
|
||||
OBJS_add_floors.BALLANCE_OT_add_floors.bl_idname,
|
||||
text=item, icon_value = UTILS_constants.icons_floorDict[item])
|
||||
cop.floor_type = item
|
||||
|
||||
layout.separator()
|
||||
layout.label(text="Derived floor")
|
||||
for item in UTILS_constants.floor_derivedBlockList:
|
||||
cop = layout.operator(
|
||||
OBJS_add_floors.BALLANCE_OT_add_floors.bl_idname,
|
||||
text=item, icon_value = UTILS_constants.icons_floorDict[item])
|
||||
cop.floor_type = item
|
||||
|
||||
|
||||
# =============================================
|
||||
# blender call system
|
||||
|
||||
classes = (
|
||||
UTILS_preferences.BallanceBlenderPluginPreferences,
|
||||
UTILS_preferences.MyPropertyGroup,
|
||||
|
||||
BMFILE_import.BALLANCE_OT_import_bm,
|
||||
BMFILE_export.BALLANCE_OT_export_bm,
|
||||
|
||||
MODS_rail_uv.BALLANCE_OT_rail_uv,
|
||||
MODS_3dsmax_align.BALLANCE_OT_super_align,
|
||||
MODS_flatten_uv.BALLANCE_OT_flatten_uv,
|
||||
BALLANCE_MT_ThreeDViewerMenu,
|
||||
|
||||
OBJS_add_components.BALLANCE_OT_add_components,
|
||||
OBJS_add_rails.BALLANCE_OT_add_rails,
|
||||
OBJS_add_floors.BALLANCE_OT_add_floors,
|
||||
BALLANCE_MT_AddFloorMenu,
|
||||
|
||||
NAMES_rename_system.BALLANCE_OT_rename_by_group,
|
||||
NAMES_rename_system.BALLANCE_OT_convert_name,
|
||||
NAMES_rename_system.BALLANCE_OT_auto_grouping,
|
||||
|
||||
UTILS_virtools_prop.BALLANCE_PG_virtools_material,
|
||||
UTILS_virtools_prop.BALLANCE_PG_virtools_group,
|
||||
PROPS_virtools_group.BALLANCE_OT_add_virtools_group,
|
||||
PROPS_virtools_group.BALLANCE_OT_rm_virtools_group,
|
||||
PROPS_virtools_group.BALLANCE_UL_virtools_group,
|
||||
PROPS_virtools_group.BALLANCE_PT_virtools_group,
|
||||
PROPS_virtools_material.BALLANCE_OT_apply_virtools_material,
|
||||
PROPS_virtools_material.BALLANCE_OT_parse_virtools_material,
|
||||
PROPS_virtools_material.BALLANCE_PT_virtools_material,
|
||||
|
||||
OBJS_group_opers.BALLANCE_OT_select_virtools_group,
|
||||
OBJS_group_opers.BALLANCE_OT_filter_virtools_group,
|
||||
OBJS_group_opers.BALLANCE_OT_ctx_set_group,
|
||||
OBJS_group_opers.BALLANCE_OT_ctx_unset_group,
|
||||
OBJS_group_opers.BALLANCE_OT_ctx_clear_group,
|
||||
|
||||
)
|
||||
|
||||
def menu_func_bm_import(self, context):
|
||||
self.layout.operator(BMFILE_import.BALLANCE_OT_import_bm.bl_idname, text="Ballance Map (.bmx)")
|
||||
def menu_func_bm_export(self, context):
|
||||
self.layout.operator(BMFILE_export.BALLANCE_OT_export_bm.bl_idname, text="Ballance Map (.bmx)")
|
||||
def menu_func_ballance_3d(self, context):
|
||||
layout = self.layout
|
||||
layout.menu(BALLANCE_MT_ThreeDViewerMenu.bl_idname)
|
||||
def menu_func_ballance_add(self, context):
|
||||
layout = self.layout
|
||||
layout.separator()
|
||||
layout.label(text="Ballance")
|
||||
layout.operator_menu_enum(
|
||||
OBJS_add_components.BALLANCE_OT_add_components.bl_idname,
|
||||
"elements_type", icon='MESH_ICOSPHERE', text="Elements")
|
||||
layout.operator(OBJS_add_rails.BALLANCE_OT_add_rails.bl_idname, icon='MESH_CIRCLE', text="Rail section")
|
||||
layout.menu(BALLANCE_MT_AddFloorMenu.bl_idname, icon='MESH_CUBE')
|
||||
def menu_func_ballance_rename(self, context):
|
||||
layout = self.layout
|
||||
layout.separator()
|
||||
col = layout.column()
|
||||
col.operator_context = 'INVOKE_DEFAULT'
|
||||
col.label(text="Ballance")
|
||||
col.operator(NAMES_rename_system.BALLANCE_OT_rename_by_group.bl_idname, icon='GREASEPENCIL')
|
||||
col.operator(NAMES_rename_system.BALLANCE_OT_convert_name.bl_idname, icon='ARROW_LEFTRIGHT')
|
||||
col.operator(NAMES_rename_system.BALLANCE_OT_auto_grouping.bl_idname, icon='GROUP')
|
||||
def menu_func_ballance_select(self, context):
|
||||
layout = self.layout
|
||||
layout.separator()
|
||||
layout.label(text="Ballance")
|
||||
layout.operator(OBJS_group_opers.BALLANCE_OT_select_virtools_group.bl_idname, icon='SELECT_SET')
|
||||
layout.operator(OBJS_group_opers.BALLANCE_OT_filter_virtools_group.bl_idname, icon='FILTER')
|
||||
def menu_func_ballance_grouping(self, context):
|
||||
layout = self.layout
|
||||
layout.separator()
|
||||
col = layout.column()
|
||||
col.operator_context = 'INVOKE_DEFAULT'
|
||||
col.label(text="Ballance")
|
||||
col.operator(OBJS_group_opers.BALLANCE_OT_ctx_set_group.bl_idname, icon='ADD', text="Group into...")
|
||||
col.operator(OBJS_group_opers.BALLANCE_OT_ctx_unset_group.bl_idname, icon='REMOVE', text="Ungroup from...")
|
||||
col.operator(OBJS_group_opers.BALLANCE_OT_ctx_clear_group.bl_idname, icon='TRASH', text="Clear Grouping")
|
||||
|
||||
|
||||
def register():
|
||||
# we need init all icon first
|
||||
icon_path = os.path.join(os.path.dirname(__file__), "icons")
|
||||
UTILS_constants.icons_floor = bpy.utils.previews.new()
|
||||
for key, value in UTILS_constants.floor_blockDict.items():
|
||||
blockIconName = "Ballance_FloorIcon_" + key
|
||||
UTILS_constants.icons_floor.load(blockIconName, os.path.join(icon_path, "floor", value["BindingDisplayTexture"]), 'IMAGE')
|
||||
UTILS_constants.icons_floorDict[key] = UTILS_constants.icons_floor[blockIconName].icon_id
|
||||
|
||||
for cls in classes:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
bpy.types.Scene.BallanceBlenderPluginProperty = bpy.props.PointerProperty(type=UTILS_preferences.MyPropertyGroup)
|
||||
UTILS_virtools_prop.register_props()
|
||||
|
||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_bm_import)
|
||||
bpy.types.TOPBAR_MT_file_export.append(menu_func_bm_export)
|
||||
|
||||
bpy.types.VIEW3D_MT_editor_menus.prepend(menu_func_ballance_3d)
|
||||
bpy.types.VIEW3D_MT_add.append(menu_func_ballance_add)
|
||||
bpy.types.OUTLINER_MT_collection.append(menu_func_ballance_rename)
|
||||
|
||||
bpy.types.VIEW3D_MT_select_object.append(menu_func_ballance_select)
|
||||
|
||||
bpy.types.VIEW3D_MT_object_context_menu.append(menu_func_ballance_grouping)
|
||||
bpy.types.OUTLINER_MT_object.append(menu_func_ballance_grouping) # share the same menu
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_bm_import)
|
||||
bpy.types.TOPBAR_MT_file_export.remove(menu_func_bm_export)
|
||||
|
||||
bpy.types.VIEW3D_MT_editor_menus.remove(menu_func_ballance_3d)
|
||||
bpy.types.VIEW3D_MT_add.remove(menu_func_ballance_add)
|
||||
bpy.types.OUTLINER_MT_collection.remove(menu_func_ballance_rename)
|
||||
|
||||
bpy.types.VIEW3D_MT_select_object.remove(menu_func_ballance_select)
|
||||
|
||||
bpy.types.VIEW3D_MT_object_context_menu.remove(menu_func_ballance_grouping)
|
||||
bpy.types.OUTLINER_MT_object.append(menu_func_ballance_grouping)
|
||||
|
||||
UTILS_virtools_prop.unregister_props()
|
||||
del bpy.types.Scene.BallanceBlenderPluginProperty
|
||||
|
||||
for cls in classes:
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
# we need uninstall all icon after all classes unregister
|
||||
bpy.utils.previews.remove(UTILS_constants.icons_floor)
|
||||
|
||||
if __name__=="__main__":
|
||||
register()
|
@ -1,483 +0,0 @@
|
||||
[
|
||||
{
|
||||
"Type": "Flat",
|
||||
"BindingDisplayTexture": "Flat.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Freedom",
|
||||
"InitColumnDirection": "PositiveY",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": false,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"0, 0, 0",
|
||||
"2.5+d2, 0, 0",
|
||||
"2.5+d2, 2.5+d1, 0",
|
||||
"0, 2.5+d1, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0, -d2",
|
||||
"0.5+d1, -d2",
|
||||
"0.5+d1, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"2.5+d2, 0, -5-d3",
|
||||
"2.5+d2, 2.5+d1, -5-d3",
|
||||
"0, 2.5+d1, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0, -d2",
|
||||
"0.5+d1, -d2",
|
||||
"0.5+d1, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 0, 0",
|
||||
"0, 2.5+d1, 0",
|
||||
"0, 2.5+d1, -5-d3",
|
||||
"0, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"0, 0.5+d1",
|
||||
"1+d3, 0.5+d1",
|
||||
"1+d3, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"2.5+d2, 2.5+d1, 0",
|
||||
"2.5+d2, 2.5+d1, -5-d3",
|
||||
"0, 2.5+d1, -5-d3",
|
||||
"0, 2.5+d1, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d2",
|
||||
"1+d3, -d2",
|
||||
"1+d3, 0.5",
|
||||
"0, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"2.5+d2, 2.5+d1, 0",
|
||||
"2.5+d2, 0, 0",
|
||||
"2.5+d2, 0, -5-d3",
|
||||
"2.5+d2, 2.5+d1, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5+d1",
|
||||
"0, 0",
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5+d1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"0, 0, 0",
|
||||
"0, 0, -5-d3",
|
||||
"2.5+d2, 0, -5-d3",
|
||||
"2.5+d2, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"1+d3, 0.5",
|
||||
"1+d3, -d2",
|
||||
"0, -d2"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": null,
|
||||
"TwoDRightSideExpand": null,
|
||||
"TwoDBottomSideExpand": null,
|
||||
"TwoDLeftSideExpand": null
|
||||
},
|
||||
{
|
||||
"Type": "NormalSinkTransition",
|
||||
"BindingDisplayTexture": "NormalSinkTransition.png",
|
||||
"UnitSize": "Large",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"0, 0, 0",
|
||||
"5, 0, 0",
|
||||
"0, 2.5, -0.7",
|
||||
"5, 5, 0",
|
||||
"0, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopProfilFlat",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"UVs": [
|
||||
"0, 1",
|
||||
"0, 0",
|
||||
"0.5, 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopProfilFlat",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1
|
||||
],
|
||||
"UVs": [
|
||||
"1, 0",
|
||||
"0.5, 1",
|
||||
"0, 0"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopProfilFlat",
|
||||
"Indices": [
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 1",
|
||||
"1, 0",
|
||||
"1, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"5, 0, -5-d3",
|
||||
"5, 5, -5-d3",
|
||||
"0, 5, -5-d3",
|
||||
"0, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 1",
|
||||
"1, 1",
|
||||
"1, 0",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0",
|
||||
"0, 2.5, -0.7",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 5, -5-d3",
|
||||
"0, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"0, 1",
|
||||
"0.5, 0.86",
|
||||
"0.5, -d3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
5,
|
||||
4,
|
||||
3,
|
||||
2
|
||||
],
|
||||
"UVs": [
|
||||
"1, 1",
|
||||
"1, -d3",
|
||||
"0.5, -d3",
|
||||
"0.5, 0.86"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"5, 5, 0",
|
||||
"5, 5, -5-d3",
|
||||
"0, 5, -5-d3",
|
||||
"0, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"5, 5, 0",
|
||||
"5, 0, 0",
|
||||
"5, 0, -5-d3",
|
||||
"5, 5, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 1",
|
||||
"0, 0",
|
||||
"1+d3, 0",
|
||||
"1+d3, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"5, 0, 0",
|
||||
"5, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 1",
|
||||
"1+d3, 1",
|
||||
"1+d3, 0",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": null,
|
||||
"TwoDRightSideExpand": {
|
||||
"Vertices": [
|
||||
"0, 5, 0",
|
||||
"5, 5, 0",
|
||||
"5, 5, -2.5",
|
||||
"0, 5, -2.5",
|
||||
"0, 5, -5-d3",
|
||||
"5, 5, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorder_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 1",
|
||||
"0, 0",
|
||||
"0.5, 0",
|
||||
"0.5, 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
5,
|
||||
4,
|
||||
3,
|
||||
2
|
||||
],
|
||||
"UVs": [
|
||||
"0.5+d3, 0",
|
||||
"0.5+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSideExpand": null,
|
||||
"TwoDLeftSideExpand": {
|
||||
"Vertices": [
|
||||
"0, 0, 0",
|
||||
"5, 0, 0",
|
||||
"5, 0, -2.5",
|
||||
"0, 0, -2.5",
|
||||
"0, 0, -5-d3",
|
||||
"5, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorder_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 1",
|
||||
"0.5, 0",
|
||||
"0, 0",
|
||||
"0, 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"0, 1",
|
||||
"0.5+d3, 1",
|
||||
"0.5+d3, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -1,680 +0,0 @@
|
||||
[
|
||||
{
|
||||
"Type": "NormalBorder",
|
||||
"BindingDisplayTexture": "NormalBorder.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Column",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"0, 0, 0",
|
||||
"2.5+d1, 0, 0",
|
||||
"2.5+d1, 2.5, 0",
|
||||
"0, 2.5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopFlat",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0, -d1",
|
||||
"0.5, -d1",
|
||||
"0.5, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, -5-d3",
|
||||
"2.5+d1, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0.5, 0.5",
|
||||
"0.5, -d1",
|
||||
"0, -d1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 2.5, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0, 0",
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 2.5, 0",
|
||||
"2.5+d1, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 2.5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d1",
|
||||
"1+d3, -d1",
|
||||
"1+d3, 0.5",
|
||||
"0, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, -5-d3",
|
||||
"2.5+d1, 2.5, -5-d3",
|
||||
"2.5+d1, 2.5, 0",
|
||||
"2.5+d1, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0.5",
|
||||
"1+d3, 0",
|
||||
"0, 0",
|
||||
"0, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, 0",
|
||||
"2.5+d1, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"1+d3, 0.5",
|
||||
"1+d3, -d1",
|
||||
"0, -d1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": null,
|
||||
"TwoDRightSideExpand": null,
|
||||
"TwoDBottomSideExpand": null,
|
||||
"TwoDLeftSideExpand": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, -2.5",
|
||||
"2.5+d1, 0, -2.5",
|
||||
"2.5+d1, 0, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorder_ForSide",
|
||||
"Indices": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 0.5",
|
||||
"0.5, -d1",
|
||||
"0, -d1",
|
||||
"0, 0.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d1",
|
||||
"0, 0.5",
|
||||
"0.5+d3, 0.5",
|
||||
"0.5+d3, -d1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Type": "NormalOutterCorner",
|
||||
"BindingDisplayTexture": "NormalOutterCorner.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"0, 0, 0",
|
||||
"2.5, 0, 0",
|
||||
"2.5, 2.5, 0",
|
||||
"0, 2.5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopFlat",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0, 1",
|
||||
"0.5, 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopFlat",
|
||||
"Indices": [
|
||||
0,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1, 0.5",
|
||||
"0.5, 1",
|
||||
"1, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"2.5, 0, -5-d3",
|
||||
"2.5, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0.5, 0.5",
|
||||
"0.5, 0",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0",
|
||||
"0, 2.5, 0",
|
||||
"0, 2.5, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"0, 0",
|
||||
"0, 0.5",
|
||||
"1+d3, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"2.5, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 2.5, 0",
|
||||
"2.5, 2.5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5",
|
||||
"0, 0.5",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"2.5, 0, -5-d3",
|
||||
"2.5, 2.5, -5-d3",
|
||||
"2.5, 2.5, 0",
|
||||
"2.5, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5",
|
||||
"0, 0.5",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"2.5, 0, 0",
|
||||
"2.5, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"1+d3, 0.5",
|
||||
"1+d3, 0",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 2.5, -2.5",
|
||||
"0, 0, -2.5",
|
||||
"0, 0, 0",
|
||||
"0, 2.5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorder_ForSide",
|
||||
"Indices": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 0.5",
|
||||
"0.5, 0",
|
||||
"0, 0",
|
||||
"0, 0.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"0, 0.5",
|
||||
"0.5+d3, 0.5",
|
||||
"0.5+d3, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSideExpand": null,
|
||||
"TwoDBottomSideExpand": null,
|
||||
"TwoDLeftSideExpand": {
|
||||
"Vertices": [
|
||||
"2.5, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, -2.5",
|
||||
"2.5, 0, -2.5",
|
||||
"2.5, 0, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorder_ForSide",
|
||||
"Indices": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 0.5",
|
||||
"0.5, 0",
|
||||
"0, 0",
|
||||
"0, 0.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"0, 0.5",
|
||||
"0.5+d3, 0.5",
|
||||
"0.5+d3, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Type": "NormalInnerCorner",
|
||||
"BindingDisplayTexture": "NormalInnerCorner.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": false,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"2.5, 0, 0",
|
||||
"2.5, 2.5, 0",
|
||||
"0, 0, 0",
|
||||
"0, 2.5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopFlat",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 1",
|
||||
"0, 1",
|
||||
"0.5, 0.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopFlat",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 1",
|
||||
"0.5, 0.5",
|
||||
"1, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"2.5, 0, -5-d3",
|
||||
"2.5, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0.5, 0.5",
|
||||
"0.5, 0",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 0, 0",
|
||||
"0, 2.5, 0",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"0, 0.5",
|
||||
"1+d3, 0.5",
|
||||
"1+d3, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"2.5, 2.5, 0",
|
||||
"2.5, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 2.5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5",
|
||||
"0, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"2.5, 2.5, 0",
|
||||
"2.5, 0, 0",
|
||||
"2.5, 0, -5-d3",
|
||||
"2.5, 2.5, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0, 0",
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"0, 0, 0",
|
||||
"0, 0, -5-d3",
|
||||
"2.5, 0, -5-d3",
|
||||
"2.5, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"1+d3, 0.5",
|
||||
"1+d3, 0",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": null,
|
||||
"TwoDRightSideExpand": null,
|
||||
"TwoDBottomSideExpand": null,
|
||||
"TwoDLeftSideExpand": null
|
||||
}
|
||||
]
|
@ -1,493 +0,0 @@
|
||||
[
|
||||
{
|
||||
"Type": "RibbonBorder",
|
||||
"BindingDisplayTexture": "RibbonBorder.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Column",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, 0",
|
||||
"2.5+d1, 2.5, -0.7",
|
||||
"0, 2.5, -0.7",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopFlat",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d1",
|
||||
"1, -d1",
|
||||
"1, 0.5",
|
||||
"0, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, -5-d3",
|
||||
"2.5+d1, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0.5, 0.5",
|
||||
"0.5, -d1",
|
||||
"0, -d1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0",
|
||||
"0, 2.5, -0.7",
|
||||
"0, 2.5, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"0, 0",
|
||||
"0.14, 0.5",
|
||||
"1+d3, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 2.5, -0.7",
|
||||
"2.5+d1, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 2.5, -0.7"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0.14, -d1",
|
||||
"1+d3, -d1",
|
||||
"1+d3, 0.5",
|
||||
"0.14, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, 0",
|
||||
"2.5+d1, 0, -5-d3",
|
||||
"2.5+d1, 2.5, -5-d3",
|
||||
"2.5+d1, 2.5, -0.7"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5",
|
||||
"0.14, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, 0",
|
||||
"2.5+d1, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"1+d3, 0.5",
|
||||
"1+d3, -d1",
|
||||
"0, -d1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": null,
|
||||
"TwoDRightSideExpand": null,
|
||||
"TwoDBottomSideExpand": null,
|
||||
"TwoDLeftSideExpand": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, -2.5",
|
||||
"2.5+d1, 0, -2.5",
|
||||
"2.5+d1, 0, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorder_ForSide",
|
||||
"Indices": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 0.5",
|
||||
"0.5, -d1",
|
||||
"0, -d1",
|
||||
"0, 0.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d1",
|
||||
"0, 0.5",
|
||||
"0.5+d3, 0.5",
|
||||
"0.5+d3, -d1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Type": "RibbonOutterCorner",
|
||||
"BindingDisplayTexture": "RibbonOutterCorner.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"0, 0, 0",
|
||||
"2.5, 0, 0",
|
||||
"2.5, 2.5, -0.7",
|
||||
"0, 2.5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopFlat",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"0, 1",
|
||||
"1, 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopFlat",
|
||||
"Indices": [
|
||||
0,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1, 0",
|
||||
"0, 1",
|
||||
"1, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"2.5, 0, -5-d3",
|
||||
"2.5, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0.5, 0.5",
|
||||
"0.5, 0",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0",
|
||||
"0, 2.5, 0",
|
||||
"0, 2.5, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"0, 0",
|
||||
"0, 0.5",
|
||||
"1+d3, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"2.5, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 2.5, 0",
|
||||
"2.5, 2.5, -0.7"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5",
|
||||
"0, 0.5",
|
||||
"0.14, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"2.5, 0, -5-d3",
|
||||
"2.5, 2.5, -5-d3",
|
||||
"2.5, 2.5, -0.7",
|
||||
"2.5, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5",
|
||||
"0.14, 0.5",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"2.5, 0, 0",
|
||||
"2.5, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"1+d3, 0.5",
|
||||
"1+d3, 0",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 2.5, -2.5",
|
||||
"0, 0, -2.5",
|
||||
"0, 0, 0",
|
||||
"0, 2.5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorder_ForSide",
|
||||
"Indices": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 0.5",
|
||||
"0.5, 0",
|
||||
"0, 0",
|
||||
"0, 0.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"0, 0.5",
|
||||
"0.5+d3, 0.5",
|
||||
"0.5+d3, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSideExpand": null,
|
||||
"TwoDBottomSideExpand": null,
|
||||
"TwoDLeftSideExpand": {
|
||||
"Vertices": [
|
||||
"2.5, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, -2.5",
|
||||
"2.5, 0, -2.5",
|
||||
"2.5, 0, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorder_ForSide",
|
||||
"Indices": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 0.5",
|
||||
"0.5, 0",
|
||||
"0, 0",
|
||||
"0, 0.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"0, 0.5",
|
||||
"0.5+d3, 0.5",
|
||||
"0.5+d3, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -1,680 +0,0 @@
|
||||
[
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"BindingDisplayTexture": "SinkBorder.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Column",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, 0",
|
||||
"2.5+d1, 2.5, -0.7",
|
||||
"0, 2.5, -0.7",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopProfil",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d1",
|
||||
"0.5, -d1",
|
||||
"0.5, 0.5",
|
||||
"0, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, -5-d3",
|
||||
"2.5+d1, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0.5, 0.5",
|
||||
"0.5, -d1",
|
||||
"0, -d1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0",
|
||||
"0, 2.5, -0.7",
|
||||
"0, 2.5, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"0, 0",
|
||||
"0.14, 0.5",
|
||||
"1+d3, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 2.5, -0.7",
|
||||
"2.5+d1, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 2.5, -0.7"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0.14, -d1",
|
||||
"1+d3, -d1",
|
||||
"1+d3, 0.5",
|
||||
"0.14, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, 0",
|
||||
"2.5+d1, 0, -5-d3",
|
||||
"2.5+d1, 2.5, -5-d3",
|
||||
"2.5+d1, 2.5, -0.7"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5",
|
||||
"0.14, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, 0",
|
||||
"2.5+d1, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"1+d3, 0.5",
|
||||
"1+d3, -d1",
|
||||
"0, -d1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": null,
|
||||
"TwoDRightSideExpand": null,
|
||||
"TwoDBottomSideExpand": null,
|
||||
"TwoDLeftSideExpand": {
|
||||
"Vertices": [
|
||||
"2.5+d1, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, -2.5",
|
||||
"2.5+d1, 0, -2.5",
|
||||
"2.5+d1, 0, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorder_ForSide",
|
||||
"Indices": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 0.5",
|
||||
"0.5, -d1",
|
||||
"0, -d1",
|
||||
"0, 0.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d1",
|
||||
"0, 0.5",
|
||||
"0.5+d3, 0.5",
|
||||
"0.5+d3, -d1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"BindingDisplayTexture": "SinkOutterCorner.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"0, 0, 0",
|
||||
"2.5, 0, 0",
|
||||
"2.5, 2.5, -0.7",
|
||||
"0, 2.5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopProfil",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0, 1",
|
||||
"0.5, 1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopProfil",
|
||||
"Indices": [
|
||||
0,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1, 0.5",
|
||||
"0.5, 1",
|
||||
"1, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"2.5, 0, -5-d3",
|
||||
"2.5, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0.5, 0.5",
|
||||
"0.5, 0",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0",
|
||||
"0, 2.5, 0",
|
||||
"0, 2.5, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"0, 0",
|
||||
"0, 0.5",
|
||||
"1+d3, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"2.5, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 2.5, 0",
|
||||
"2.5, 2.5, -0.7"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5",
|
||||
"0, 0.5",
|
||||
"0.14, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"2.5, 0, -5-d3",
|
||||
"2.5, 2.5, -5-d3",
|
||||
"2.5, 2.5, -0.7",
|
||||
"2.5, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5",
|
||||
"0.14, 0.5",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"2.5, 0, 0",
|
||||
"2.5, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"1+d3, 0.5",
|
||||
"1+d3, 0",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 2.5, -2.5",
|
||||
"0, 0, -2.5",
|
||||
"0, 0, 0",
|
||||
"0, 2.5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorder_ForSide",
|
||||
"Indices": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 0.5",
|
||||
"0.5, 0",
|
||||
"0, 0",
|
||||
"0, 0.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"0, 0.5",
|
||||
"0.5+d3, 0.5",
|
||||
"0.5+d3, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSideExpand": null,
|
||||
"TwoDBottomSideExpand": null,
|
||||
"TwoDLeftSideExpand": {
|
||||
"Vertices": [
|
||||
"2.5, 0, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, -2.5",
|
||||
"2.5, 0, -2.5",
|
||||
"2.5, 0, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorder_ForSide",
|
||||
"Indices": [
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 0.5",
|
||||
"0.5, 0",
|
||||
"0, 0",
|
||||
"0, 0.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"0, 0.5",
|
||||
"0.5+d3, 0.5",
|
||||
"0.5+d3, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"BindingDisplayTexture": "SinkInnerCorner.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": false,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"2.5, 0, -0.7",
|
||||
"2.5, 2.5, 0",
|
||||
"0, 0, -0.7",
|
||||
"0, 2.5, -0.7"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopProfil",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 1",
|
||||
"0, 1",
|
||||
"0.5, 0.5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "TRIANGLE",
|
||||
"Textures": "FloorTopProfil",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1
|
||||
],
|
||||
"UVs": [
|
||||
"0.5, 1",
|
||||
"0.5, 0.5",
|
||||
"1, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"2.5, 0, -5-d3",
|
||||
"2.5, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
3,
|
||||
2,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0.5, 0.5",
|
||||
"0.5, 0",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 0, -0.7",
|
||||
"0, 2.5, -0.7",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0.14, 0",
|
||||
"0.14, 0.5",
|
||||
"1+d3, 0.5",
|
||||
"1+d3, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"2.5, 2.5, 0",
|
||||
"2.5, 2.5, -5-d3",
|
||||
"0, 2.5, -5-d3",
|
||||
"0, 2.5, -0.7"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5",
|
||||
"0.14, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"2.5, 2.5, 0",
|
||||
"2.5, 0, -0.7",
|
||||
"2.5, 0, -5-d3",
|
||||
"2.5, 2.5, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0.5",
|
||||
"0.14, 0",
|
||||
"1+d3, 0",
|
||||
"1+d3, 0.5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"0, 0, -0.7",
|
||||
"0, 0, -5-d3",
|
||||
"2.5, 0, -5-d3",
|
||||
"2.5, 0, -0.7"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "FloorTopBorderless_ForSide",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0.14, 0.5",
|
||||
"1+d3, 0.5",
|
||||
"1+d3, 0",
|
||||
"0.14, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": null,
|
||||
"TwoDRightSideExpand": null,
|
||||
"TwoDBottomSideExpand": null,
|
||||
"TwoDLeftSideExpand": null
|
||||
}
|
||||
]
|
@ -1,827 +0,0 @@
|
||||
[
|
||||
{
|
||||
"Type": "WoodTrafo",
|
||||
"BindingDisplayTexture": "WoodTrafo.png",
|
||||
"UnitSize": "Large",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": true,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": true
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"0, 5, 0",
|
||||
"0, 0, 0",
|
||||
"5, 0, 0",
|
||||
"5, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallWood",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"1, 0",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 5, -5-d3",
|
||||
"5, 5, -5-d3",
|
||||
"5, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallWood",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"1, 0",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 5, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0",
|
||||
"0, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallWood",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"1, -d3",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"5, 5, -5-d3",
|
||||
"0, 5, -5-d3",
|
||||
"0, 5, 0",
|
||||
"5, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallWood",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"1, -d3",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"5, 0, -5-d3",
|
||||
"5, 5, -5-d3",
|
||||
"5, 5, 0",
|
||||
"5, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallWood",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"1, -d3",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"5, 0, -5-d3",
|
||||
"5, 0, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallWood",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"1, -d3",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": {
|
||||
"Vertices": [
|
||||
"0, 5, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0",
|
||||
"0, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallWood",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSideExpand": {
|
||||
"Vertices": [
|
||||
"5, 5, -5-d3",
|
||||
"0, 5, -5-d3",
|
||||
"0, 5, 0",
|
||||
"5, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallWood",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSideExpand": {
|
||||
"Vertices": [
|
||||
"5, 0, -5-d3",
|
||||
"5, 5, -5-d3",
|
||||
"5, 5, 0",
|
||||
"5, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallWood",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSideExpand": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"5, 0, -5-d3",
|
||||
"5, 0, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallWood",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Type": "StoneTrafo",
|
||||
"BindingDisplayTexture": "StoneTrafo.png",
|
||||
"UnitSize": "Large",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": true,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": true
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"0, 5, 0",
|
||||
"0, 0, 0",
|
||||
"5, 0, 0",
|
||||
"5, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallStone",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"1, 0",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 5, -5-d3",
|
||||
"5, 5, -5-d3",
|
||||
"5, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallStone",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"1, 0",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 5, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0",
|
||||
"0, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallStone",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"1, -d3",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"5, 5, -5-d3",
|
||||
"0, 5, -5-d3",
|
||||
"0, 5, 0",
|
||||
"5, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallStone",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"1, -d3",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"5, 0, -5-d3",
|
||||
"5, 5, -5-d3",
|
||||
"5, 5, 0",
|
||||
"5, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallStone",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"1, -d3",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"5, 0, -5-d3",
|
||||
"5, 0, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallStone",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"1, -d3",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": {
|
||||
"Vertices": [
|
||||
"0, 5, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0",
|
||||
"0, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallStone",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSideExpand": {
|
||||
"Vertices": [
|
||||
"5, 5, -5-d3",
|
||||
"0, 5, -5-d3",
|
||||
"0, 5, 0",
|
||||
"5, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallStone",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSideExpand": {
|
||||
"Vertices": [
|
||||
"5, 0, -5-d3",
|
||||
"5, 5, -5-d3",
|
||||
"5, 5, 0",
|
||||
"5, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallStone",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSideExpand": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"5, 0, -5-d3",
|
||||
"5, 0, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallStone",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"Type": "PaperTrafo",
|
||||
"BindingDisplayTexture": "PaperTrafo.png",
|
||||
"UnitSize": "Large",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": true,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": true
|
||||
},
|
||||
"ThreeDTopFace": {
|
||||
"Vertices": [
|
||||
"0, 5, 0",
|
||||
"0, 0, 0",
|
||||
"5, 0, 0",
|
||||
"5, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallPaper",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"1, 0",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"ThreeDBottomFace": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"0, 5, -5-d3",
|
||||
"5, 5, -5-d3",
|
||||
"5, 0, -5-d3"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallPaper",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, 0",
|
||||
"1, 0",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSide": {
|
||||
"Vertices": [
|
||||
"0, 5, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0",
|
||||
"0, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallPaper",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"1, -d3",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSide": {
|
||||
"Vertices": [
|
||||
"5, 5, -5-d3",
|
||||
"0, 5, -5-d3",
|
||||
"0, 5, 0",
|
||||
"5, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallPaper",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"1, -d3",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSide": {
|
||||
"Vertices": [
|
||||
"5, 0, -5-d3",
|
||||
"5, 5, -5-d3",
|
||||
"5, 5, 0",
|
||||
"5, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallPaper",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"1, -d3",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSide": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"5, 0, -5-d3",
|
||||
"5, 0, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallPaper",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"0, -d3",
|
||||
"1, -d3",
|
||||
"1, 1",
|
||||
"0, 1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDTopSideExpand": {
|
||||
"Vertices": [
|
||||
"0, 5, -5-d3",
|
||||
"0, 0, -5-d3",
|
||||
"0, 0, 0",
|
||||
"0, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallPaper",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDRightSideExpand": {
|
||||
"Vertices": [
|
||||
"5, 5, -5-d3",
|
||||
"0, 5, -5-d3",
|
||||
"0, 5, 0",
|
||||
"5, 5, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallPaper",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDBottomSideExpand": {
|
||||
"Vertices": [
|
||||
"5, 0, -5-d3",
|
||||
"5, 5, -5-d3",
|
||||
"5, 5, 0",
|
||||
"5, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallPaper",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"TwoDLeftSideExpand": {
|
||||
"Vertices": [
|
||||
"0, 0, -5-d3",
|
||||
"5, 0, -5-d3",
|
||||
"5, 0, 0",
|
||||
"0, 0, 0"
|
||||
],
|
||||
"Faces": [
|
||||
{
|
||||
"Type": "RECTANGLE",
|
||||
"Textures": "BallPaper",
|
||||
"Indices": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3
|
||||
],
|
||||
"UVs": [
|
||||
"1+d3, 0",
|
||||
"1+d3, 1",
|
||||
"0, 1",
|
||||
"0, 0"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
@ -1,317 +0,0 @@
|
||||
[
|
||||
{
|
||||
"Type": "NormalFloor",
|
||||
"BindingDisplayTexture": "NormalFloor.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Column",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "NormalBorder",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;2dBottom;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "d1, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalBorder",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "2dBottom;False;2dTop;2dRight;3dTop;3dBottom",
|
||||
"StartPosition": "d1, (2.5*1), 0",
|
||||
"ExpandParam": "d1, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "NormalFloorTerminal",
|
||||
"BindingDisplayTexture": "NormalFloorTerminal.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "NormalOutterCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;2dBottom;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalOutterCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;2dBottom;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "Normal1x1",
|
||||
"BindingDisplayTexture": "Normal1x1.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": true,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "NormalOutterCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalOutterCorner",
|
||||
"Rotation": "R90",
|
||||
"SideSync": "2dLeft;False;False;2dBottom;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalOutterCorner",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "2dBottom;False;False;2dRight;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalOutterCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "NormalPlatform",
|
||||
"BindingDisplayTexture": "NormalPlatform.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Freedom",
|
||||
"InitColumnDirection": "PositiveY",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": true,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "Flat",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;False;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), (2.5*1), 0",
|
||||
"ExpandParam": "d1, d2"
|
||||
},
|
||||
{
|
||||
"Type": "NormalBorder",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), 0, 0",
|
||||
"ExpandParam": "d2, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalBorder",
|
||||
"Rotation": "R90",
|
||||
"SideSync": "False;False;False;2dBottom;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2) +d2, (2.5*1), 0",
|
||||
"ExpandParam": "d1, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalBorder",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "False;False;False;2dRight;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1) +d2, (2.5*2) +d1, 0",
|
||||
"ExpandParam": "d2, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalBorder",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "False;False;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1) +d1, 0",
|
||||
"ExpandParam": "d1, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalOutterCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalOutterCorner",
|
||||
"Rotation": "R90",
|
||||
"SideSync": "2dLeft;False;False;2dBottom;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2) +d2, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalOutterCorner",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "2dBottom;False;False;2dRight;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2) +d2, (2.5*2) +d1, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalOutterCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*2) +d1, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "NormalLConnector",
|
||||
"BindingDisplayTexture": "NormalLConnector.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "NormalOutterCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalBorder",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;2dBottom;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalInnerCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalBorder",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "NormalTConnector",
|
||||
"BindingDisplayTexture": "NormalTConnector.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": false,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "NormalInnerCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "False;2dBottom;2dLeft;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalInnerCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalBorder",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;False;2dLeft;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1), 0",
|
||||
"ExpandParam": "1, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "NormalCrossroad",
|
||||
"BindingDisplayTexture": "NormalCrossroad.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": false,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "NormalInnerCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalInnerCorner",
|
||||
"Rotation": "R90",
|
||||
"SideSync": "False;2dTop;2dRight;False;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalInnerCorner",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "False;2dLeft;2dTop;False;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "NormalInnerCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "False;2dBottom;2dLeft;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,397 +0,0 @@
|
||||
[
|
||||
{
|
||||
"Type": "SinkFloor",
|
||||
"BindingDisplayTexture": "SinkFloor.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Column",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;2dBottom;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "d1, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "2dBottom;False;2dTop;2dRight;3dTop;3dBottom",
|
||||
"StartPosition": "d1, (2.5*1), 0",
|
||||
"ExpandParam": "d1, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "SinkFloorTerminal",
|
||||
"BindingDisplayTexture": "SinkFloorTerminal.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;2dBottom;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;2dBottom;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "Sink1x1",
|
||||
"BindingDisplayTexture": "Sink1x1.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": true,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R90",
|
||||
"SideSync": "2dLeft;False;False;2dBottom;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "2dBottom;False;False;2dRight;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "SinkPlatform",
|
||||
"BindingDisplayTexture": "SinkPlatform.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Freedom",
|
||||
"InitColumnDirection": "PositiveY",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": true,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "Flat",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;False;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), (2.5*1), -0.7",
|
||||
"ExpandParam": "d1, d2"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), 0, 0",
|
||||
"ExpandParam": "d2, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R90",
|
||||
"SideSync": "False;False;False;2dBottom;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2) +d2, (2.5*1), 0",
|
||||
"ExpandParam": "d1, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "False;False;False;2dRight;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1) +d2, (2.5*2) +d1, 0",
|
||||
"ExpandParam": "d2, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "False;False;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1) +d1, 0",
|
||||
"ExpandParam": "d1, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R90",
|
||||
"SideSync": "2dLeft;False;False;2dBottom;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2) +d2, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "2dBottom;False;False;2dRight;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2) +d2, (2.5*2) +d1, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*2) +d1, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "RibbonPlatform",
|
||||
"BindingDisplayTexture": "RibbonPlatform.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Freedom",
|
||||
"InitColumnDirection": "PositiveY",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": true,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "Flat",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;False;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), (2.5*1), -0.7",
|
||||
"ExpandParam": "d1, d2"
|
||||
},
|
||||
{
|
||||
"Type": "RibbonBorder",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), 0, 0",
|
||||
"ExpandParam": "d2, 0"
|
||||
},
|
||||
{
|
||||
"Type": "RibbonBorder",
|
||||
"Rotation": "R90",
|
||||
"SideSync": "False;False;False;2dBottom;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2) +d2, (2.5*1), 0",
|
||||
"ExpandParam": "d1, 0"
|
||||
},
|
||||
{
|
||||
"Type": "RibbonBorder",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "False;False;False;2dRight;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1) +d2, (2.5*2) +d1, 0",
|
||||
"ExpandParam": "d2, 0"
|
||||
},
|
||||
{
|
||||
"Type": "RibbonBorder",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "False;False;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1) +d1, 0",
|
||||
"ExpandParam": "d1, 0"
|
||||
},
|
||||
{
|
||||
"Type": "RibbonOutterCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "RibbonOutterCorner",
|
||||
"Rotation": "R90",
|
||||
"SideSync": "2dLeft;False;False;2dBottom;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2) +d2, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "RibbonOutterCorner",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "2dBottom;False;False;2dRight;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2) +d2, (2.5*2) +d1, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "RibbonOutterCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*2) +d1, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "SinkLConnector",
|
||||
"BindingDisplayTexture": "SinkLConnector.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;2dBottom;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "SinkTConnector",
|
||||
"BindingDisplayTexture": "SinkTConnector.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": false,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "False;2dBottom;2dLeft;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;False;2dLeft;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1), 0",
|
||||
"ExpandParam": "1, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "SinkCrossroad",
|
||||
"BindingDisplayTexture": "SinkCrossroad.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Static",
|
||||
"InitColumnDirection": "PositiveX",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": false,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R90",
|
||||
"SideSync": "False;2dTop;2dRight;False;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1), 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "False;2dLeft;2dTop;False;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "False;2dBottom;2dLeft;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -1,262 +0,0 @@
|
||||
[
|
||||
{
|
||||
"Type": "WideFloor",
|
||||
"BindingDisplayTexture": "WideFloor.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Freedom",
|
||||
"InitColumnDirection": "PositiveY",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;2dBottom;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "d2, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "2dBottom;False;2dTop;2dRight;3dTop;3dBottom",
|
||||
"StartPosition": "d2, (2.5*2)+d1, 0",
|
||||
"ExpandParam": "d2, 0"
|
||||
},
|
||||
{
|
||||
"Type": "Flat",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1), -0.7",
|
||||
"ExpandParam": "d1, d2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "WideFloorTerminal",
|
||||
"BindingDisplayTexture": "WideFloorTerminal.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Column",
|
||||
"InitColumnDirection": "PositiveY",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": true,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;2dBottom;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;2dBottom;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*2)+d1, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "False;2dBottom;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*1) +d1, 0",
|
||||
"ExpandParam": "d1, 0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "WideLConnector",
|
||||
"BindingDisplayTexture": "WideLConnector.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Column",
|
||||
"InitColumnDirection": "PositiveY",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": true,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "SinkOutterCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;2dBottom;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*1), 0, 0",
|
||||
"ExpandParam": "d1 + 1, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2) + d1, (2.5*2) + d1, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;False;False;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*2)+d1, 0",
|
||||
"ExpandParam": "d1 + 1, 0"
|
||||
},
|
||||
{
|
||||
"Type": "Flat",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "2.5, 2.5, -0.7",
|
||||
"ExpandParam": "d1, d1 + 1"
|
||||
},
|
||||
{
|
||||
"Type": "Flat",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;2dRight;False;False;3dTop;3dBottom",
|
||||
"StartPosition": "2.5, (2.5 * 2) + d1, -0.7",
|
||||
"ExpandParam": "0, d1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "WideTConnector",
|
||||
"BindingDisplayTexture": "WideTConnector.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Column",
|
||||
"InitColumnDirection": "PositiveY",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": true,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": false,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "False;2dBottom;2dLeft;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2) + d1, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2) + d1, (2.5*2) + d1, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkBorder",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "2dRight;False;2dLeft;2dTop;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*2) + d1, 0",
|
||||
"ExpandParam": "d1 + 2, 0"
|
||||
},
|
||||
{
|
||||
"Type": "Flat",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "2.5, 2.5, -0.7",
|
||||
"ExpandParam": "d1, d1+1"
|
||||
},
|
||||
{
|
||||
"Type": "Flat",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "2.5, 0, -0.7",
|
||||
"ExpandParam": "0, d1"
|
||||
},
|
||||
{
|
||||
"Type": "Flat",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;2dRight;False;False;3dTop;3dBottom",
|
||||
"StartPosition": "2.5, (2.5*2)+d1, -0.7",
|
||||
"ExpandParam": "0, d1"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"Type": "WideCrossroad",
|
||||
"BindingDisplayTexture": "WideCrossroad.png",
|
||||
"UnitSize": "Small",
|
||||
"ExpandType": "Column",
|
||||
"InitColumnDirection": "PositiveY",
|
||||
"DefaultSideConfig": {
|
||||
"UseTwoDTop": false,
|
||||
"UseTwoDRight": false,
|
||||
"UseTwoDBottom": false,
|
||||
"UseTwoDLeft": false,
|
||||
"UseThreeDTop": true,
|
||||
"UseThreeDBottom": false
|
||||
},
|
||||
"SmashedBlocks": [
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;2dRight;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2)+d1, (2.5*2)+d1, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R90",
|
||||
"SideSync": "False;2dTop;2dRight;False;3dTop;3dBottom",
|
||||
"StartPosition": "0, (2.5*2)+d1, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R180",
|
||||
"SideSync": "False;2dLeft;2dTop;False;3dTop;3dBottom",
|
||||
"StartPosition": "0, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "SinkInnerCorner",
|
||||
"Rotation": "R270",
|
||||
"SideSync": "False;2dBottom;2dLeft;False;3dTop;3dBottom",
|
||||
"StartPosition": "(2.5*2)+d1, 0, 0",
|
||||
"ExpandParam": "0, 0"
|
||||
},
|
||||
{
|
||||
"Type": "Flat",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "2dTop;False;2dBottom;False;3dTop;3dBottom",
|
||||
"StartPosition": "0, 2.5, -0.7",
|
||||
"ExpandParam": "d1, d1+2"
|
||||
},
|
||||
{
|
||||
"Type": "Flat",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;False;False;2dLeft;3dTop;3dBottom",
|
||||
"StartPosition": "2.5, 0, -0.7",
|
||||
"ExpandParam": "0, d1"
|
||||
},
|
||||
{
|
||||
"Type": "Flat",
|
||||
"Rotation": "R0",
|
||||
"SideSync": "False;2dRight;False;False;3dTop;3dBottom",
|
||||
"StartPosition": "2.5, (2.5*2)+d1, -0.7",
|
||||
"ExpandParam": "0, d1"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
150
bbp_ng/.gitignore
vendored
Normal file
150
bbp_ng/.gitignore
vendored
Normal file
@ -0,0 +1,150 @@
|
||||
# My Ban
|
||||
PyBMap/*.dll
|
||||
PyBMap/*.so
|
||||
PyBMap/*.dylib
|
||||
PyBMap/*.bin
|
||||
PyBMap/*.pdb
|
||||
|
||||
icons/*
|
||||
!icons/.gitkeep
|
||||
jsons/*
|
||||
!jsons/.gitkeep
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
402
bbp_ng/.style.yapf
Normal file
402
bbp_ng/.style.yapf
Normal file
@ -0,0 +1,402 @@
|
||||
[style]
|
||||
# Align closing bracket with visual indentation.
|
||||
align_closing_bracket_with_visual_indent=True
|
||||
|
||||
# Allow dictionary keys to exist on multiple lines. For example:
|
||||
#
|
||||
# x = {
|
||||
# ('this is the first element of a tuple',
|
||||
# 'this is the second element of a tuple'):
|
||||
# value,
|
||||
# }
|
||||
allow_multiline_dictionary_keys=False
|
||||
|
||||
# Allow lambdas to be formatted on more than one line.
|
||||
allow_multiline_lambdas=False
|
||||
|
||||
# Allow splitting before a default / named assignment in an argument list.
|
||||
allow_split_before_default_or_named_assigns=False
|
||||
|
||||
# Allow splits before the dictionary value.
|
||||
allow_split_before_dict_value=False
|
||||
|
||||
# Let spacing indicate operator precedence. For example:
|
||||
#
|
||||
# a = 1 * 2 + 3 / 4
|
||||
# b = 1 / 2 - 3 * 4
|
||||
# c = (1 + 2) * (3 - 4)
|
||||
# d = (1 - 2) / (3 + 4)
|
||||
# e = 1 * 2 - 3
|
||||
# f = 1 + 2 + 3 + 4
|
||||
#
|
||||
# will be formatted as follows to indicate precedence:
|
||||
#
|
||||
# a = 1*2 + 3/4
|
||||
# b = 1/2 - 3*4
|
||||
# c = (1+2) * (3-4)
|
||||
# d = (1-2) / (3+4)
|
||||
# e = 1*2 - 3
|
||||
# f = 1 + 2 + 3 + 4
|
||||
#
|
||||
arithmetic_precedence_indication=False
|
||||
|
||||
# Number of blank lines surrounding top-level function and class
|
||||
# definitions.
|
||||
blank_lines_around_top_level_definition=1
|
||||
|
||||
# Number of blank lines between top-level imports and variable
|
||||
# definitions.
|
||||
blank_lines_between_top_level_imports_and_variables=1
|
||||
|
||||
# Insert a blank line before a class-level docstring.
|
||||
blank_line_before_class_docstring=False
|
||||
|
||||
# Insert a blank line before a module docstring.
|
||||
blank_line_before_module_docstring=False
|
||||
|
||||
# Insert a blank line before a 'def' or 'class' immediately nested
|
||||
# within another 'def' or 'class'. For example:
|
||||
#
|
||||
# class Foo:
|
||||
# # <------ this blank line
|
||||
# def method():
|
||||
# pass
|
||||
blank_line_before_nested_class_or_def=False
|
||||
|
||||
# Do not split consecutive brackets. Only relevant when
|
||||
# dedent_closing_brackets is set. For example:
|
||||
#
|
||||
# call_func_that_takes_a_dict(
|
||||
# {
|
||||
# 'key1': 'value1',
|
||||
# 'key2': 'value2',
|
||||
# }
|
||||
# )
|
||||
#
|
||||
# would reformat to:
|
||||
#
|
||||
# call_func_that_takes_a_dict({
|
||||
# 'key1': 'value1',
|
||||
# 'key2': 'value2',
|
||||
# })
|
||||
coalesce_brackets=False
|
||||
|
||||
# The column limit.
|
||||
column_limit=79
|
||||
|
||||
# The style for continuation alignment. Possible values are:
|
||||
#
|
||||
# - SPACE: Use spaces for continuation alignment. This is default behavior.
|
||||
# - FIXED: Use fixed number (CONTINUATION_INDENT_WIDTH) of columns
|
||||
# (ie: CONTINUATION_INDENT_WIDTH/INDENT_WIDTH tabs or
|
||||
# CONTINUATION_INDENT_WIDTH spaces) for continuation alignment.
|
||||
# - VALIGN-RIGHT: Vertically align continuation lines to multiple of
|
||||
# INDENT_WIDTH columns. Slightly right (one tab or a few spaces) if
|
||||
# cannot vertically align continuation lines with indent characters.
|
||||
continuation_align_style=SPACE
|
||||
|
||||
# Indent width used for line continuations.
|
||||
continuation_indent_width=4
|
||||
|
||||
# Put closing brackets on a separate line, dedented, if the bracketed
|
||||
# expression can't fit in a single line. Applies to all kinds of brackets,
|
||||
# including function definitions and calls. For example:
|
||||
#
|
||||
# config = {
|
||||
# 'key1': 'value1',
|
||||
# 'key2': 'value2',
|
||||
# } # <--- this bracket is dedented and on a separate line
|
||||
#
|
||||
# time_series = self.remote_client.query_entity_counters(
|
||||
# entity='dev3246.region1',
|
||||
# key='dns.query_latency_tcp',
|
||||
# transform=Transformation.AVERAGE(window=timedelta(seconds=60)),
|
||||
# start_ts=now()-timedelta(days=3),
|
||||
# end_ts=now(),
|
||||
# ) # <--- this bracket is dedented and on a separate line
|
||||
dedent_closing_brackets=False
|
||||
|
||||
# Disable the heuristic which places each list element on a separate line
|
||||
# if the list is comma-terminated.
|
||||
disable_ending_comma_heuristic=False
|
||||
|
||||
# Place each dictionary entry onto its own line.
|
||||
each_dict_entry_on_separate_line=True
|
||||
|
||||
# Require multiline dictionary even if it would normally fit on one line.
|
||||
# For example:
|
||||
#
|
||||
# config = {
|
||||
# 'key1': 'value1'
|
||||
# }
|
||||
force_multiline_dict=True
|
||||
|
||||
# The regex for an i18n comment. The presence of this comment stops
|
||||
# reformatting of that line, because the comments are required to be
|
||||
# next to the string they translate.
|
||||
i18n_comment=
|
||||
|
||||
# The i18n function call names. The presence of this function stops
|
||||
# reformattting on that line, because the string it has cannot be moved
|
||||
# away from the i18n comment.
|
||||
i18n_function_call=
|
||||
|
||||
# Indent blank lines.
|
||||
indent_blank_lines=True
|
||||
|
||||
# Put closing brackets on a separate line, indented, if the bracketed
|
||||
# expression can't fit in a single line. Applies to all kinds of brackets,
|
||||
# including function definitions and calls. For example:
|
||||
#
|
||||
# config = {
|
||||
# 'key1': 'value1',
|
||||
# 'key2': 'value2',
|
||||
# } # <--- this bracket is indented and on a separate line
|
||||
#
|
||||
# time_series = self.remote_client.query_entity_counters(
|
||||
# entity='dev3246.region1',
|
||||
# key='dns.query_latency_tcp',
|
||||
# transform=Transformation.AVERAGE(window=timedelta(seconds=60)),
|
||||
# start_ts=now()-timedelta(days=3),
|
||||
# end_ts=now(),
|
||||
# ) # <--- this bracket is indented and on a separate line
|
||||
indent_closing_brackets=False
|
||||
|
||||
# Indent the dictionary value if it cannot fit on the same line as the
|
||||
# dictionary key. For example:
|
||||
#
|
||||
# config = {
|
||||
# 'key1':
|
||||
# 'value1',
|
||||
# 'key2': value1 +
|
||||
# value2,
|
||||
# }
|
||||
indent_dictionary_value=False
|
||||
|
||||
# The number of columns to use for indentation.
|
||||
indent_width=4
|
||||
|
||||
# Join short lines into one line. E.g., single line 'if' statements.
|
||||
join_multiple_lines=True
|
||||
|
||||
# Do not include spaces around selected binary operators. For example:
|
||||
#
|
||||
# 1 + 2 * 3 - 4 / 5
|
||||
#
|
||||
# will be formatted as follows when configured with "*,/":
|
||||
#
|
||||
# 1 + 2*3 - 4/5
|
||||
no_spaces_around_selected_binary_operators=
|
||||
|
||||
# Use spaces around default or named assigns.
|
||||
spaces_around_default_or_named_assign=True
|
||||
|
||||
# Adds a space after the opening '{' and before the ending '}' dict
|
||||
# delimiters.
|
||||
#
|
||||
# {1: 2}
|
||||
#
|
||||
# will be formatted as:
|
||||
#
|
||||
# { 1: 2 }
|
||||
spaces_around_dict_delimiters=False
|
||||
|
||||
# Adds a space after the opening '[' and before the ending ']' list
|
||||
# delimiters.
|
||||
#
|
||||
# [1, 2]
|
||||
#
|
||||
# will be formatted as:
|
||||
#
|
||||
# [ 1, 2 ]
|
||||
spaces_around_list_delimiters=False
|
||||
|
||||
# Use spaces around the power operator.
|
||||
spaces_around_power_operator=False
|
||||
|
||||
# Use spaces around the subscript / slice operator. For example:
|
||||
#
|
||||
# my_list[1 : 10 : 2]
|
||||
spaces_around_subscript_colon=False
|
||||
|
||||
# Adds a space after the opening '(' and before the ending ')' tuple
|
||||
# delimiters.
|
||||
#
|
||||
# (1, 2, 3)
|
||||
#
|
||||
# will be formatted as:
|
||||
#
|
||||
# ( 1, 2, 3 )
|
||||
spaces_around_tuple_delimiters=False
|
||||
|
||||
# The number of spaces required before a trailing comment.
|
||||
# This can be a single value (representing the number of spaces
|
||||
# before each trailing comment) or list of values (representing
|
||||
# alignment column values; trailing comments within a block will
|
||||
# be aligned to the first column value that is greater than the maximum
|
||||
# line length within the block). For example:
|
||||
#
|
||||
# With spaces_before_comment=5:
|
||||
#
|
||||
# 1 + 1 # Adding values
|
||||
#
|
||||
# will be formatted as:
|
||||
#
|
||||
# 1 + 1 # Adding values <-- 5 spaces between the end of the
|
||||
# # statement and comment
|
||||
#
|
||||
# With spaces_before_comment=15, 20:
|
||||
#
|
||||
# 1 + 1 # Adding values
|
||||
# two + two # More adding
|
||||
#
|
||||
# longer_statement # This is a longer statement
|
||||
# short # This is a shorter statement
|
||||
#
|
||||
# a_very_long_statement_that_extends_beyond_the_final_column # Comment
|
||||
# short # This is a shorter statement
|
||||
#
|
||||
# will be formatted as:
|
||||
#
|
||||
# 1 + 1 # Adding values <-- end of line comments in block
|
||||
# # aligned to col 15
|
||||
# two + two # More adding
|
||||
#
|
||||
# longer_statement # This is a longer statement <-- end of line
|
||||
# # comments in block aligned to col 20
|
||||
# short # This is a shorter statement
|
||||
#
|
||||
# a_very_long_statement_that_extends_beyond_the_final_column # Comment <-- the end of line comments are aligned based on the line length
|
||||
# short # This is a shorter statement
|
||||
#
|
||||
spaces_before_comment=2
|
||||
|
||||
# Insert a space between the ending comma and closing bracket of a list,
|
||||
# etc.
|
||||
space_between_ending_comma_and_closing_bracket=True
|
||||
|
||||
# Use spaces inside brackets, braces, and parentheses. For example:
|
||||
#
|
||||
# method_call( 1 )
|
||||
# my_dict[ 3 ][ 1 ][ get_index( *args, **kwargs ) ]
|
||||
# my_set = { 1, 2, 3 }
|
||||
space_inside_brackets=False
|
||||
|
||||
# Split before arguments.
|
||||
split_all_comma_separated_values=False
|
||||
|
||||
# Split before arguments, but do not split all subexpressions recursively
|
||||
# (unless needed).
|
||||
split_all_top_level_comma_separated_values=False
|
||||
|
||||
# Split before arguments if the argument list is terminated by a
|
||||
# comma.
|
||||
split_arguments_when_comma_terminated=False
|
||||
|
||||
# Set to True to prefer splitting before '+', '-', '*', '/', '//', or '@'
|
||||
# rather than after.
|
||||
split_before_arithmetic_operator=False
|
||||
|
||||
# Set to True to prefer splitting before '&', '|' or '^' rather than
|
||||
# after.
|
||||
split_before_bitwise_operator=True
|
||||
|
||||
# Split before the closing bracket if a list or dict literal doesn't fit on
|
||||
# a single line.
|
||||
split_before_closing_bracket=True
|
||||
|
||||
# Split before a dictionary or set generator (comp_for). For example, note
|
||||
# the split before the 'for':
|
||||
#
|
||||
# foo = {
|
||||
# variable: 'Hello world, have a nice day!'
|
||||
# for variable in bar if variable != 42
|
||||
# }
|
||||
split_before_dict_set_generator=True
|
||||
|
||||
# Split before the '.' if we need to split a longer expression:
|
||||
#
|
||||
# foo = ('This is a really long string: {}, {}, {}, {}'.format(a, b, c, d))
|
||||
#
|
||||
# would reformat to something like:
|
||||
#
|
||||
# foo = ('This is a really long string: {}, {}, {}, {}'
|
||||
# .format(a, b, c, d))
|
||||
split_before_dot=False
|
||||
|
||||
# Split after the opening paren which surrounds an expression if it doesn't
|
||||
# fit on a single line.
|
||||
split_before_expression_after_opening_paren=False
|
||||
|
||||
# If an argument / parameter list is going to be split, then split before
|
||||
# the first argument.
|
||||
split_before_first_argument=False
|
||||
|
||||
# Set to True to prefer splitting before 'and' or 'or' rather than
|
||||
# after.
|
||||
split_before_logical_operator=True
|
||||
|
||||
# Split named assignments onto individual lines.
|
||||
split_before_named_assigns=True
|
||||
|
||||
# Set to True to split list comprehensions and generators that have
|
||||
# non-trivial expressions and multiple clauses before each of these
|
||||
# clauses. For example:
|
||||
#
|
||||
# result = [
|
||||
# a_long_var + 100 for a_long_var in xrange(1000)
|
||||
# if a_long_var % 10]
|
||||
#
|
||||
# would reformat to something like:
|
||||
#
|
||||
# result = [
|
||||
# a_long_var + 100
|
||||
# for a_long_var in xrange(1000)
|
||||
# if a_long_var % 10]
|
||||
split_complex_comprehension=False
|
||||
|
||||
# The penalty for splitting right after the opening bracket.
|
||||
split_penalty_after_opening_bracket=300
|
||||
|
||||
# The penalty for splitting the line after a unary operator.
|
||||
split_penalty_after_unary_operator=10000
|
||||
|
||||
# The penalty of splitting the line around the '+', '-', '*', '/', '//',
|
||||
# `%`, and '@' operators.
|
||||
split_penalty_arithmetic_operator=300
|
||||
|
||||
# The penalty for splitting right before an if expression.
|
||||
split_penalty_before_if_expr=0
|
||||
|
||||
# The penalty of splitting the line around the '&', '|', and '^' operators.
|
||||
split_penalty_bitwise_operator=300
|
||||
|
||||
# The penalty for splitting a list comprehension or generator
|
||||
# expression.
|
||||
split_penalty_comprehension=80
|
||||
|
||||
# The penalty for characters over the column limit.
|
||||
split_penalty_excess_character=7000
|
||||
|
||||
# The penalty incurred by adding a line split to the logical line. The
|
||||
# more line splits added the higher the penalty.
|
||||
split_penalty_for_added_line_split=30
|
||||
|
||||
# The penalty of splitting a list of "import as" names. For example:
|
||||
#
|
||||
# from a_very_long_or_indented_module_name_yada_yad import (long_argument_1,
|
||||
# long_argument_2,
|
||||
# long_argument_3)
|
||||
#
|
||||
# would reformat to something like:
|
||||
#
|
||||
# from a_very_long_or_indented_module_name_yada_yad import (
|
||||
# long_argument_1, long_argument_2, long_argument_3)
|
||||
split_penalty_import_names=0
|
||||
|
||||
# The penalty of splitting the line around the 'and' and 'or' operators.
|
||||
split_penalty_logical_operator=300
|
||||
|
||||
# Use the Tab character for indentation.
|
||||
use_tabs=False
|
||||
|
293
bbp_ng/OP_ADDS_bme.py
Normal file
293
bbp_ng/OP_ADDS_bme.py
Normal file
@ -0,0 +1,293 @@
|
||||
import bpy, mathutils
|
||||
import typing
|
||||
from . import PROP_preferences
|
||||
from . import UTIL_functions, UTIL_bme
|
||||
|
||||
#region BME Adder
|
||||
|
||||
_g_EnumHelper_BmeStructType: UTIL_bme.EnumPropHelper = UTIL_bme.EnumPropHelper()
|
||||
|
||||
class BBP_PG_bme_adder_cfgs(bpy.types.PropertyGroup):
|
||||
prop_int: bpy.props.IntProperty(
|
||||
name = 'Single Int', description = 'Single Int',
|
||||
min = 0, max = 64,
|
||||
soft_min = 0, soft_max = 32,
|
||||
step = 1,
|
||||
default = 1,
|
||||
) # type: ignore
|
||||
prop_float: bpy.props.FloatProperty(
|
||||
name = 'Single Float', description = 'Single Float',
|
||||
min = 0.0, max = 1024.0,
|
||||
soft_min = 0.0, soft_max = 512.0,
|
||||
step = 50, # Step is in UI, in [1, 100] (WARNING: actual value is /100). So we choose 50, mean 0.5
|
||||
default = 5.0,
|
||||
) # type: ignore
|
||||
prop_bool: bpy.props.BoolProperty(
|
||||
name = 'Single Bool', description = 'Single Bool',
|
||||
default = True
|
||||
) # type: ignore
|
||||
|
||||
class BBP_OT_add_bme_struct(bpy.types.Operator):
|
||||
"""Add BME Struct"""
|
||||
bl_idname = "bbp.add_bme_struct"
|
||||
bl_label = "Add BME Struct"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
## There is a compromise due to the shitty Blender design.
|
||||
#
|
||||
# The passed `self` of Blender Property update function is not the instance of operator,
|
||||
# but a simple OperatorProperties.
|
||||
# It mean that I can not visit the full operator, only what I can do is visit existing
|
||||
# Blender properties.
|
||||
#
|
||||
# So these is the solution about generating cache list according to the change of bme struct type.
|
||||
# First, update function will only set a "outdated" flag for operator which is a pre-registered Blender property.
|
||||
# The "outdated" flags is not showen and not saved.
|
||||
# Then call a internal cache list update function at the begin of `invoke`, `execute` and `draw`.
|
||||
# In this internal cache list updator, check "outdated" flag first, if cache is outdated, update and reset flag.
|
||||
# Otherwise do nothing.
|
||||
#
|
||||
# Reference: https://docs.blender.org/api/current/bpy.props.html#update-example
|
||||
|
||||
## Compromise used "outdated" flag.
|
||||
outdated_flag: bpy.props.BoolProperty(
|
||||
name = "Outdated Type",
|
||||
description = "Internal flag.",
|
||||
options = {'HIDDEN', 'SKIP_SAVE'},
|
||||
default = False
|
||||
) # type: ignore
|
||||
|
||||
## A BME struct cfgs descriptor cache list
|
||||
# Not only the descriptor self, also the cfg associated index in bme_struct_cfgs
|
||||
bme_struct_cfg_index_cache: list[tuple[UTIL_bme.PrototypeShowcaseCfgDescriptor, int]]
|
||||
|
||||
def __internal_update_bme_struct_type(self) -> None:
|
||||
# if not outdated, skip
|
||||
if not self.outdated_flag: return
|
||||
|
||||
# get available cfg entires
|
||||
cfgs: typing.Iterator[UTIL_bme.PrototypeShowcaseCfgDescriptor]
|
||||
cfgs = _g_EnumHelper_BmeStructType.get_bme_showcase_cfgs(
|
||||
_g_EnumHelper_BmeStructType.get_selection(self.bme_struct_type)
|
||||
)
|
||||
|
||||
# analyse cfgs.
|
||||
# create counter first
|
||||
counter_int: int = 0
|
||||
counter_float: int = 0
|
||||
counter_bool: int = 0
|
||||
# create cache list
|
||||
self.bme_struct_cfg_index_cache.clear()
|
||||
# iterate cfgs and register them
|
||||
for cfg in cfgs:
|
||||
match(cfg.get_type()):
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
||||
self.bme_struct_cfg_index_cache.append((cfg, counter_int))
|
||||
counter_int += 1
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
||||
self.bme_struct_cfg_index_cache.append((cfg, counter_float))
|
||||
counter_float += 1
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
||||
self.bme_struct_cfg_index_cache.append((cfg, counter_bool))
|
||||
counter_bool += 1
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
||||
self.bme_struct_cfg_index_cache.append((cfg, counter_bool))
|
||||
counter_bool += 6 # face will occupy 6 bool.
|
||||
|
||||
|
||||
# init data collection
|
||||
# clear first
|
||||
self.bme_struct_cfgs.clear()
|
||||
# create enough entries specified by gotten cfgs
|
||||
for _ in range(max(counter_int, counter_float, counter_bool)):
|
||||
self.bme_struct_cfgs.add()
|
||||
|
||||
# assign default value
|
||||
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
|
||||
# show prop differently by cfg type
|
||||
match(cfg.get_type()):
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
||||
self.bme_struct_cfgs[cfg_index].prop_int = cfg.get_default()
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
||||
self.bme_struct_cfgs[cfg_index].prop_float = cfg.get_default()
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
||||
self.bme_struct_cfgs[cfg_index].prop_bool = cfg.get_default()
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
||||
# face is just 6 bool
|
||||
default_values: tuple[bool, ...] = cfg.get_default()
|
||||
for i in range(6):
|
||||
self.bme_struct_cfgs[cfg_index + i].prop_bool = default_values[i]
|
||||
|
||||
# reset outdated flag
|
||||
self.outdated_flag = False
|
||||
|
||||
# the updator for default side value
|
||||
def bme_struct_type_updated(self, context):
|
||||
# update outdated flag
|
||||
self.outdated_flag = True
|
||||
# blender required
|
||||
return None
|
||||
|
||||
bme_struct_type: bpy.props.EnumProperty(
|
||||
name = "Type",
|
||||
description = "BME struct type",
|
||||
items = _g_EnumHelper_BmeStructType.generate_items(),
|
||||
update = bme_struct_type_updated
|
||||
) # type: ignore
|
||||
|
||||
bme_struct_cfgs : bpy.props.CollectionProperty(
|
||||
name = "Cfgs",
|
||||
description = "Cfg collection.",
|
||||
type = BBP_PG_bme_adder_cfgs,
|
||||
) # type: ignore
|
||||
|
||||
## Extra transform for good "what you see is what you gotten".
|
||||
# Extra transform will be added after moving this object to cursor.
|
||||
extra_translation: bpy.props.FloatVectorProperty(
|
||||
name = "Extra Translation",
|
||||
description = "The extra translation applied to object after moving to cursor.",
|
||||
size = 3,
|
||||
subtype = 'TRANSLATION',
|
||||
step = 50, # same step as the float entry of BBP_PG_bme_adder_cfgs
|
||||
default = (0.0, 0.0, 0.0)
|
||||
) # type: ignore
|
||||
extra_rotation: bpy.props.FloatVectorProperty(
|
||||
name = "Extra Rotation",
|
||||
description = "The extra rotation applied to object after moving to cursor.",
|
||||
size = 3,
|
||||
subtype = 'EULER',
|
||||
step = 100, # We choosen 100, mean 1. Sync with property window.
|
||||
default = (0.0, 0.0, 0.0)
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||
|
||||
def invoke(self, context, event):
|
||||
# reset extra transform to identy
|
||||
self.extra_translation = (0.0, 0.0, 0.0)
|
||||
self.extra_rotation = (0.0, 0.0, 0.0)
|
||||
self.extra_scale = (1.0, 1.0, 1.0)
|
||||
# create internal list
|
||||
self.bme_struct_cfg_index_cache = []
|
||||
# trigger default bme struct type updator
|
||||
self.bme_struct_type_updated(context)
|
||||
# call internal updator
|
||||
self.__internal_update_bme_struct_type()
|
||||
# run execute() function
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
# call internal updator
|
||||
self.__internal_update_bme_struct_type()
|
||||
|
||||
# collect cfgs data
|
||||
cfgs: dict[str, typing.Any] = {}
|
||||
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
|
||||
match(cfg.get_type()):
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
||||
cfgs[cfg.get_field()] = self.bme_struct_cfgs[cfg_index].prop_int
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
||||
cfgs[cfg.get_field()] = self.bme_struct_cfgs[cfg_index].prop_float
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
||||
cfgs[cfg.get_field()] = self.bme_struct_cfgs[cfg_index].prop_bool
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
||||
# face is just 6 bool tuple
|
||||
cfgs[cfg.get_field()] = tuple(
|
||||
self.bme_struct_cfgs[cfg_index + i].prop_bool for i in range(6)
|
||||
)
|
||||
|
||||
# call general creator
|
||||
obj: bpy.types.Object = UTIL_bme.create_bme_struct_wrapper(
|
||||
_g_EnumHelper_BmeStructType.get_selection(self.bme_struct_type),
|
||||
cfgs
|
||||
)
|
||||
|
||||
# add into scene and move to cursor
|
||||
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
||||
# add extra transform
|
||||
obj.matrix_world = obj.matrix_world @ mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector(self.extra_translation),
|
||||
mathutils.Euler(self.extra_rotation, 'XYZ'),
|
||||
mathutils.Vector((1.0, 1.0, 1.0)) # no scale
|
||||
)
|
||||
# select created object
|
||||
UTIL_functions.select_certain_objects((obj, ))
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
# call internal updator
|
||||
self.__internal_update_bme_struct_type()
|
||||
|
||||
# start drawing
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
# show type
|
||||
layout.prop(self, 'bme_struct_type')
|
||||
|
||||
# visit cfgs cache list to show cfg
|
||||
layout.label(text = "Prototype Configurations:")
|
||||
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
|
||||
# create box for cfgs
|
||||
box_layout: bpy.types.UILayout = layout.box()
|
||||
|
||||
# draw title and description first
|
||||
box_layout.label(text = cfg.get_title())
|
||||
box_layout.label(text = cfg.get_desc())
|
||||
|
||||
# show prop differently by cfg type
|
||||
match(cfg.get_type()):
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
||||
box_layout.prop(self.bme_struct_cfgs[cfg_index], 'prop_int', text = '')
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
||||
box_layout.prop(self.bme_struct_cfgs[cfg_index], 'prop_float', text = '')
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
||||
box_layout.prop(self.bme_struct_cfgs[cfg_index], 'prop_bool', text = '')
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
||||
# face will show a special layout (grid view)
|
||||
grids = box_layout.grid_flow(
|
||||
row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
|
||||
grids.alignment = 'CENTER'
|
||||
grids.separator()
|
||||
grids.prop(self.bme_struct_cfgs[cfg_index + 0], 'prop_bool', text = 'Top') # top
|
||||
grids.prop(self.bme_struct_cfgs[cfg_index + 2], 'prop_bool', text = 'Front') # front
|
||||
grids.prop(self.bme_struct_cfgs[cfg_index + 4], 'prop_bool', text = 'Left') # left
|
||||
grids.label(text = '', icon = 'CUBE') # show a 3d cube as icon
|
||||
grids.prop(self.bme_struct_cfgs[cfg_index + 5], 'prop_bool', text = 'Right') # right
|
||||
grids.prop(self.bme_struct_cfgs[cfg_index + 3], 'prop_bool', text = 'Back') # back
|
||||
grids.prop(self.bme_struct_cfgs[cfg_index + 1], 'prop_bool', text = 'Bottom') # bottom
|
||||
grids.separator()
|
||||
|
||||
# show extra transform props
|
||||
# forcely order that each one are placed horizontally
|
||||
layout.label(text = "Extra Transform:")
|
||||
# translation
|
||||
layout.label(text = 'Translation')
|
||||
hbox_layout: bpy.types.UILayout = layout.row()
|
||||
hbox_layout.prop(self, 'extra_translation', text = '')
|
||||
# rotation
|
||||
layout.label(text = 'Rotation')
|
||||
hbox_layout = layout.row()
|
||||
hbox_layout.prop(self, 'extra_rotation', text = '')
|
||||
|
||||
@classmethod
|
||||
def draw_blc_menu(cls, layout: bpy.types.UILayout):
|
||||
for ident in _g_EnumHelper_BmeStructType.get_bme_identifiers():
|
||||
# draw operator
|
||||
cop = layout.operator(
|
||||
cls.bl_idname,
|
||||
text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident),
|
||||
icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident)
|
||||
)
|
||||
# and assign its init type value
|
||||
cop.bme_struct_type = _g_EnumHelper_BmeStructType.to_selection(ident)
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_PG_bme_adder_cfgs)
|
||||
bpy.utils.register_class(BBP_OT_add_bme_struct)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_add_bme_struct)
|
||||
bpy.utils.unregister_class(BBP_PG_bme_adder_cfgs)
|
608
bbp_ng/OP_ADDS_component.py
Normal file
608
bbp_ng/OP_ADDS_component.py
Normal file
@ -0,0 +1,608 @@
|
||||
import bpy, mathutils
|
||||
import math, typing
|
||||
from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convension
|
||||
from . import PROP_ballance_element, PROP_virtools_group, PROP_ballance_map_info
|
||||
|
||||
#region Param Help Classes
|
||||
|
||||
class ComponentSectorParam():
|
||||
component_sector: bpy.props.IntProperty(
|
||||
name = "Sector",
|
||||
description = "Define which sector the object will be grouped in",
|
||||
min = 1, max = 999,
|
||||
soft_min = 1, soft_max = 8,
|
||||
default = 1,
|
||||
) # type: ignore
|
||||
|
||||
def general_get_component_sector(self) -> int:
|
||||
return self.component_sector
|
||||
|
||||
def draw_component_sector_params(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(self, 'component_sector')
|
||||
|
||||
class ComponentCountParam():
|
||||
component_count: bpy.props.IntProperty(
|
||||
name = "Count",
|
||||
description = "The count of components you want to generate",
|
||||
min = 1, max = 64,
|
||||
soft_min = 1, soft_max = 32,
|
||||
default = 1,
|
||||
) # type: ignore
|
||||
|
||||
def general_get_component_count(self) -> int:
|
||||
return self.component_count
|
||||
|
||||
def draw_component_count_params(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(self, 'component_count')
|
||||
|
||||
#endregion
|
||||
|
||||
#region Help Classes & Functions
|
||||
|
||||
def _get_component_info(comp_type: PROP_ballance_element.BallanceElementType, comp_sector: int) -> UTIL_naming_convension.BallanceObjectInfo:
|
||||
match(comp_type):
|
||||
# process for 2 special unique components
|
||||
case PROP_ballance_element.BallanceElementType.PS_FourFlames:
|
||||
return UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.LEVEL_START)
|
||||
case PROP_ballance_element.BallanceElementType.PE_Balloon:
|
||||
return UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.LEVEL_END)
|
||||
# process naming convention required special components
|
||||
case PROP_ballance_element.BallanceElementType.PC_TwoFlames:
|
||||
return UTIL_naming_convension.BallanceObjectInfo.create_from_checkpoint(comp_sector)
|
||||
case PROP_ballance_element.BallanceElementType.PR_Resetpoint:
|
||||
return UTIL_naming_convension.BallanceObjectInfo.create_from_resetpoint(comp_sector)
|
||||
# process for other components
|
||||
case _:
|
||||
return UTIL_naming_convension.BallanceObjectInfo.create_from_component(
|
||||
PROP_ballance_element.get_ballance_element_name(comp_type),
|
||||
comp_sector
|
||||
)
|
||||
|
||||
def _set_component_by_info(obj: bpy.types.Object, info: UTIL_naming_convension.BallanceObjectInfo) -> None:
|
||||
# set component name and grouping it into virtools group at the same time
|
||||
# set name first
|
||||
if not UTIL_naming_convension.YYCToolchainConvention.set_to_object(obj, info, None):
|
||||
raise UTIL_functions.BBPException('impossible fail to set component name.')
|
||||
|
||||
# set vt group next
|
||||
if not UTIL_naming_convension.VirtoolsGroupConvention.set_to_object(obj, info, None):
|
||||
raise UTIL_functions.BBPException('impossible fail to set component virtools groups.')
|
||||
|
||||
def _check_component_existance(comp_type: PROP_ballance_element.BallanceElementType, comp_sector: int) -> str | None:
|
||||
"""
|
||||
Check the existance of 4 special components name, PS, PE, PC, PR
|
||||
These 4 components will have special name.
|
||||
|
||||
@return Return name if selected component is one of PS, PE, PC, PR and there already is a name conflict, otherwise None.
|
||||
"""
|
||||
# check component type requirements
|
||||
match(comp_type):
|
||||
case PROP_ballance_element.BallanceElementType.PS_FourFlames | PROP_ballance_element.BallanceElementType.PE_Balloon | PROP_ballance_element.BallanceElementType.PC_TwoFlames | PROP_ballance_element.BallanceElementType.PR_Resetpoint:
|
||||
pass # exit match and start check
|
||||
case _:
|
||||
return None # return, do not check
|
||||
|
||||
# get info
|
||||
comp_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector)
|
||||
|
||||
# get expected name
|
||||
expect_name: str | None = UTIL_naming_convension.YYCToolchainConvention.set_to_name(comp_info, None)
|
||||
if expect_name is None:
|
||||
raise UTIL_functions.BBPException('impossible fail to get component name.')
|
||||
|
||||
# check expected name
|
||||
if expect_name in bpy.data.objects: return expect_name
|
||||
else: return None
|
||||
|
||||
class _GeneralComponentCreator():
|
||||
"""
|
||||
The assist class for general component creation function.
|
||||
Because we need select all created component, thus we need collect all created object into a list.
|
||||
This is the reason why we create this class.
|
||||
"""
|
||||
|
||||
## The list storing all created component within this creation.
|
||||
__mObjList: list[bpy.types.Object]
|
||||
|
||||
def __init__(self):
|
||||
self.__mObjList = []
|
||||
|
||||
def create_component(self,
|
||||
comp_type: PROP_ballance_element.BallanceElementType,
|
||||
comp_sector: int,
|
||||
comp_count: int,
|
||||
comp_offset: typing.Callable[[int], mathutils.Matrix]
|
||||
) -> None:
|
||||
"""
|
||||
General component creation function.
|
||||
|
||||
@param comp_type[in] The component type created.
|
||||
@param comp_sector[in] The sector param which passed to other functions. For non-sector component, pass any number.
|
||||
@param comp_count[in] The count of created component. For single component creation, please pass 1.
|
||||
@param comp_offset[in] The function pointer which receive 1 argument indicating the index of object which we want to get its offset.
|
||||
You can pass `lambda _: mathutils.Matrix.Identity(4)` to get zero offset for every items.
|
||||
You can pass `lambda _: mathutils.Matrix( xxx )` to get same offset for every items.
|
||||
You can pass `lambda i: mathutils.Matrix( func(i) )` to get index based offset for each items.
|
||||
The offset is the offset to the origin point, not the previous object.
|
||||
@return The created component instance.
|
||||
"""
|
||||
# get element info first
|
||||
ele_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector)
|
||||
|
||||
# create blc element context
|
||||
with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator:
|
||||
# object creation counter
|
||||
for i in range(comp_count):
|
||||
# get mesh from element context, and create with empty name first. we assign name later.
|
||||
obj: bpy.types.Object = bpy.data.objects.new('', creator.get_element(comp_type))
|
||||
# assign virtools group, object name by we gotten element info.
|
||||
_set_component_by_info(obj, ele_info)
|
||||
# add into scene and move to cursor
|
||||
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
||||
# move with extra offset by calling offset getter
|
||||
obj.matrix_world = obj.matrix_world @ comp_offset(i)
|
||||
# put into created object list
|
||||
self.__mObjList.append(obj)
|
||||
|
||||
# enlarge scene sector field for non-PS (start point) PE (end point) component
|
||||
# read from scene and create var for enlarged sector count
|
||||
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||
enlarged_sector: int
|
||||
# check component type to get enlarged value
|
||||
match(ele_info.mBasicType):
|
||||
case UTIL_naming_convension.BallanceObjectType.COMPONENT:
|
||||
enlarged_sector = comp_sector
|
||||
case UTIL_naming_convension.BallanceObjectType.CHECKPOINT:
|
||||
# checkpoint 1 means that there is sector 2, so we plus 1 for it.
|
||||
enlarged_sector = comp_sector + 1
|
||||
case UTIL_naming_convension.BallanceObjectType.RESETPOINT:
|
||||
enlarged_sector = comp_sector
|
||||
case _:
|
||||
# this component is not a sector based component
|
||||
# so we do not change it (use original value)
|
||||
enlarged_sector = map_info.mSectorCount
|
||||
# enlarge it
|
||||
map_info.mSectorCount = max(map_info.mSectorCount, enlarged_sector)
|
||||
PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info)
|
||||
|
||||
def finish_component(self) -> None:
|
||||
"""
|
||||
Finish up component creation.
|
||||
Just deselect all objects and select all created components.
|
||||
"""
|
||||
UTIL_functions.select_certain_objects(tuple(self.__mObjList))
|
||||
|
||||
#endregion
|
||||
|
||||
#region Noemal Component Adder
|
||||
|
||||
# element enum prop helper
|
||||
|
||||
def _get_component_icon_by_name(elename: str):
|
||||
icon: int | None = UTIL_icons_manager.get_component_icon(elename)
|
||||
if icon is None: return UTIL_icons_manager.get_empty_icon()
|
||||
else: return icon
|
||||
_g_EnumHelper_Component: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
|
||||
PROP_ballance_element.BallanceElementType,
|
||||
lambda x: str(x.value),
|
||||
lambda x: PROP_ballance_element.BallanceElementType(int(x)),
|
||||
lambda x: x.name,
|
||||
lambda x: '',
|
||||
lambda x: _get_component_icon_by_name(PROP_ballance_element.get_ballance_element_name(x)),
|
||||
)
|
||||
|
||||
class BBP_OT_add_component(bpy.types.Operator, ComponentSectorParam):
|
||||
"""Add Component"""
|
||||
bl_idname = "bbp.add_component"
|
||||
bl_label = "Add Component"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
component_type: bpy.props.EnumProperty(
|
||||
name = "Type",
|
||||
description = "This component type",
|
||||
items = _g_EnumHelper_Component.generate_items(),
|
||||
) # type: ignore
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
# show type
|
||||
layout.prop(self, "component_type")
|
||||
|
||||
# only show sector for non-PE/PS component
|
||||
eletype: PROP_ballance_element.BallanceElementType = _g_EnumHelper_Component.get_selection(self.component_type)
|
||||
if eletype != PROP_ballance_element.BallanceElementType.PS_FourFlames and eletype != PROP_ballance_element.BallanceElementType.PE_Balloon:
|
||||
self.draw_component_sector_params(layout)
|
||||
|
||||
# check for some special components and show warning
|
||||
elename: str | None = _check_component_existance(_g_EnumHelper_Component.get_selection(self.component_type), self.general_get_component_sector())
|
||||
if elename is not None:
|
||||
layout.label(text = f'Warning: {elename} already exist.')
|
||||
|
||||
def execute(self, context):
|
||||
# call general creator
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
_g_EnumHelper_Component.get_selection(self.component_type),
|
||||
self.general_get_component_sector(),
|
||||
1, # only create one
|
||||
lambda _: mathutils.Matrix.Identity(4)
|
||||
)
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
def draw_blc_menu(layout: bpy.types.UILayout):
|
||||
for item in PROP_ballance_element.BallanceElementType:
|
||||
item_name: str = PROP_ballance_element.get_ballance_element_name(item)
|
||||
|
||||
cop = layout.operator(
|
||||
BBP_OT_add_component.bl_idname, text = item_name,
|
||||
icon_value = UTIL_icons_manager.get_component_icon(item_name)
|
||||
)
|
||||
cop.component_type = _g_EnumHelper_Component.to_selection(item)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nong Comp Adder
|
||||
|
||||
class BBP_OT_add_nong_extra_point(bpy.types.Operator, ComponentSectorParam, ComponentCountParam):
|
||||
"""Add Nong Extra Point"""
|
||||
bl_idname = "bbp.add_nong_extra_point"
|
||||
bl_label = "Nong Extra Point"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
self.draw_component_sector_params(layout)
|
||||
self.draw_component_count_params(layout)
|
||||
|
||||
def execute(self, context):
|
||||
# create objects and rotate it by a certain degree calculated by its index
|
||||
# calc percent first
|
||||
percent: float = 1.0 / self.general_get_component_count()
|
||||
# create elements
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
PROP_ballance_element.BallanceElementType.P_Extra_Point,
|
||||
self.general_get_component_sector(),
|
||||
self.general_get_component_count(),
|
||||
lambda i: mathutils.Matrix.Rotation(percent * i * math.pi * 2, 4, 'Z')
|
||||
)
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
def draw_blc_menu(layout: bpy.types.UILayout):
|
||||
layout.operator(
|
||||
BBP_OT_add_nong_extra_point.bl_idname,
|
||||
icon_value = UTIL_icons_manager.get_component_icon(
|
||||
PROP_ballance_element.get_ballance_element_name(PROP_ballance_element.BallanceElementType.P_Extra_Point)
|
||||
)
|
||||
)
|
||||
|
||||
class BBP_OT_add_nong_ventilator(bpy.types.Operator, ComponentSectorParam, ComponentCountParam):
|
||||
"""Add Nong Ventilator"""
|
||||
bl_idname = "bbp.add_nong_ventilator"
|
||||
bl_label = "Nong Ventilator"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
ventilator_count_source: bpy.props.EnumProperty(
|
||||
name = "Ventilator Count Source",
|
||||
items = [
|
||||
('DEFINED', "Predefined", "Pre-defined ventilator count."),
|
||||
('CUSTOM', "Custom", "User specified ventilator count."),
|
||||
],
|
||||
) # type: ignore
|
||||
|
||||
preset_vetilator_count: bpy.props.EnumProperty(
|
||||
name = "Preset Count",
|
||||
description = "Pick preset ventilator count.",
|
||||
items = [
|
||||
# (token, display name, descriptions, icon, index)
|
||||
('PAPER', 'Paper', 'The ventilator count (1) can push paper ball up.'),
|
||||
('WOOD', 'Wood', 'The ventilator count (6) can push wood ball up.'),
|
||||
('STONE', 'Stone', 'The ventilator count (32) can push stone ball up.'),
|
||||
],
|
||||
) # type: ignore
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
# draw sector settings
|
||||
self.draw_component_sector_params(layout)
|
||||
|
||||
# draw count settings by different source
|
||||
layout.label(text = 'Count')
|
||||
layout.prop(self, 'ventilator_count_source', expand = True)
|
||||
if (self.ventilator_count_source == 'CUSTOM'):
|
||||
self.draw_component_count_params(layout)
|
||||
else:
|
||||
layout.prop(self, 'preset_vetilator_count')
|
||||
|
||||
def execute(self, context):
|
||||
# get ventilator count
|
||||
count: int = 0
|
||||
if (self.ventilator_count_source == 'CUSTOM'):
|
||||
count = self.general_get_component_count()
|
||||
else:
|
||||
match(self.preset_vetilator_count):
|
||||
case 'PAPER': count = 1
|
||||
case 'WOOD': count = 6
|
||||
case 'STONE': count = 32
|
||||
case _: raise UTIL_functions.BBPException('invalid enumprop data')
|
||||
|
||||
# create elements without any move
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
PROP_ballance_element.BallanceElementType.P_Modul_18,
|
||||
self.general_get_component_sector(),
|
||||
count,
|
||||
lambda _: mathutils.Matrix.Identity(4)
|
||||
)
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
def draw_blc_menu(layout: bpy.types.UILayout):
|
||||
layout.operator(
|
||||
BBP_OT_add_nong_ventilator.bl_idname,
|
||||
icon_value = UTIL_icons_manager.get_component_icon(
|
||||
PROP_ballance_element.get_ballance_element_name(PROP_ballance_element.BallanceElementType.P_Modul_18)
|
||||
)
|
||||
)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Series Comp Adder
|
||||
|
||||
class BBP_OT_add_tilting_block_series(bpy.types.Operator, ComponentSectorParam, ComponentCountParam):
|
||||
"""Add Tilting Block Series"""
|
||||
bl_idname = "bbp.add_tilting_block_series"
|
||||
bl_label = "Tilting Block Series"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
component_span: bpy.props.FloatProperty(
|
||||
name = "Span",
|
||||
description = "The distance between each titling blocks",
|
||||
min = 0.0, max = 100.0,
|
||||
soft_min = 0.0, soft_max = 12.0,
|
||||
default = 6.0022,
|
||||
) # type: ignore
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
self.draw_component_sector_params(layout)
|
||||
self.draw_component_count_params(layout)
|
||||
layout.prop(self, 'component_span')
|
||||
|
||||
def execute(self, context):
|
||||
# create objects and move it by delta
|
||||
# get span first
|
||||
span: float = self.component_span
|
||||
# create elements
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
PROP_ballance_element.BallanceElementType.P_Modul_41,
|
||||
self.general_get_component_sector(),
|
||||
self.general_get_component_count(),
|
||||
lambda i: mathutils.Matrix.Translation(mathutils.Vector((span * i, 0.0, 0.0))) # move with extra delta in x axis
|
||||
)
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
def draw_blc_menu(layout: bpy.types.UILayout):
|
||||
layout.operator(
|
||||
BBP_OT_add_tilting_block_series.bl_idname,
|
||||
icon_value = UTIL_icons_manager.get_component_icon(
|
||||
PROP_ballance_element.get_ballance_element_name(PROP_ballance_element.BallanceElementType.P_Modul_41)
|
||||
)
|
||||
)
|
||||
|
||||
class BBP_OT_add_swing_series(bpy.types.Operator, ComponentSectorParam, ComponentCountParam):
|
||||
"""Add Swing Series"""
|
||||
bl_idname = "bbp.add_swing_series"
|
||||
bl_label = "Swing Series"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
component_span: bpy.props.FloatProperty(
|
||||
name = "Span",
|
||||
description = "The distance between each swing",
|
||||
min = 0.0, max = 100.0,
|
||||
soft_min = 0.0, soft_max = 30.0,
|
||||
default = 15.0,
|
||||
) # type: ignore
|
||||
|
||||
staggered_swing: bpy.props.BoolProperty(
|
||||
name = 'Staggered',
|
||||
description = 'Whether place Swing staggered. Staggered Swing accept any ball however Non-Staggered Swing only accept Wood and Paper ball.',
|
||||
default = True
|
||||
) # type: ignore
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
self.draw_component_sector_params(layout)
|
||||
self.draw_component_count_params(layout)
|
||||
layout.prop(self, 'component_span')
|
||||
layout.prop(self, 'staggered_swing')
|
||||
|
||||
def execute(self, context):
|
||||
# create objects and move it by delta
|
||||
# get span first
|
||||
span: float = self.component_span
|
||||
staggered: bool = self.staggered_swing
|
||||
# create elements
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
PROP_ballance_element.BallanceElementType.P_Modul_08,
|
||||
self.general_get_component_sector(),
|
||||
self.general_get_component_count(),
|
||||
lambda i: mathutils.Matrix.LocRotScale(
|
||||
# move with extra delta in x axis
|
||||
mathutils.Vector((span * i, 0.0, 0.0)),
|
||||
# and rotate 90 degree for even one if staggered placement.
|
||||
mathutils.Euler((0, 0, math.radians(180) if (staggered and (i % 2 == 0)) else 0)),
|
||||
None
|
||||
)
|
||||
)
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
def draw_blc_menu(layout: bpy.types.UILayout):
|
||||
layout.operator(
|
||||
BBP_OT_add_swing_series.bl_idname,
|
||||
icon_value = UTIL_icons_manager.get_component_icon(
|
||||
PROP_ballance_element.get_ballance_element_name(PROP_ballance_element.BallanceElementType.P_Modul_08)
|
||||
)
|
||||
)
|
||||
|
||||
class BBP_OT_add_ventilator_series(bpy.types.Operator, ComponentSectorParam, ComponentCountParam):
|
||||
"""Add Ventilator Series"""
|
||||
bl_idname = "bbp.add_ventilator_series"
|
||||
bl_label = "Ventilator Series"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
component_translation: bpy.props.FloatVectorProperty(
|
||||
name = "Delta Vector",
|
||||
description = "The translation between each ventilators. You can use this property to implement vertical or horizontal ventilator series. Set all factors to zero can get Nong ventilator.",
|
||||
size = 3, subtype = 'TRANSLATION',
|
||||
min = 0.0, max = 100.0,
|
||||
soft_min = 0.0, soft_max = 50.0,
|
||||
default = (0.0, 0.0, 15.0),
|
||||
) # type: ignore
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
self.draw_component_sector_params(layout)
|
||||
self.draw_component_count_params(layout)
|
||||
layout.prop(self, 'component_translation')
|
||||
|
||||
def execute(self, context):
|
||||
# create objects and move it by delta
|
||||
# get translation first
|
||||
translation: mathutils.Vector = mathutils.Vector(self.component_translation)
|
||||
# create elements
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
PROP_ballance_element.BallanceElementType.P_Modul_18,
|
||||
self.general_get_component_sector(),
|
||||
self.general_get_component_count(),
|
||||
lambda i: mathutils.Matrix.Translation(i * translation) # move with extra translation
|
||||
)
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
def draw_blc_menu(layout: bpy.types.UILayout):
|
||||
layout.operator(
|
||||
BBP_OT_add_ventilator_series.bl_idname,
|
||||
icon_value = UTIL_icons_manager.get_component_icon(
|
||||
PROP_ballance_element.get_ballance_element_name(PROP_ballance_element.BallanceElementType.P_Modul_18)
|
||||
)
|
||||
)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Comp Pair Adder
|
||||
|
||||
class BBP_OT_add_sector_component_pair(bpy.types.Operator, ComponentSectorParam):
|
||||
"""Add Sector Pair, both check point and reset point."""
|
||||
bl_idname = "bbp.add_sector_component_pair"
|
||||
bl_label = "Sector Pair"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
def __get_checkpoint(self) -> tuple[PROP_ballance_element.BallanceElementType, int]:
|
||||
if self.general_get_component_sector() == 1:
|
||||
return (PROP_ballance_element.BallanceElementType.PS_FourFlames, 1)
|
||||
else:
|
||||
# the sector of two flames should be `sector - 1` because first one was occupied by FourFlams
|
||||
return (PROP_ballance_element.BallanceElementType.PC_TwoFlames, self.general_get_component_sector() - 1)
|
||||
|
||||
def __get_resetpoint(self) -> tuple[PROP_ballance_element.BallanceElementType, int]:
|
||||
# resetpoint's sector is just sector it self.
|
||||
return (PROP_ballance_element.BallanceElementType.PR_Resetpoint, self.general_get_component_sector())
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
self.draw_component_sector_params(layout)
|
||||
|
||||
# check checkpoint and resetpoint name conflict and show warnings
|
||||
(checkp_ty, checkp_sector) = self.__get_checkpoint()
|
||||
elename: str | None = _check_component_existance(checkp_ty, checkp_sector)
|
||||
if elename is not None:
|
||||
layout.label(text = f'Warning: {elename} already exist.')
|
||||
|
||||
(resetp_ty, resetp_sector) = self.__get_resetpoint()
|
||||
elename = _check_component_existance(resetp_ty, resetp_sector)
|
||||
if elename is not None:
|
||||
layout.label(text = f'Warning: {elename} already exist.')
|
||||
|
||||
def execute(self, context):
|
||||
# create checkpoint and resetpoint individually in element context
|
||||
# get type and sector data first
|
||||
(checkp_ty, checkp_sector) = self.__get_checkpoint()
|
||||
(resetp_ty, resetp_sector) = self.__get_resetpoint()
|
||||
# calc resetpoint offset
|
||||
# resetpoint need a extra offset between checkpoint
|
||||
# but it is different in FourFlams and TwoFlams
|
||||
resetp_offset: float
|
||||
if checkp_ty == PROP_ballance_element.BallanceElementType.PS_FourFlames:
|
||||
resetp_offset = 3.25
|
||||
else:
|
||||
resetp_offset = 2.0
|
||||
|
||||
# add elements
|
||||
# create checkpoint
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
checkp_ty,
|
||||
checkp_sector,
|
||||
1, # only create one
|
||||
lambda _: mathutils.Matrix.Identity(4)
|
||||
)
|
||||
# create resetpoint
|
||||
creator.create_component(
|
||||
resetp_ty,
|
||||
resetp_sector,
|
||||
1, # only create one
|
||||
lambda _: mathutils.Matrix.Translation(mathutils.Vector((0.0, 0.0, resetp_offset))) # apply resetpoint offset
|
||||
)
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
def draw_blc_menu(layout: bpy.types.UILayout):
|
||||
layout.operator(
|
||||
BBP_OT_add_sector_component_pair.bl_idname,
|
||||
icon_value = UTIL_icons_manager.get_component_icon(
|
||||
PROP_ballance_element.get_ballance_element_name(PROP_ballance_element.BallanceElementType.PR_Resetpoint)
|
||||
)
|
||||
)
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_add_component)
|
||||
bpy.utils.register_class(BBP_OT_add_nong_extra_point)
|
||||
bpy.utils.register_class(BBP_OT_add_nong_ventilator)
|
||||
bpy.utils.register_class(BBP_OT_add_tilting_block_series)
|
||||
bpy.utils.register_class(BBP_OT_add_swing_series)
|
||||
bpy.utils.register_class(BBP_OT_add_ventilator_series)
|
||||
bpy.utils.register_class(BBP_OT_add_sector_component_pair)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_add_sector_component_pair)
|
||||
bpy.utils.unregister_class(BBP_OT_add_ventilator_series)
|
||||
bpy.utils.unregister_class(BBP_OT_add_swing_series)
|
||||
bpy.utils.unregister_class(BBP_OT_add_tilting_block_series)
|
||||
bpy.utils.unregister_class(BBP_OT_add_nong_ventilator)
|
||||
bpy.utils.unregister_class(BBP_OT_add_nong_extra_point)
|
||||
bpy.utils.unregister_class(BBP_OT_add_component)
|
772
bbp_ng/OP_ADDS_rail.py
Normal file
772
bbp_ng/OP_ADDS_rail.py
Normal file
@ -0,0 +1,772 @@
|
||||
import bpy, bmesh, mathutils, math
|
||||
import typing
|
||||
from . import UTIL_functions, UTIL_naming_convension
|
||||
|
||||
## Const Value Hint:
|
||||
# Default Rail Radius: 0.35 (in measure)
|
||||
# Default Rail Span: 3.75 (in convention)
|
||||
# Default Monorail Sink Depth in Rail & Monorail Transition: 0.6259 (calculated by ImbalancedDream)
|
||||
# Equation: Sink = sqrt( ((RailRadius + BallRadius) ^ 2) - ((RailSpan / 2) ^ 2) ) - BallRadius - RailRadius
|
||||
# BallRadius is the radius of player ball. It always is 2.
|
||||
# Ref: https://tieba.baidu.com/p/6557180791
|
||||
#
|
||||
# For Normal Side Rail (paper ball + wood ball can pass it):
|
||||
# Rail Span: 3.864
|
||||
# Angle (between rail panel and XY panel): 79.563 degree
|
||||
# For Special Side Rail (stone ball can pass it):
|
||||
# Rail Span: 3.864
|
||||
# Angle (between rail panel and XY panel): 57 degree
|
||||
# These infos are gotten from BallanceBug.
|
||||
#
|
||||
# For Side Spiral Rail, the distance between each layer is 3.6
|
||||
# Measured in Level 9 and Level 13.
|
||||
# For Spiral Rail, the distance between each layer is 5
|
||||
# Measured in Level 10.
|
||||
|
||||
c_DefaultRailRadius: float = 0.35
|
||||
c_DefaultRailSpan: float = 3.75
|
||||
c_SideRailSpan: float = 3.864
|
||||
c_NormalSideRailAngle: float = 79.563
|
||||
c_StoneSideRailAngle: float = 57
|
||||
c_SpiralRailScrew: float = 5
|
||||
c_SideSpiralRailScrew: float = 3.6
|
||||
|
||||
#region Operator Helpers
|
||||
|
||||
class SharedExtraTransform():
|
||||
"""
|
||||
This class is served for all rail creation which allow user
|
||||
provide extra transform after moving created rail to cursor.
|
||||
For "what you look is what you gotten" experience, this extra transform is essential.
|
||||
"""
|
||||
|
||||
extra_translation: bpy.props.FloatVectorProperty(
|
||||
name = "Extra Translation",
|
||||
description = "The extra translation applied to object after moving to cursor.",
|
||||
size = 3,
|
||||
subtype = 'TRANSLATION',
|
||||
step = 50, # same step as the float entry of BBP_PG_bme_adder_cfgs
|
||||
default = (0.0, 0.0, 0.0)
|
||||
) # type: ignore
|
||||
extra_rotation: bpy.props.FloatVectorProperty(
|
||||
name = "Extra Rotation",
|
||||
description = "The extra rotation applied to object after moving to cursor.",
|
||||
size = 3,
|
||||
subtype = 'EULER',
|
||||
step = 100, # We choosen 100, mean 1. Sync with property window.
|
||||
default = (0.0, 0.0, 0.0)
|
||||
) # type: ignore
|
||||
|
||||
def draw_extra_transform_input(self, layout: bpy.types.UILayout) -> None:
|
||||
# show extra transform props
|
||||
# forcely order that each one are placed horizontally
|
||||
layout.label(text = "Extra Transform:")
|
||||
# translation
|
||||
layout.label(text = 'Translation')
|
||||
row = layout.row()
|
||||
row.prop(self, 'extra_translation', text = '')
|
||||
# rotation
|
||||
layout.label(text = 'Rotation')
|
||||
row = layout.row()
|
||||
row.prop(self, 'extra_rotation', text = '')
|
||||
|
||||
def general_get_extra_transform(self) -> mathutils.Matrix:
|
||||
return mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector(self.extra_translation),
|
||||
mathutils.Euler(self.extra_rotation, 'XYZ'),
|
||||
mathutils.Vector((1.0, 1.0, 1.0)) # no scale
|
||||
)
|
||||
|
||||
class SharedRailSectionInputProperty():
|
||||
"""
|
||||
This class is served for user to pick the transition type of rail.
|
||||
And order rail radius and span accoridng to user picked rail type.
|
||||
"""
|
||||
|
||||
rail_type: bpy.props.EnumProperty(
|
||||
name = "Type",
|
||||
description = "Rail type",
|
||||
items = [
|
||||
('MONORAIL', "Monorail", ""),
|
||||
('RAIL', "Rail", ""),
|
||||
],
|
||||
default = 'RAIL',
|
||||
) # type: ignore
|
||||
|
||||
def draw_rail_section_input(self, layout: bpy.types.UILayout) -> None:
|
||||
row = layout.row()
|
||||
row.prop(self, 'rail_type', expand = True)
|
||||
|
||||
def general_get_is_monorail(self) -> bool:
|
||||
return self.rail_type == 'MONORAIL'
|
||||
|
||||
class SharedRailCapInputProperty():
|
||||
"""
|
||||
This class provide properties for cap switch.
|
||||
Support head cap and tail cap. Both straight and screw rail can use this.
|
||||
"""
|
||||
|
||||
rail_start_cap: bpy.props.BoolProperty(
|
||||
name = 'Start Cap',
|
||||
description = 'Whether this rail should have cap at start terminal.',
|
||||
default = False
|
||||
) # type: ignore
|
||||
|
||||
rail_end_cap: bpy.props.BoolProperty(
|
||||
name = 'End Cap',
|
||||
description = 'Whether this rail should have cap at end terminal.',
|
||||
default = False
|
||||
) # type: ignore
|
||||
|
||||
def draw_rail_cap_input(self, layout: bpy.types.UILayout) -> None:
|
||||
row = layout.row()
|
||||
row.prop(self, "rail_start_cap", toggle = 1)
|
||||
row.prop(self, "rail_end_cap", toggle = 1)
|
||||
|
||||
def general_get_rail_start_cap(self) -> bool:
|
||||
return self.rail_start_cap
|
||||
def general_get_rail_end_cap(self) -> bool:
|
||||
return self.rail_end_cap
|
||||
|
||||
class SharedStraightRailInputProperty():
|
||||
"""
|
||||
The properties for straight rail.
|
||||
"""
|
||||
|
||||
rail_length: bpy.props.FloatProperty(
|
||||
name = "Length",
|
||||
description = "The length of this rail.",
|
||||
default = 5.0,
|
||||
min = 0,
|
||||
step = 50, # same unit as BME Struct
|
||||
unit = 'LENGTH'
|
||||
) # type: ignore
|
||||
|
||||
def draw_straight_rail_input(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(self, "rail_length")
|
||||
|
||||
def general_get_rail_length(self) -> float:
|
||||
return self.rail_length
|
||||
|
||||
class SharedScrewRailInputProperty():
|
||||
"""
|
||||
The properties for straight rail.
|
||||
"""
|
||||
|
||||
rail_screw_steps: bpy.props.IntProperty(
|
||||
name = "Steps",
|
||||
description = "The segment count per iteration. More segment, more smooth but lower performance.",
|
||||
default = 28,
|
||||
min = 1,
|
||||
) # type: ignore
|
||||
|
||||
rail_screw_radius: bpy.props.FloatProperty(
|
||||
name = "Radius",
|
||||
description = "The screw radius. Minus radius will flip the built screw.",
|
||||
default = 5,
|
||||
unit = 'LENGTH'
|
||||
) # type: ignore
|
||||
|
||||
def draw_screw_rail_input(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(self, "rail_screw_radius")
|
||||
layout.prop(self, "rail_screw_steps")
|
||||
|
||||
def general_get_rail_screw_radius(self) -> float:
|
||||
return self.rail_screw_radius
|
||||
def general_get_rail_screw_steps(self) -> int:
|
||||
return self.rail_screw_steps
|
||||
|
||||
#endregion
|
||||
|
||||
#region Operators
|
||||
|
||||
class BBP_OT_add_rail_section(SharedRailSectionInputProperty, bpy.types.Operator):
|
||||
"""Add Rail Section"""
|
||||
bl_idname = "bbp.add_rail_section"
|
||||
bl_label = "Rail Section"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_rail_section(
|
||||
bm, self.general_get_is_monorail(),
|
||||
c_DefaultRailRadius, c_DefaultRailSpan
|
||||
),
|
||||
mathutils.Matrix.Identity(4)
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
self.draw_rail_section_input(layout)
|
||||
|
||||
class BBP_OT_add_transition_section(bpy.types.Operator):
|
||||
"""Add Transition Section"""
|
||||
bl_idname = "bbp.add_transition_section"
|
||||
bl_label = "Transition Section"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_transition_section(bm, c_DefaultRailRadius, c_DefaultRailSpan),
|
||||
mathutils.Matrix.Identity(4)
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'No Options Available')
|
||||
|
||||
class BBP_OT_add_straight_rail(SharedExtraTransform, SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
||||
"""Add Straight Rail"""
|
||||
bl_idname = "bbp.add_straight_rail"
|
||||
bl_label = "Straight Rail"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_straight_rail(
|
||||
bm,
|
||||
self.general_get_is_monorail(), c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_length(), 0,
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Straight Rail')
|
||||
self.draw_rail_section_input(layout)
|
||||
self.draw_straight_rail_input(layout)
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
class BBP_OT_add_transition_rail(SharedExtraTransform, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
||||
"""Add Transition Rail"""
|
||||
bl_idname = "bbp.add_transition_rail"
|
||||
bl_label = "Transition Rail"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_transition_rail(
|
||||
bm,
|
||||
c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_length(),
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Transition Rail')
|
||||
self.draw_straight_rail_input(layout)
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
class BBP_OT_add_side_rail(SharedExtraTransform, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
||||
"""Add Side Rail"""
|
||||
bl_idname = "bbp.add_side_rail"
|
||||
bl_label = "Side Rail"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
side_rail_type: bpy.props.EnumProperty(
|
||||
name = "Side Type",
|
||||
description = "Side rail type",
|
||||
items = [
|
||||
('NORMAL', "Normal", "The normal side rail."),
|
||||
('STONE', "Stone Specific", "The side rail which also allow stone ball passed."),
|
||||
],
|
||||
default = 'NORMAL',
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_straight_rail(
|
||||
bm,
|
||||
False, c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_length(),
|
||||
c_NormalSideRailAngle if self.side_rail_type == 'NORMAL' else c_StoneSideRailAngle,
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Side Rail')
|
||||
layout.prop(self, 'side_rail_type')
|
||||
self.draw_straight_rail_input(layout)
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
class BBP_OT_add_arc_rail(SharedExtraTransform, SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
||||
"""Add Arc Rail"""
|
||||
bl_idname = "bbp.add_arc_rail"
|
||||
bl_label = "Arc Rail"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
rail_screw_angle: bpy.props.FloatProperty(
|
||||
name = "Angle",
|
||||
description = "The angle of this arc rail rotated.",
|
||||
default = math.radians(90),
|
||||
min = 0, max = math.radians(360),
|
||||
subtype = 'ANGLE',
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_screw_rail(
|
||||
bm,
|
||||
self.general_get_is_monorail(), c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
||||
math.degrees(self.rail_screw_angle), 0, 1, # blender passed value is in radians
|
||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Arc Rail')
|
||||
self.draw_rail_section_input(layout)
|
||||
self.draw_screw_rail_input(layout)
|
||||
layout.prop(self, "rail_screw_angle")
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
class BBP_OT_add_spiral_rail(SharedExtraTransform, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
||||
"""Add Spiral Rail"""
|
||||
bl_idname = "bbp.add_spiral_rail"
|
||||
bl_label = "Spiral Rail"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
rail_screw_screw: bpy.props.FloatProperty(
|
||||
name = "Screw",
|
||||
description = "The increased height in each iteration. Minus height also is accepted.",
|
||||
default = c_SpiralRailScrew,
|
||||
unit = 'LENGTH'
|
||||
) # type: ignore
|
||||
|
||||
rail_screw_iterations: bpy.props.IntProperty(
|
||||
name = "Iterations",
|
||||
description = "Indicate how many layers of this spiral rail should be generated.",
|
||||
default = 1,
|
||||
min = 1,
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_screw_rail(
|
||||
bm,
|
||||
False, c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
||||
360, self.rail_screw_screw, self.rail_screw_iterations,
|
||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Spiral Rail')
|
||||
self.draw_screw_rail_input(layout)
|
||||
layout.prop(self, "rail_screw_screw")
|
||||
layout.prop(self, "rail_screw_iterations")
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
class BBP_OT_add_side_spiral_rail(SharedExtraTransform, SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
||||
"""Add Side Spiral Rail"""
|
||||
bl_idname = "bbp.add_side_spiral_rail"
|
||||
bl_label = "Side Spiral Rail"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
rail_screw_iterations: bpy.props.IntProperty(
|
||||
name = "Iterations",
|
||||
description = "Indicate how many layers of this spiral rail should be generated.",
|
||||
default = 2,
|
||||
# at least 2 ietrations can create 1 useful side spiral rail.
|
||||
# becuase side spiral rail is edge shared.
|
||||
min = 2,
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_screw_rail(
|
||||
bm,
|
||||
True, c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
||||
360, c_SideSpiralRailScrew, self.rail_screw_iterations,
|
||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Spiral Rail')
|
||||
self.draw_screw_rail_input(layout)
|
||||
layout.prop(self, "rail_screw_iterations")
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
#endregion
|
||||
|
||||
#region BMesh Operations Helper
|
||||
|
||||
def _bmesh_extrude(bm: bmesh.types.BMesh, start_edges: list[bmesh.types.BMEdge], direction: mathutils.Vector) -> list[bmesh.types.BMEdge]:
|
||||
# extrude
|
||||
ret: dict[str, typing.Any] = bmesh.ops.extrude_edge_only(
|
||||
bm,
|
||||
edges = start_edges,
|
||||
use_normal_flip = True, # NOTE: flip normal according to test result.
|
||||
use_select_history = False
|
||||
)
|
||||
|
||||
# get end edges
|
||||
ret_geom = ret['geom']
|
||||
del ret
|
||||
end_verts: list[bmesh.types.BMVert] = list(filter(lambda x: isinstance(x, bmesh.types.BMVert), ret_geom))
|
||||
end_edges: list[bmesh.types.BMEdge] = list(filter(lambda x: isinstance(x, bmesh.types.BMEdge) and x.is_boundary, ret_geom))
|
||||
# and move it
|
||||
bmesh.ops.translate(
|
||||
bm,
|
||||
vec = direction, space = mathutils.Matrix.Identity(4),
|
||||
verts = end_verts,
|
||||
use_shapekey = False
|
||||
)
|
||||
|
||||
# return value
|
||||
return end_edges
|
||||
|
||||
def _bmesh_screw(
|
||||
bm: bmesh.types.BMesh,
|
||||
start_verts: list[bmesh.types.BMVert], start_edges: list[bmesh.types.BMEdge],
|
||||
angle: float, steps: int, iterations: int,
|
||||
center: mathutils.Vector, screw_per_iteration: float) -> list[bmesh.types.BMEdge]:
|
||||
"""
|
||||
Hints: Angle is input as degree unit.
|
||||
"""
|
||||
# screw
|
||||
ret: dict[str, typing.Any] = bmesh.ops.spin(
|
||||
bm,
|
||||
geom = start_edges,
|
||||
cent = center,
|
||||
axis = mathutils.Vector((0, 0, 1)), # default to +Z
|
||||
dvec = mathutils.Vector((0, 0, screw_per_iteration / steps)), # conv to step delta
|
||||
angle = math.radians(angle) * iterations,
|
||||
space = mathutils.Matrix.Identity(4),
|
||||
steps = steps * iterations,
|
||||
use_merge = False,
|
||||
use_normal_flip = True, # NOTE: flip nml according to real test result
|
||||
use_duplicate = False
|
||||
)
|
||||
|
||||
# return last segment
|
||||
geom_last = ret['geom_last']
|
||||
del ret
|
||||
return list(filter(lambda x: isinstance(x, bmesh.types.BMEdge), geom_last))
|
||||
|
||||
def _bmesh_smooth_all_edges(bm: bmesh.types.BMesh) -> None:
|
||||
"""
|
||||
Resrt all edges to smooth. Call this before calling edge cap function.
|
||||
"""
|
||||
# reset all edges to smooth
|
||||
edge: bmesh.types.BMEdge
|
||||
for edge in bm.edges:
|
||||
edge.smooth = True
|
||||
|
||||
def _bmesh_cap(bm: bmesh.types.BMesh, edges: list[bmesh.types.BMEdge]) -> None:
|
||||
"""
|
||||
Cap given edges. And mark it as sharp edge.
|
||||
Please reset all edges to smooth one before calling this.
|
||||
"""
|
||||
# fill holes
|
||||
bmesh.ops.triangle_fill(
|
||||
bm,
|
||||
use_beauty = False, use_dissolve = False,
|
||||
edges = edges
|
||||
# no pass to normal.
|
||||
)
|
||||
|
||||
# and only set sharp for cap's edges
|
||||
for edge in edges:
|
||||
edge.smooth = False
|
||||
|
||||
#endregion
|
||||
|
||||
#region Real Rail Creators
|
||||
|
||||
def _rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], None], extra_transform: mathutils.Matrix) -> bpy.types.Object:
|
||||
# create mesh first
|
||||
bm: bmesh.types.BMesh = bmesh.new()
|
||||
|
||||
# call cret fct
|
||||
fct_poly_cret(bm)
|
||||
|
||||
# finish up
|
||||
mesh: bpy.types.Mesh = bpy.data.meshes.new('Rail')
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
|
||||
# setup smooth for mesh
|
||||
mesh.shade_smooth()
|
||||
|
||||
# create object and assoc with it
|
||||
# create info first
|
||||
rail_info: UTIL_naming_convension.BallanceObjectInfo = UTIL_naming_convension.BallanceObjectInfo.create_from_others(
|
||||
UTIL_naming_convension.BallanceObjectType.RAIL
|
||||
)
|
||||
# then get object name
|
||||
rail_name: str | None = UTIL_naming_convension.YYCToolchainConvention.set_to_name(rail_info, None)
|
||||
if rail_name is None: raise UTIL_functions.BBPException('impossible null name')
|
||||
# create object by name
|
||||
obj: bpy.types.Object = bpy.data.objects.new(rail_name, mesh)
|
||||
# assign virtools groups
|
||||
UTIL_naming_convension.VirtoolsGroupConvention.set_to_object(obj, rail_info, None)
|
||||
|
||||
# move to cursor
|
||||
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
||||
# add extra transform
|
||||
obj.matrix_world = obj.matrix_world @ extra_transform
|
||||
# select created object
|
||||
UTIL_functions.select_certain_objects((obj, ))
|
||||
|
||||
# return rail
|
||||
return obj
|
||||
|
||||
def _create_rail_section(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
matrix: mathutils.Matrix = mathutils.Matrix.Identity(4)) -> None:
|
||||
"""
|
||||
Add a rail section.
|
||||
|
||||
If created is monorail, the original point locate at the center of section.
|
||||
Otherwise, the original point locate at the center point of the line connecting between left rail section and right rail section.
|
||||
The section will be placed in XZ panel.
|
||||
|
||||
If ordered is monorail, `rail_span` param will be ignored.
|
||||
"""
|
||||
if is_monorail:
|
||||
# create monorail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
None,
|
||||
mathutils.Euler((math.radians(90), math.radians(22.5), 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
else:
|
||||
# create rail
|
||||
# create left rail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector((-rail_span / 2, 0, 0)),
|
||||
mathutils.Euler((math.radians(90), 0, 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
# create right rail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector((rail_span / 2, 0, 0)),
|
||||
mathutils.Euler((math.radians(90), 0, 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
|
||||
def _create_transition_section(
|
||||
bm: bmesh.types.BMesh,
|
||||
rail_radius: float, rail_span: float) -> None:
|
||||
"""
|
||||
Create the transition section between rail and monorail.
|
||||
"""
|
||||
# create rail section
|
||||
_create_rail_section(bm, False, rail_radius, rail_span)
|
||||
|
||||
# create monorail
|
||||
# calc sink first
|
||||
monorail_sink: float
|
||||
try:
|
||||
monorail_sink = math.sqrt((rail_radius + 2) ** 2 - (rail_span / 2) ** 2) - 2 - rail_radius
|
||||
except:
|
||||
monorail_sink = -2 # if sqrt(minus number) happended, it mean no triangle relation. the depth should always be -2.
|
||||
# create monorail with calculated sink
|
||||
_create_rail_section(
|
||||
bm, True, rail_radius, rail_span,
|
||||
mathutils.Matrix.Translation((0, 0, monorail_sink))
|
||||
)
|
||||
|
||||
def _create_straight_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
rail_length: float, rail_angle: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool) -> None:
|
||||
"""
|
||||
Add a straight rail.
|
||||
|
||||
The original point is same as `_add_rail_section()`.
|
||||
The start terminal of this straight will be placed in XZ panel.
|
||||
The expand direction is +Y.
|
||||
|
||||
If ordered is monorail, `rail_span` param will be ignored.
|
||||
|
||||
The rail angle is in degree unit and indicate how any angle this rail should rotated by its axis.
|
||||
It usually used to create side rail.
|
||||
"""
|
||||
# create section first
|
||||
_create_rail_section(
|
||||
bm, is_monorail, rail_radius, rail_span,
|
||||
mathutils.Matrix.LocRotScale(
|
||||
None,
|
||||
mathutils.Euler((0, math.radians(rail_angle), 0), 'XYZ'),
|
||||
None
|
||||
)
|
||||
)
|
||||
|
||||
# get start edges
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
# extrude and get end edges
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_extrude(
|
||||
bm, start_edges, mathutils.Vector((0, rail_length, 0))
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
def _create_transition_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
rail_radius: float, rail_span: float,
|
||||
rail_length: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool) -> None:
|
||||
"""
|
||||
Add a transition rail.
|
||||
|
||||
The original point is same as `_add_transition_section()`.
|
||||
The start terminal of this straight will be placed in XZ panel.
|
||||
The expand direction is +Y.
|
||||
"""
|
||||
# create section first
|
||||
_create_transition_section(bm, rail_radius, rail_span)
|
||||
|
||||
# get start edges
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
# extrude and get end edges
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_extrude(
|
||||
bm, start_edges, mathutils.Vector((0, rail_length, 0))
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
def _create_screw_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool,
|
||||
rail_screw_angle: float, rail_screw_screw: float, rail_screw_iterations: int,
|
||||
rail_screw_steps: int, rail_screw_radius: float) -> None:
|
||||
"""
|
||||
Add a screw rail.
|
||||
|
||||
The original point is same as `_add_rail_section()`.
|
||||
The start terminal of this straight will be placed in XZ panel.
|
||||
The expand direction is +Y.
|
||||
|
||||
If ordered is monorail, `rail_span` param will be ignored.
|
||||
|
||||
Angle is input as degree unit.
|
||||
"""
|
||||
# create section first
|
||||
_create_rail_section(bm, is_monorail, rail_radius, rail_span)
|
||||
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_screw(
|
||||
bm,
|
||||
bm.verts[:], start_edges,
|
||||
rail_screw_angle,
|
||||
rail_screw_steps, rail_screw_iterations,
|
||||
mathutils.Vector((rail_screw_radius, 0, 0)),
|
||||
rail_screw_screw
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_add_rail_section)
|
||||
bpy.utils.register_class(BBP_OT_add_transition_section)
|
||||
|
||||
bpy.utils.register_class(BBP_OT_add_straight_rail)
|
||||
bpy.utils.register_class(BBP_OT_add_transition_rail)
|
||||
bpy.utils.register_class(BBP_OT_add_side_rail)
|
||||
|
||||
bpy.utils.register_class(BBP_OT_add_arc_rail)
|
||||
bpy.utils.register_class(BBP_OT_add_spiral_rail)
|
||||
bpy.utils.register_class(BBP_OT_add_side_spiral_rail)
|
||||
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_add_side_spiral_rail)
|
||||
bpy.utils.unregister_class(BBP_OT_add_spiral_rail)
|
||||
bpy.utils.unregister_class(BBP_OT_add_arc_rail)
|
||||
|
||||
bpy.utils.unregister_class(BBP_OT_add_side_rail)
|
||||
bpy.utils.unregister_class(BBP_OT_add_transition_rail)
|
||||
bpy.utils.unregister_class(BBP_OT_add_straight_rail)
|
||||
|
||||
bpy.utils.unregister_class(BBP_OT_add_transition_section)
|
||||
bpy.utils.unregister_class(BBP_OT_add_rail_section)
|
32
bbp_ng/OP_EXPORT_bmfile.py
Normal file
32
bbp_ng/OP_EXPORT_bmfile.py
Normal file
@ -0,0 +1,32 @@
|
||||
import bpy
|
||||
from . import PROP_preferences, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_icons_manager, UTIL_ioport_shared
|
||||
|
||||
class BBP_OT_export_bmfile(bpy.types.Operator, UTIL_file_browser.ExportBmxFile, UTIL_ioport_shared.ExportParams):
|
||||
"""Save a Ballance Map File (BM File Spec 1.4)"""
|
||||
bl_idname = "bbp.export_bmfile"
|
||||
bl_label = "Export BM (Ballance Map) File"
|
||||
bl_options = {'PRESET'}
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||
|
||||
def execute(self, context):
|
||||
UTIL_functions.message_box(
|
||||
('This function not supported yet.', ),
|
||||
'No Implement',
|
||||
UTIL_icons_manager.BlenderPresetIcons.Error.value
|
||||
)
|
||||
self.report({'INFO'}, "BM File Exporting Finished.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Export Target')
|
||||
self.draw_export_params(layout.box())
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_export_bmfile)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_export_bmfile)
|
512
bbp_ng/OP_EXPORT_virtools.py
Normal file
512
bbp_ng/OP_EXPORT_virtools.py
Normal file
@ -0,0 +1,512 @@
|
||||
import bpy
|
||||
from bpy_extras.wm_utils.progress_report import ProgressReport
|
||||
import tempfile, os, typing
|
||||
from . import PROP_preferences, UTIL_ioport_shared
|
||||
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_icons_manager, UTIL_naming_convension
|
||||
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_ballance_map_info
|
||||
from .PyBMap import bmap_wrapper as bmap
|
||||
|
||||
# define global tex save opt blender enum prop helper
|
||||
_g_EnumHelper_CK_TEXTURE_SAVEOPTIONS: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS)
|
||||
|
||||
class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtoolsFile, UTIL_ioport_shared.ExportParams, UTIL_ioport_shared.VirtoolsParams):
|
||||
"""Export Virtools File"""
|
||||
bl_idname = "bbp.export_virtools"
|
||||
bl_label = "Export Virtools File"
|
||||
bl_options = {'PRESET'}
|
||||
|
||||
texture_save_opt: bpy.props.EnumProperty(
|
||||
name = "Global Texture Save Options",
|
||||
description = "Decide how texture saved if texture is specified as Use Global as its Save Options.",
|
||||
items = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.generate_items(),
|
||||
default = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.to_selection(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL)
|
||||
) # type: ignore
|
||||
|
||||
use_compress: bpy.props.BoolProperty(
|
||||
name="Use Compress",
|
||||
description = "Whether use ZLib to compress result when saving composition.",
|
||||
default = True,
|
||||
) # type: ignore
|
||||
|
||||
compress_level: bpy.props.IntProperty(
|
||||
name = "Compress Level",
|
||||
description = "The ZLib compress level used by Virtools Engine when saving composition.",
|
||||
min = 1, max = 9,
|
||||
default = 5,
|
||||
) # type: ignore
|
||||
|
||||
successive_sector: bpy.props.BoolProperty(
|
||||
name="Successive Sector",
|
||||
description = "Whether order exporter to use document specified sector count to make sure sector is successive.",
|
||||
default = True,
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return (
|
||||
PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||
and bmap.is_bmap_available())
|
||||
|
||||
def execute(self, context):
|
||||
# check selecting first
|
||||
objls: tuple[bpy.types.Object] | None = self.general_get_export_objects()
|
||||
if objls is None:
|
||||
UTIL_functions.message_box(
|
||||
('No selected target!', ),
|
||||
'Lost Parameters',
|
||||
UTIL_icons_manager.BlenderPresetIcons.Error.value
|
||||
)
|
||||
return {'CANCELLED'}
|
||||
|
||||
# start exporting
|
||||
with UTIL_ioport_shared.ExportEditModeBackup() as editmode_guard:
|
||||
_export_virtools(
|
||||
self.general_get_filename(),
|
||||
self.general_get_vt_encodings(),
|
||||
_g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.get_selection(self.texture_save_opt),
|
||||
self.use_compress,
|
||||
self.compress_level,
|
||||
self.successive_sector,
|
||||
objls
|
||||
)
|
||||
|
||||
self.report({'INFO'}, "Virtools File Exporting Finished.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Export Target')
|
||||
self.draw_export_params(layout.box())
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = 'Virtools Params')
|
||||
box = layout.box()
|
||||
self.draw_virtools_params(box)
|
||||
|
||||
box.separator()
|
||||
box.label(text = 'Global Texture Save Option')
|
||||
box.prop(self, 'texture_save_opt', text = '')
|
||||
|
||||
box.separator()
|
||||
box.prop(self, 'use_compress')
|
||||
if self.use_compress:
|
||||
box.prop(self, 'compress_level')
|
||||
|
||||
# show sector info to notice user
|
||||
layout.separator()
|
||||
layout.label(text = 'Ballance Params')
|
||||
box = layout.box()
|
||||
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||
box.prop(self, 'successive_sector')
|
||||
box.label(text = f'Map Sectors: {map_info.mSectorCount}')
|
||||
|
||||
_TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject]
|
||||
_TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh]
|
||||
_TMaterialPair = tuple[bpy.types.Material, bmap.BMMaterial]
|
||||
_TTexturePair = tuple[bpy.types.Image, bmap.BMTexture]
|
||||
|
||||
def _export_virtools(
|
||||
file_name_: str,
|
||||
encodings_: tuple[str],
|
||||
texture_save_opt_: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS,
|
||||
use_compress_: bool,
|
||||
compress_level_: int,
|
||||
successive_sector_: bool,
|
||||
export_objects: tuple[bpy.types.Object, ...]
|
||||
) -> None:
|
||||
|
||||
# create temp folder
|
||||
with tempfile.TemporaryDirectory() as vt_temp_folder:
|
||||
print(f'Virtools Engine Temp: {vt_temp_folder}')
|
||||
|
||||
# create virtools reader context
|
||||
with bmap.BMFileWriter(
|
||||
vt_temp_folder,
|
||||
PROP_preferences.get_raw_preferences().mBallanceTextureFolder,
|
||||
encodings_) as writer:
|
||||
|
||||
# prepare progress reporter
|
||||
with ProgressReport(wm = bpy.context.window_manager) as progress:
|
||||
# prepare 3dobject
|
||||
obj3d_crets: tuple[_TObj3dPair, ...] = _prepare_virtools_3dobjects(
|
||||
writer, progress, export_objects)
|
||||
# export group and 3dobject by prepared 3dobject
|
||||
_export_virtools_groups(writer, progress, successive_sector_, obj3d_crets)
|
||||
mesh_crets: tuple[_TMeshPair, ...] = _export_virtools_3dobjects(
|
||||
writer, progress, obj3d_crets)
|
||||
# export mesh
|
||||
material_crets: tuple[_TMaterialPair, ...] = _export_virtools_meshes(
|
||||
writer, progress, mesh_crets)
|
||||
# export material
|
||||
texture_crets: tuple[_TTexturePair, ...] = _export_virtools_materials(
|
||||
writer, progress, material_crets)
|
||||
# export texture
|
||||
_export_virtools_textures(writer, progress, vt_temp_folder, texture_crets)
|
||||
|
||||
# save document
|
||||
_save_virtools_document(
|
||||
writer, progress, file_name_, texture_save_opt_, use_compress_, compress_level_)
|
||||
|
||||
def _prepare_virtools_3dobjects(
|
||||
writer: bmap.BMFileWriter,
|
||||
progress: ProgressReport,
|
||||
export_objects: tuple[bpy.types.Object]
|
||||
) -> tuple[_TObj3dPair, ...]:
|
||||
# this function only create equvalent entries in virtools engine and do not export anything
|
||||
# because _export_virtools_3dobjects() and _export_virtools_groups() are need use the return value of this function
|
||||
|
||||
# create 3dobject hashset and result
|
||||
obj3d_crets: list[_TObj3dPair] = []
|
||||
obj3d_cret_set: set[bpy.types.Object] = set()
|
||||
# start saving
|
||||
progress.enter_substeps(len(export_objects), "Creating 3dObjects")
|
||||
|
||||
for obj3d in export_objects:
|
||||
if obj3d not in obj3d_cret_set:
|
||||
# add into set
|
||||
obj3d_cret_set.add(obj3d)
|
||||
# create virtools instance
|
||||
vtobj3d: bmap.BM3dObject = writer.create_3dobject()
|
||||
# add into result list
|
||||
obj3d_crets.append((obj3d, vtobj3d))
|
||||
|
||||
# step progress no matter whether create new one
|
||||
progress.step()
|
||||
|
||||
# leave progress and return
|
||||
progress.leave_substeps()
|
||||
return tuple(obj3d_crets)
|
||||
|
||||
def _export_virtools_groups(
|
||||
writer: bmap.BMFileWriter,
|
||||
progress: ProgressReport,
|
||||
successive_sector: bool,
|
||||
obj3d_crets: tuple[_TObj3dPair, ...]
|
||||
) -> None:
|
||||
# create virtools group
|
||||
group_cret_map: dict[str, bmap.BMGroup] = {}
|
||||
# start saving
|
||||
progress.enter_substeps(len(obj3d_crets), "Saving Groups")
|
||||
|
||||
# create sector group first if user ordered
|
||||
# This step is designed for ensure that the created sector group is successive.
|
||||
#
|
||||
# Due to the design of Ballance, Ballance rely on checking the existance of Sector_XX to get how many sectors this map have.
|
||||
# Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash
|
||||
# or be ended at a accident sector.
|
||||
#
|
||||
# So we create all needed sector group in here to make sure exported virtools file can be read by Ballancde correctly.
|
||||
if successive_sector:
|
||||
map_info: PROP_ballance_map_info.RawBallanceMapInfo
|
||||
map_info = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||
for i in range(map_info.mSectorCount):
|
||||
gp_name: str = UTIL_naming_convension.build_name_from_sector_index(i + 1)
|
||||
vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None)
|
||||
if vtgroup is None:
|
||||
vtgroup = writer.create_group()
|
||||
vtgroup.set_name(gp_name)
|
||||
group_cret_map[gp_name] = vtgroup
|
||||
|
||||
for obj3d, vtobj3d in obj3d_crets:
|
||||
# open group visitor
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj3d) as gp_visitor:
|
||||
for gp_name in gp_visitor.iterate_groups():
|
||||
# get group or create new group from guard
|
||||
vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None)
|
||||
if vtgroup is None:
|
||||
vtgroup = writer.create_group()
|
||||
vtgroup.set_name(gp_name)
|
||||
group_cret_map[gp_name] = vtgroup
|
||||
|
||||
# group this object
|
||||
vtgroup.add_object(vtobj3d)
|
||||
|
||||
# leave group visitor and step
|
||||
progress.step()
|
||||
|
||||
# leave progress and return
|
||||
progress.leave_substeps()
|
||||
|
||||
def _export_virtools_3dobjects(
|
||||
writer: bmap.BMFileWriter,
|
||||
progress: ProgressReport,
|
||||
obj3d_crets: tuple[_TObj3dPair, ...]
|
||||
) -> tuple[_TMeshPair, ...]:
|
||||
# create virtools mesh
|
||||
mesh_crets: list[_TMeshPair] = []
|
||||
mesh_cret_map: dict[bpy.types.Mesh, bmap.BMMesh] = {}
|
||||
# start saving
|
||||
progress.enter_substeps(len(obj3d_crets), "Saving 3dObjects")
|
||||
|
||||
for obj3d, vtobj3d in obj3d_crets:
|
||||
# set name
|
||||
vtobj3d.set_name(obj3d.name)
|
||||
|
||||
# check mesh
|
||||
mesh: bpy.types.Mesh | None = obj3d.data
|
||||
if mesh is not None:
|
||||
# get existing vt mesh or create new one
|
||||
vtmesh: bmap.BMMesh | None = mesh_cret_map.get(mesh, None)
|
||||
if vtmesh is None:
|
||||
vtmesh = writer.create_mesh()
|
||||
mesh_crets.append((obj3d, mesh, vtmesh))
|
||||
mesh_cret_map[mesh] = vtmesh
|
||||
|
||||
# assign mesh
|
||||
vtobj3d.set_current_mesh(vtmesh)
|
||||
else:
|
||||
vtobj3d.set_current_mesh(None)
|
||||
|
||||
# set world matrix
|
||||
vtmat: UTIL_virtools_types.VxMatrix = UTIL_virtools_types.VxMatrix()
|
||||
UTIL_virtools_types.vxmatrix_from_blender(vtmat, obj3d.matrix_world)
|
||||
UTIL_virtools_types.vxmatrix_conv_co(vtmat)
|
||||
vtobj3d.set_world_matrix(vtmat)
|
||||
|
||||
# set visibility
|
||||
vtobj3d.set_visibility(not obj3d.hide_get())
|
||||
|
||||
# step
|
||||
progress.step()
|
||||
|
||||
# leave progress and return
|
||||
progress.leave_substeps()
|
||||
return tuple(mesh_crets)
|
||||
|
||||
def _export_virtools_meshes(
|
||||
writer: bmap.BMFileWriter,
|
||||
progress: ProgressReport,
|
||||
mesh_crets: tuple[_TMeshPair, ...]
|
||||
) -> tuple[_TMaterialPair, ...]:
|
||||
# create virtools mesh
|
||||
material_crets: list[_TMaterialPair] = []
|
||||
material_cret_map: dict[bpy.types.Material, bmap.BMMaterial] = {}
|
||||
# start saving
|
||||
progress.enter_substeps(len(mesh_crets), "Saving Meshes")
|
||||
|
||||
# iterate meshes
|
||||
for obj3d, mesh, vtmesh in mesh_crets:
|
||||
# we need use temporary mesh function to visit triangulated meshes
|
||||
# so we ignore mesh factor and use obj3d to create temp mesh to get data
|
||||
# open temp mesh helper
|
||||
with UTIL_blender_mesh.TemporaryMesh(obj3d) as tempmesh:
|
||||
# sync mesh name, lit mode
|
||||
vtmesh.set_name(mesh.name)
|
||||
mesh_settings: PROP_virtools_mesh.RawVirtoolsMesh = PROP_virtools_mesh.get_raw_virtools_mesh(mesh)
|
||||
vtmesh.set_lit_mode(mesh_settings.mLitMode)
|
||||
|
||||
# sync mesh main data
|
||||
# open mesh visitor
|
||||
with UTIL_blender_mesh.MeshReader(tempmesh.get_temp_mesh()) as mesh_visitor:
|
||||
# construct data provider
|
||||
def pos_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||
for v in mesh_visitor.get_vertex_position():
|
||||
UTIL_virtools_types.vxvector3_conv_co(v)
|
||||
yield v
|
||||
def nml_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||
for v in mesh_visitor.get_vertex_normal():
|
||||
UTIL_virtools_types.vxvector3_conv_co(v)
|
||||
yield v
|
||||
def uv_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector2]:
|
||||
for v in mesh_visitor.get_vertex_uv():
|
||||
UTIL_virtools_types.vxvector2_conv_co(v)
|
||||
yield v
|
||||
|
||||
# construct mtl slot
|
||||
def mtl_iterator() -> typing.Iterator[bmap.BMMaterial | None]:
|
||||
for mtl in mesh_visitor.get_material_slot():
|
||||
if mtl is None: yield None
|
||||
else:
|
||||
# get existing one or create new one
|
||||
vtmaterial: bmap.BMMaterial | None = material_cret_map.get(mtl, None)
|
||||
if vtmaterial is None:
|
||||
vtmaterial = writer.create_material()
|
||||
material_crets.append((mtl, vtmaterial))
|
||||
material_cret_map[mtl] = vtmaterial
|
||||
# yield data
|
||||
yield vtmaterial
|
||||
|
||||
def face_idx_iterator(idx_type: int) -> typing.Iterator[UTIL_virtools_types.CKFaceIndices]:
|
||||
data: UTIL_virtools_types.CKFaceIndices = UTIL_virtools_types.CKFaceIndices()
|
||||
for fidx in mesh_visitor.get_face():
|
||||
# swap indices
|
||||
fidx.conv_co()
|
||||
# set data by specific index
|
||||
match(idx_type):
|
||||
case 0: data.i1, data.i2, data.i3 = fidx.mIndices[0].mPosIdx, fidx.mIndices[1].mPosIdx, fidx.mIndices[2].mPosIdx
|
||||
case 1: data.i1, data.i2, data.i3 = fidx.mIndices[0].mNmlIdx, fidx.mIndices[1].mNmlIdx, fidx.mIndices[2].mNmlIdx
|
||||
case 2: data.i1, data.i2, data.i3 = fidx.mIndices[0].mUvIdx, fidx.mIndices[1].mUvIdx, fidx.mIndices[2].mUvIdx
|
||||
case _: raise UTIL_functions.BBPException('invalid index type.')
|
||||
# yield data
|
||||
yield data
|
||||
def face_mtl_iterator() -> typing.Iterator[int]:
|
||||
for fidx in mesh_visitor.get_face():
|
||||
yield fidx.mMtlIdx
|
||||
|
||||
# create virtools mesh transition
|
||||
# and write into mesh
|
||||
with bmap.BMMeshTrans() as mesh_trans:
|
||||
# prepare vertices
|
||||
mesh_trans.prepare_vertex(
|
||||
mesh_visitor.get_vertex_position_count(),
|
||||
pos_iterator()
|
||||
)
|
||||
mesh_trans.prepare_normal(
|
||||
mesh_visitor.get_vertex_normal_count(),
|
||||
nml_iterator()
|
||||
)
|
||||
mesh_trans.prepare_uv(
|
||||
mesh_visitor.get_vertex_uv_count(),
|
||||
uv_iterator()
|
||||
)
|
||||
# prepare mtl slots
|
||||
mesh_trans.prepare_mtl_slot(
|
||||
mesh_visitor.get_material_slot_count(),
|
||||
mtl_iterator()
|
||||
)
|
||||
# prepare face
|
||||
mesh_trans.prepare_face(
|
||||
mesh_visitor.get_face_count(),
|
||||
face_idx_iterator(0),
|
||||
face_idx_iterator(1),
|
||||
face_idx_iterator(2),
|
||||
face_mtl_iterator()
|
||||
)
|
||||
|
||||
# parse to vtmesh
|
||||
mesh_trans.parse(writer, vtmesh)
|
||||
|
||||
# end of mesh trans
|
||||
# end of mesh visitor
|
||||
# end of temp mesh
|
||||
|
||||
# step
|
||||
progress.step()
|
||||
|
||||
# leave progress and return
|
||||
progress.leave_substeps()
|
||||
return tuple(material_crets)
|
||||
|
||||
def _export_virtools_materials(
|
||||
writer: bmap.BMFileWriter,
|
||||
progress: ProgressReport,
|
||||
material_crets: tuple[_TMaterialPair, ...]
|
||||
) -> tuple[_TTexturePair, ...]:
|
||||
# create virtools mesh
|
||||
texture_crets: list[_TTexturePair] = []
|
||||
texture_cret_map: dict[bpy.types.Image, bmap.BMTexture] = {}
|
||||
# start saving
|
||||
progress.enter_substeps(len(material_crets), "Saving Materials")
|
||||
|
||||
for mtl, vtmaterial in material_crets:
|
||||
# set name
|
||||
vtmaterial.set_name(mtl.name)
|
||||
|
||||
# get raw mtl
|
||||
rawmtl: PROP_virtools_material.RawVirtoolsMaterial = PROP_virtools_material.get_raw_virtools_material(mtl)
|
||||
|
||||
# apply vt material
|
||||
vtmaterial.set_diffuse(rawmtl.mDiffuse)
|
||||
vtmaterial.set_ambient(rawmtl.mAmbient)
|
||||
vtmaterial.set_specular(rawmtl.mSpecular)
|
||||
vtmaterial.set_emissive(rawmtl.mEmissive)
|
||||
vtmaterial.set_specular_power(rawmtl.mSpecularPower)
|
||||
|
||||
# apply assoc texture
|
||||
if rawmtl.mTexture is not None:
|
||||
# create or get new one vt texture
|
||||
vttexture: bmap.BMTexture | None = texture_cret_map.get(rawmtl.mTexture, None)
|
||||
if vttexture is None:
|
||||
vttexture = writer.create_texture()
|
||||
texture_cret_map[rawmtl.mTexture] = vttexture
|
||||
texture_crets.append((rawmtl.mTexture, vttexture))
|
||||
# assign texture
|
||||
vtmaterial.set_texture(vttexture)
|
||||
else:
|
||||
vtmaterial.set_texture(None)
|
||||
|
||||
vtmaterial.set_texture_border_color(rawmtl.mTextureBorderColor)
|
||||
|
||||
vtmaterial.set_texture_blend_mode(rawmtl.mTextureBlendMode)
|
||||
vtmaterial.set_texture_min_mode(rawmtl.mTextureMinMode)
|
||||
vtmaterial.set_texture_mag_mode(rawmtl.mTextureMagMode)
|
||||
vtmaterial.set_texture_address_mode(rawmtl.mTextureAddressMode)
|
||||
|
||||
vtmaterial.set_source_blend(rawmtl.mSourceBlend)
|
||||
vtmaterial.set_dest_blend(rawmtl.mDestBlend)
|
||||
vtmaterial.set_fill_mode(rawmtl.mFillMode)
|
||||
vtmaterial.set_shade_mode(rawmtl.mShadeMode)
|
||||
|
||||
vtmaterial.set_alpha_test_enabled(rawmtl.mEnableAlphaTest)
|
||||
vtmaterial.set_alpha_blend_enabled(rawmtl.mEnableAlphaBlend)
|
||||
vtmaterial.set_perspective_correction_enabled(rawmtl.mEnablePerspectiveCorrection)
|
||||
vtmaterial.set_z_write_enabled(rawmtl.mEnableZWrite)
|
||||
vtmaterial.set_two_sided_enabled(rawmtl.mEnableTwoSided)
|
||||
|
||||
vtmaterial.set_alpha_ref(rawmtl.mAlphaRef)
|
||||
vtmaterial.set_alpha_func(rawmtl.mAlphaFunc)
|
||||
vtmaterial.set_z_func(rawmtl.mZFunc)
|
||||
|
||||
# step
|
||||
progress.step()
|
||||
|
||||
# leave progress and return
|
||||
progress.leave_substeps()
|
||||
return tuple(texture_crets)
|
||||
|
||||
def _export_virtools_textures(
|
||||
writer: bmap.BMFileWriter,
|
||||
progress: ProgressReport,
|
||||
vt_temp_folder: str,
|
||||
texture_crets: tuple[_TTexturePair, ...]
|
||||
) -> None:
|
||||
# start saving
|
||||
progress.enter_substeps(len(texture_crets), "Saving Textures")
|
||||
|
||||
for tex, vttexture in texture_crets:
|
||||
# set name
|
||||
vttexture.set_name(tex.name)
|
||||
|
||||
# set texture cfg
|
||||
rawtex: PROP_virtools_texture.RawVirtoolsTexture = PROP_virtools_texture.get_raw_virtools_texture(tex)
|
||||
vttexture.set_save_options(rawtex.mSaveOptions)
|
||||
vttexture.set_video_format(rawtex.mVideoFormat)
|
||||
|
||||
# save core texture
|
||||
# load ballance textures to vt engine from external ref path
|
||||
# load other textures to vt engine from temp folder.
|
||||
# no need to distinguish save options
|
||||
try_filepath: str | None = UTIL_ballance_texture.get_ballance_texture_filename(
|
||||
UTIL_ballance_texture.get_texture_filepath(tex))
|
||||
if try_filepath is None:
|
||||
# non-ballance file, save in temp and change file path to point to it.
|
||||
try_filepath = UTIL_ballance_texture.generate_other_texture_save_path(tex, vt_temp_folder)
|
||||
UTIL_ballance_texture.save_other_texture(tex, try_filepath)
|
||||
# load into vt engine
|
||||
vttexture.load_image(try_filepath)
|
||||
|
||||
# step
|
||||
progress.step()
|
||||
|
||||
# leave progress and return
|
||||
progress.leave_substeps()
|
||||
|
||||
def _save_virtools_document(
|
||||
writer: bmap.BMFileWriter,
|
||||
progress: ProgressReport,
|
||||
file_name: str,
|
||||
texture_save_opt: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS,
|
||||
use_compress: bool,
|
||||
compress_level: int
|
||||
) -> None:
|
||||
|
||||
progress.enter_substeps(1, "Saving Document")
|
||||
writer.save(file_name, texture_save_opt, use_compress, compress_level)
|
||||
progress.step()
|
||||
progress.leave_substeps()
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_export_virtools)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_export_virtools)
|
32
bbp_ng/OP_IMPORT_bmfile.py
Normal file
32
bbp_ng/OP_IMPORT_bmfile.py
Normal file
@ -0,0 +1,32 @@
|
||||
import bpy
|
||||
from . import PROP_preferences, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_icons_manager, UTIL_ioport_shared
|
||||
|
||||
class BBP_OT_import_bmfile(bpy.types.Operator, UTIL_file_browser.ImportBmxFile, UTIL_ioport_shared.ImportParams):
|
||||
"""Load a Ballance Map File (BM File Spec 1.4)"""
|
||||
bl_idname = "bbp.import_bmfile"
|
||||
bl_label = "Import BM (Ballance Map) File"
|
||||
bl_options = {'PRESET', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||
|
||||
def execute(self, context):
|
||||
UTIL_functions.message_box(
|
||||
('This function not supported yet.', ),
|
||||
'No Implement',
|
||||
UTIL_icons_manager.BlenderPresetIcons.Error.value
|
||||
)
|
||||
self.report({'INFO'}, "BM File Importing Finished.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Conflict Options')
|
||||
self.draw_import_params(layout.box())
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_import_bmfile)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_import_bmfile)
|
408
bbp_ng/OP_IMPORT_virtools.py
Normal file
408
bbp_ng/OP_IMPORT_virtools.py
Normal file
@ -0,0 +1,408 @@
|
||||
import bpy
|
||||
from bpy_extras.wm_utils.progress_report import ProgressReport
|
||||
import tempfile, os, typing
|
||||
from . import PROP_preferences, UTIL_ioport_shared
|
||||
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_naming_convension
|
||||
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_ballance_map_info
|
||||
from .PyBMap import bmap_wrapper as bmap
|
||||
|
||||
class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams):
|
||||
"""Import Virtools File"""
|
||||
bl_idname = "bbp.import_virtools"
|
||||
bl_label = "Import Virtools File"
|
||||
bl_options = {'PRESET', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return (
|
||||
PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||
and bmap.is_bmap_available())
|
||||
|
||||
def execute(self, context):
|
||||
_import_virtools(
|
||||
self.general_get_filename(),
|
||||
self.general_get_vt_encodings(),
|
||||
self.general_get_conflict_resolver()
|
||||
)
|
||||
self.report({'INFO'}, "Virtools File Importing Finished.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Conflict Options')
|
||||
self.draw_import_params(layout.box())
|
||||
layout.separator()
|
||||
layout.label(text = 'Virtools Params')
|
||||
self.draw_virtools_params(layout.box())
|
||||
|
||||
def _import_virtools(file_name_: str, encodings_: tuple[str], resolver: UTIL_ioport_shared.ConflictResolver) -> None:
|
||||
# create temp folder
|
||||
with tempfile.TemporaryDirectory() as vt_temp_folder:
|
||||
print(f'Virtools Engine Temp: {vt_temp_folder}')
|
||||
|
||||
# create virtools reader context
|
||||
with bmap.BMFileReader(
|
||||
file_name_,
|
||||
vt_temp_folder,
|
||||
PROP_preferences.get_raw_preferences().mBallanceTextureFolder,
|
||||
encodings_) as reader:
|
||||
|
||||
# prepare progress reporter
|
||||
with ProgressReport(wm = bpy.context.window_manager) as progress:
|
||||
# import textures
|
||||
texture_cret_map: dict[bmap.BMTexture, bpy.types.Image] = _import_virtools_textures(
|
||||
reader, progress, resolver)
|
||||
# import materials
|
||||
material_cret_map: dict[bmap.BMMaterial, bpy.types.Material] = _import_virtools_materials(
|
||||
reader, progress, resolver, texture_cret_map)
|
||||
# import meshes
|
||||
mesh_cret_map: dict[bmap.BMMesh, bpy.types.Mesh] = _import_virtools_meshes(
|
||||
reader, progress, resolver, material_cret_map)
|
||||
# import 3dobjects
|
||||
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object] = _import_virtools_3dobjects(
|
||||
reader, progress, resolver, mesh_cret_map)
|
||||
# import groups
|
||||
_import_virtools_groups(reader, progress, obj3d_cret_map)
|
||||
|
||||
def _import_virtools_textures(
|
||||
reader: bmap.BMFileReader,
|
||||
progress: ProgressReport,
|
||||
resolver: UTIL_ioport_shared.ConflictResolver
|
||||
) -> dict[bmap.BMTexture, bpy.types.Image]:
|
||||
# create map
|
||||
texture_cret_map: dict[bmap.BMTexture, bpy.types.Image] = {}
|
||||
progress.enter_substeps(reader.get_texture_count(), "Loading Textures")
|
||||
|
||||
# create another temp folder for raw data virtools texture importing
|
||||
with tempfile.TemporaryDirectory() as rawdata_temp:
|
||||
print(f'Texture Raw Data Temp: {rawdata_temp}')
|
||||
|
||||
for vttexture in reader.get_textures():
|
||||
tex_cret: typing.Callable[[], bpy.types.Image]
|
||||
texpath_to_load: str | None = vttexture.get_file_name()
|
||||
|
||||
# if no assoc file path (what? but it is real happended)
|
||||
# this is invalid image, create a blank image instead
|
||||
if texpath_to_load is None:
|
||||
tex_cret = lambda: bpy.data.images.new("", 1, 1)
|
||||
else:
|
||||
# if this image is raw data, save it in external folder before loading
|
||||
# the attribute of raw data saving is the file path is not absolute path
|
||||
if not os.path.isabs(texpath_to_load):
|
||||
texpath_to_load = os.path.join(
|
||||
rawdata_temp,
|
||||
os.path.basename(texpath_to_load)
|
||||
)
|
||||
vttexture.save_image(texpath_to_load)
|
||||
|
||||
# detect whether it is ballance texture and load
|
||||
try_blc_tex: str | None = UTIL_ballance_texture.get_ballance_texture_filename(texpath_to_load)
|
||||
|
||||
if try_blc_tex is not None:
|
||||
# load as ballance texture
|
||||
tex_cret = lambda: UTIL_ballance_texture.load_ballance_texture(typing.cast(str, try_blc_tex))
|
||||
else:
|
||||
# load as other textures
|
||||
tex_cret = lambda: UTIL_ballance_texture.load_other_texture(typing.cast(str, texpath_to_load))
|
||||
|
||||
# create real texture by tex cret fct
|
||||
(tex, init_tex) = resolver.create_texture(
|
||||
UTIL_virtools_types.virtools_name_regulator(vttexture.get_name()),
|
||||
tex_cret
|
||||
)
|
||||
|
||||
# init tex if needed
|
||||
if init_tex:
|
||||
# set texture cfg
|
||||
rawtex: PROP_virtools_texture.RawVirtoolsTexture = PROP_virtools_texture.RawVirtoolsTexture()
|
||||
rawtex.mSaveOptions = vttexture.get_save_options()
|
||||
rawtex.mVideoFormat = vttexture.get_video_format()
|
||||
PROP_virtools_texture.set_raw_virtools_texture(tex, rawtex)
|
||||
|
||||
# insert it to map
|
||||
texture_cret_map[vttexture] = tex
|
||||
|
||||
# inc steps
|
||||
progress.step()
|
||||
|
||||
# leave progress and return map
|
||||
progress.leave_substeps()
|
||||
return texture_cret_map
|
||||
|
||||
def _import_virtools_materials(
|
||||
reader: bmap.BMFileReader,
|
||||
progress: ProgressReport,
|
||||
resolver: UTIL_ioport_shared.ConflictResolver,
|
||||
texture_cret_map: dict[bmap.BMTexture, bpy.types.Image]
|
||||
) -> dict[bmap.BMMaterial, bpy.types.Material]:
|
||||
# create map and prepare progress
|
||||
material_cret_map: dict[bmap.BMMaterial, bpy.types.Material] = {}
|
||||
progress.enter_substeps(reader.get_material_count(), "Loading Materials")
|
||||
|
||||
for vtmaterial in reader.get_materials():
|
||||
# create mtl
|
||||
(mtl, init_mtl) = resolver.create_material(
|
||||
UTIL_virtools_types.virtools_name_regulator(vtmaterial.get_name())
|
||||
)
|
||||
|
||||
# apply it if necessary
|
||||
if init_mtl:
|
||||
# create new raw material
|
||||
rawmtl: PROP_virtools_material.RawVirtoolsMaterial = PROP_virtools_material.RawVirtoolsMaterial()
|
||||
|
||||
rawmtl.mDiffuse = vtmaterial.get_diffuse()
|
||||
rawmtl.mAmbient = vtmaterial.get_ambient()
|
||||
rawmtl.mSpecular = vtmaterial.get_specular()
|
||||
rawmtl.mEmissive = vtmaterial.get_emissive()
|
||||
rawmtl.mSpecularPower = vtmaterial.get_specular_power()
|
||||
|
||||
mtltex: bmap.BMTexture | None = vtmaterial.get_texture()
|
||||
if mtltex:
|
||||
rawmtl.mTexture = texture_cret_map.get(mtltex, None)
|
||||
else:
|
||||
rawmtl.mTexture = None
|
||||
rawmtl.mTextureBorderColor = vtmaterial.get_texture_border_color()
|
||||
|
||||
rawmtl.mTextureBlendMode = vtmaterial.get_texture_blend_mode()
|
||||
rawmtl.mTextureMinMode = vtmaterial.get_texture_min_mode()
|
||||
rawmtl.mTextureMagMode = vtmaterial.get_texture_mag_mode()
|
||||
rawmtl.mTextureAddressMode = vtmaterial.get_texture_address_mode()
|
||||
|
||||
rawmtl.mSourceBlend = vtmaterial.get_source_blend()
|
||||
rawmtl.mDestBlend = vtmaterial.get_dest_blend()
|
||||
rawmtl.mFillMode = vtmaterial.get_fill_mode()
|
||||
rawmtl.mShadeMode = vtmaterial.get_shade_mode()
|
||||
|
||||
rawmtl.mEnableAlphaTest = vtmaterial.get_alpha_test_enabled()
|
||||
rawmtl.mEnableAlphaBlend = vtmaterial.get_alpha_blend_enabled()
|
||||
rawmtl.mEnablePerspectiveCorrection = vtmaterial.get_perspective_correction_enabled()
|
||||
rawmtl.mEnableZWrite = vtmaterial.get_z_write_enabled()
|
||||
rawmtl.mEnableTwoSided = vtmaterial.get_two_sided_enabled()
|
||||
|
||||
rawmtl.mAlphaRef = vtmaterial.get_alpha_ref()
|
||||
rawmtl.mAlphaFunc = vtmaterial.get_alpha_func()
|
||||
rawmtl.mZFunc = vtmaterial.get_z_func()
|
||||
|
||||
PROP_virtools_material.set_raw_virtools_material(mtl, rawmtl)
|
||||
PROP_virtools_material.apply_to_blender_material(mtl)
|
||||
|
||||
# add into map and step
|
||||
material_cret_map[vtmaterial] = mtl
|
||||
progress.step()
|
||||
|
||||
# leave progress and return
|
||||
progress.leave_substeps()
|
||||
return material_cret_map
|
||||
|
||||
def _import_virtools_meshes(
|
||||
reader: bmap.BMFileReader,
|
||||
progress: ProgressReport,
|
||||
resolver: UTIL_ioport_shared.ConflictResolver,
|
||||
material_cret_map: dict[bmap.BMMaterial, bpy.types.Material]
|
||||
) -> dict[bmap.BMMesh, bpy.types.Mesh]:
|
||||
# create map and prepare progress
|
||||
mesh_cret_map: dict[bmap.BMMesh, bpy.types.Mesh] = {}
|
||||
progress.enter_substeps(reader.get_material_count(), "Loading Meshes")
|
||||
|
||||
for vtmesh in reader.get_meshs():
|
||||
# create mesh
|
||||
(mesh, init_mesh) = resolver.create_mesh(
|
||||
UTIL_virtools_types.virtools_name_regulator(vtmesh.get_name())
|
||||
)
|
||||
|
||||
# set mesh data if necessary
|
||||
if init_mesh:
|
||||
# open mesh writer
|
||||
with UTIL_blender_mesh.MeshWriter(mesh) as meshoper:
|
||||
# construct data provider
|
||||
data_prov: UTIL_blender_mesh.MeshWriterIngredient = UTIL_blender_mesh.MeshWriterIngredient()
|
||||
|
||||
# constructor data itor
|
||||
def pos_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||
for v in vtmesh.get_vertex_positions():
|
||||
UTIL_virtools_types.vxvector3_conv_co(v)
|
||||
yield v
|
||||
def nml_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||
for v in vtmesh.get_vertex_normals():
|
||||
UTIL_virtools_types.vxvector3_conv_co(v)
|
||||
yield v
|
||||
def uv_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector2]:
|
||||
for v in vtmesh.get_vertex_uvs():
|
||||
UTIL_virtools_types.vxvector2_conv_co(v)
|
||||
yield v
|
||||
def face_iterator() -> typing.Iterator[UTIL_blender_mesh.FaceData]:
|
||||
face: UTIL_blender_mesh.FaceData = UTIL_blender_mesh.FaceData(
|
||||
[UTIL_blender_mesh.FaceVertexData() for i in range(3)]
|
||||
)
|
||||
|
||||
findices_itor = vtmesh.get_face_indices()
|
||||
fmtl_itor = vtmesh.get_face_material_slot_indexs()
|
||||
for _ in range(vtmesh.get_face_count()):
|
||||
# set indices data
|
||||
vtindices = next(findices_itor)
|
||||
face.mIndices[0].mPosIdx = vtindices.i1
|
||||
face.mIndices[0].mNmlIdx = vtindices.i1
|
||||
face.mIndices[0].mUvIdx = vtindices.i1
|
||||
face.mIndices[1].mPosIdx = vtindices.i2
|
||||
face.mIndices[1].mNmlIdx = vtindices.i2
|
||||
face.mIndices[1].mUvIdx = vtindices.i2
|
||||
face.mIndices[2].mPosIdx = vtindices.i3
|
||||
face.mIndices[2].mNmlIdx = vtindices.i3
|
||||
face.mIndices[2].mUvIdx = vtindices.i3
|
||||
# swap indices
|
||||
face.conv_co()
|
||||
|
||||
# set mtl data
|
||||
vtmtl = next(fmtl_itor)
|
||||
face.mMtlIdx = vtmtl
|
||||
|
||||
# return
|
||||
yield face
|
||||
def mtl_iterator() -> typing.Iterator[bpy.types.Material | None]:
|
||||
for vtmtl in vtmesh.get_material_slots():
|
||||
if vtmtl:
|
||||
yield material_cret_map.get(vtmtl, None)
|
||||
else:
|
||||
yield None
|
||||
|
||||
# assign to data provider
|
||||
data_prov.mVertexPosition = pos_iterator()
|
||||
data_prov.mVertexNormal = nml_iterator()
|
||||
data_prov.mVertexUV = uv_iterator()
|
||||
data_prov.mFace = face_iterator()
|
||||
data_prov.mMaterial = mtl_iterator()
|
||||
|
||||
# add part
|
||||
meshoper.add_ingredient(data_prov)
|
||||
|
||||
# end of mesh writer
|
||||
|
||||
# set other mesh settings
|
||||
mesh_settings: PROP_virtools_mesh.RawVirtoolsMesh = PROP_virtools_mesh.RawVirtoolsMesh()
|
||||
mesh_settings.mLitMode = vtmesh.get_lit_mode()
|
||||
PROP_virtools_mesh.set_raw_virtools_mesh(mesh, mesh_settings)
|
||||
|
||||
# add into map and step
|
||||
mesh_cret_map[vtmesh] = mesh
|
||||
progress.step()
|
||||
|
||||
# leave progress and return
|
||||
progress.leave_substeps()
|
||||
return mesh_cret_map
|
||||
|
||||
def _import_virtools_3dobjects(
|
||||
reader: bmap.BMFileReader,
|
||||
progress: ProgressReport,
|
||||
resolver: UTIL_ioport_shared.ConflictResolver,
|
||||
mesh_cret_map: dict[bmap.BMMesh, bpy.types.Mesh]
|
||||
) -> dict[bmap.BM3dObject, bpy.types.Object]:
|
||||
# create map and prepare progress
|
||||
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object] = {}
|
||||
progress.enter_substeps(reader.get_material_count(), "Loading 3dObjects")
|
||||
|
||||
# get some essential blender data
|
||||
blender_view_layer = bpy.context.view_layer
|
||||
blender_collection = blender_view_layer.active_layer_collection.collection
|
||||
|
||||
for vt3dobj in reader.get_3dobjects():
|
||||
# get virtools binding mesh data first
|
||||
vt3dobj_data: bmap.BMMesh | None = vt3dobj.get_current_mesh()
|
||||
|
||||
# create 3d object with mesh
|
||||
(obj3d, init_obj3d) = resolver.create_object(
|
||||
UTIL_virtools_types.virtools_name_regulator(vt3dobj.get_name()),
|
||||
None if vt3dobj_data is None else mesh_cret_map.get(vt3dobj_data, None)
|
||||
)
|
||||
|
||||
# setup if necessary
|
||||
if init_obj3d:
|
||||
# link to collection
|
||||
blender_collection.objects.link(obj3d)
|
||||
|
||||
# set world matrix
|
||||
vtmat: UTIL_virtools_types.VxMatrix = vt3dobj.get_world_matrix()
|
||||
UTIL_virtools_types.vxmatrix_conv_co(vtmat)
|
||||
obj3d.matrix_world = UTIL_virtools_types.vxmatrix_to_blender(vtmat)
|
||||
|
||||
# set visibility
|
||||
obj3d.hide_set(not vt3dobj.get_visibility())
|
||||
|
||||
# add into map
|
||||
# NOTE: the return value only provided to group setter
|
||||
# and group setter should only set group data to new created 3d objects
|
||||
# thus we only insert pair when this 3d obj is new created.
|
||||
obj3d_cret_map[vt3dobj] = obj3d
|
||||
|
||||
# step forward
|
||||
progress.step()
|
||||
|
||||
# leave progress and return
|
||||
progress.leave_substeps()
|
||||
return obj3d_cret_map
|
||||
|
||||
def _import_virtools_groups(
|
||||
reader: bmap.BMFileReader,
|
||||
progress: ProgressReport,
|
||||
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object]
|
||||
) -> None:
|
||||
# we need iterate all groups to construct a reversed map
|
||||
# to indicate which groups should this 3dobject be grouped into.
|
||||
reverse_map: dict[bmap.BM3dObject, set[str]] = {}
|
||||
# sector counter to record the maximum sector we have processed.
|
||||
sector_count: int = 1
|
||||
|
||||
# prepare progress
|
||||
progress.enter_substeps(reader.get_material_count(), "Loading Groups")
|
||||
|
||||
for vtgroup in reader.get_groups():
|
||||
# if this group do not have name, skip it
|
||||
group_name: str | None = vtgroup.get_name()
|
||||
if group_name is None: continue
|
||||
|
||||
# try extracting sector info
|
||||
potential_sector_count: int | None = UTIL_naming_convension.extract_sector_from_name(group_name)
|
||||
if potential_sector_count is not None:
|
||||
sector_count = max(sector_count, potential_sector_count)
|
||||
|
||||
# creating map
|
||||
for item in vtgroup.get_objects():
|
||||
# get or create set
|
||||
objgroups: set[str] = reverse_map.get(item, None)
|
||||
if objgroups is None:
|
||||
objgroups = set()
|
||||
reverse_map[item] = objgroups
|
||||
|
||||
# add into list
|
||||
objgroups.add(group_name)
|
||||
|
||||
# step
|
||||
progress.step()
|
||||
|
||||
# assign to ballance map info according to gotten sector count
|
||||
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||
map_info.mSectorCount = max(map_info.mSectorCount, sector_count)
|
||||
PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info)
|
||||
|
||||
# leave progress
|
||||
progress.leave_substeps()
|
||||
|
||||
# now we can assign 3dobject group data by reverse map
|
||||
progress.enter_substeps(reader.get_material_count(), "Applying Groups")
|
||||
for mapk, mapv in reverse_map.items():
|
||||
# check object
|
||||
assoc_obj = obj3d_cret_map.get(mapk, None)
|
||||
if assoc_obj is None: continue
|
||||
|
||||
# assign group
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(assoc_obj) as gpoper:
|
||||
gpoper.clear_groups()
|
||||
gpoper.add_groups(mapv)
|
||||
|
||||
progress.leave_substeps()
|
||||
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_import_virtools)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_import_virtools)
|
39
bbp_ng/OP_MTL_fix_material.py
Normal file
39
bbp_ng/OP_MTL_fix_material.py
Normal file
@ -0,0 +1,39 @@
|
||||
import bpy
|
||||
from . import UTIL_functions
|
||||
from . import PROP_virtools_material, PROP_preferences
|
||||
|
||||
class BBP_OT_fix_all_material(bpy.types.Operator):
|
||||
"""Fix All Materials by Its Referred Ballance Texture Name."""
|
||||
bl_idname = "bbp.fix_all_material"
|
||||
bl_label = "Fix Material"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
# only enable this when plugin have a valid ballance texture folder
|
||||
# and we are in object mode
|
||||
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder() and UTIL_functions.is_in_object_mode()
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_confirm(self, event)
|
||||
|
||||
def execute(self, context):
|
||||
# do work and count
|
||||
counter_all: int = 0
|
||||
counter_suc: int = 0
|
||||
for mtl in bpy.data.materials:
|
||||
counter_all += 1
|
||||
if PROP_virtools_material.fix_material(mtl):
|
||||
PROP_virtools_material.apply_to_blender_material(mtl)
|
||||
counter_suc += 1
|
||||
|
||||
# report and return
|
||||
self.report({'INFO'}, f'Fix {counter_suc}/{counter_all} materials.')
|
||||
return {'FINISHED'}
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_fix_all_material)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_fix_all_material)
|
276
bbp_ng/OP_OBJECT_legacy_align.py
Normal file
276
bbp_ng/OP_OBJECT_legacy_align.py
Normal file
@ -0,0 +1,276 @@
|
||||
import bpy, mathutils
|
||||
import enum, typing
|
||||
from . import UTIL_functions
|
||||
|
||||
#region Align Mode
|
||||
|
||||
class AlignMode(enum.IntEnum):
|
||||
Min = enum.auto()
|
||||
BBoxCenter = enum.auto()
|
||||
AxisCenter = enum.auto()
|
||||
Max = enum.auto()
|
||||
_g_AlignModeDesc: dict[AlignMode, tuple[str, str]] = {
|
||||
AlignMode.Min: ("Min", "The min value in specified axis."),
|
||||
AlignMode.BBoxCenter: ("Center (Bounding Box)", "The bounding box center in specified axis."),
|
||||
AlignMode.AxisCenter: ("Center (Axis)", "The object's source point in specified axis."),
|
||||
AlignMode.Max: ("Max", "The max value in specified axis."),
|
||||
}
|
||||
_g_EnumHelper_AlignMode: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
|
||||
AlignMode,
|
||||
lambda x: str(x.value),
|
||||
lambda x: AlignMode(int(x)),
|
||||
lambda x: _g_AlignModeDesc[x][0],
|
||||
lambda x: _g_AlignModeDesc[x][1],
|
||||
lambda _: ''
|
||||
)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Align Cache Implement
|
||||
|
||||
## As we known, 3ds Max's align window have a Apply button which can apply current align to scene,
|
||||
# and user call set next align settings after clicking Apply. It will not affect previous set align settings.
|
||||
# But Blender have no vanilla Apply function for operator. The only possible way is re-run this operator.
|
||||
# However the experience is pretty shit. Because the window still locate at the left-bottom corner.
|
||||
# User can't keep up to change it.
|
||||
#
|
||||
# We use a dirty way to implement Apply function. The solution is pretty like BME struct adder.
|
||||
# We use a CollectionProperty to store all align steps.
|
||||
# And use a BoolProperty with update function to implement Apply button. Once its value changed,
|
||||
# reset its value (order a recursive hinder), and add a new settings.
|
||||
|
||||
class BBP_PG_legacy_align_history(bpy.types.PropertyGroup):
|
||||
align_x: bpy.props.BoolProperty(
|
||||
name = "X Position",
|
||||
default = False,
|
||||
) # type: ignore
|
||||
align_y: bpy.props.BoolProperty(
|
||||
name = "Y Position",
|
||||
default = False,
|
||||
) # type: ignore
|
||||
align_z: bpy.props.BoolProperty(
|
||||
name = "Z Position",
|
||||
default = False,
|
||||
) # type: ignore
|
||||
current_align_mode: bpy.props.EnumProperty(
|
||||
name = "Current Object (Active Object)",
|
||||
items = _g_EnumHelper_AlignMode.generate_items(),
|
||||
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
||||
) # type: ignore
|
||||
target_align_mode: bpy.props.EnumProperty(
|
||||
name = "Target Objects (Other Objects)",
|
||||
items = _g_EnumHelper_AlignMode.generate_items(),
|
||||
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
||||
) # type: ignore
|
||||
|
||||
#endregion
|
||||
|
||||
class BBP_OT_legacy_align(bpy.types.Operator):
|
||||
"""Align Objects with 3ds Max Style"""
|
||||
bl_idname = "bbp.legacy_align"
|
||||
bl_label = "3ds Max Align"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
# the updator for apply flag value
|
||||
def apply_flag_updated(self, context):
|
||||
# check hinder and set hinder first
|
||||
if self.recursive_hinder: return
|
||||
self.recursive_hinder = True
|
||||
|
||||
# reset apply button value (default is True)
|
||||
# due to the hinder, no recursive calling will happend
|
||||
if self.apply_flag == True: return
|
||||
self.apply_flag = True
|
||||
|
||||
# check whether add new entry
|
||||
# if no selected axis, this alignment is invalid
|
||||
entry: BBP_PG_legacy_align_history = self.align_history[-1]
|
||||
if entry.align_x == True or entry.align_y == True or entry.align_z == True:
|
||||
# valid one
|
||||
# add a new entry in history
|
||||
self.align_history.add()
|
||||
else:
|
||||
# invalid one
|
||||
# reset all data to default
|
||||
entry.align_x = False
|
||||
entry.align_y = False
|
||||
entry.align_z = False
|
||||
entry.current_align_mode = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter)
|
||||
entry.target_align_mode = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter)
|
||||
|
||||
# reset hinder
|
||||
self.recursive_hinder = False
|
||||
# blender required
|
||||
return None
|
||||
|
||||
apply_flag: bpy.props.BoolProperty(
|
||||
name = "Apply Flag",
|
||||
description = "Internal flag.",
|
||||
options = {'HIDDEN', 'SKIP_SAVE'},
|
||||
default = True, # default True value to make it as a "light" button, not a grey one.
|
||||
update = apply_flag_updated,
|
||||
) # type: ignore
|
||||
recursive_hinder: bpy.props.BoolProperty(
|
||||
name = "Recursive Hinder",
|
||||
description = "An internal flag to prevent the loop calling to apply_flags's updator.",
|
||||
options = {'HIDDEN', 'SKIP_SAVE'},
|
||||
default = False,
|
||||
) # type: ignore
|
||||
align_history : bpy.props.CollectionProperty(
|
||||
name = "Historys",
|
||||
description = "Align history.",
|
||||
type = BBP_PG_legacy_align_history,
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return _check_align_requirement()
|
||||
|
||||
def invoke(self, context, event):
|
||||
# clear history and add 1 entry for following functions
|
||||
self.align_history.clear()
|
||||
self.align_history.add()
|
||||
# run execute() function
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
# get processed objects
|
||||
(current_obj, target_objs) = _prepare_objects()
|
||||
# iterate history to align objects
|
||||
entry: BBP_PG_legacy_align_history
|
||||
for entry in self.align_history:
|
||||
_align_objects(
|
||||
current_obj, target_objs,
|
||||
entry.align_x, entry.align_y, entry.align_z,
|
||||
_g_EnumHelper_AlignMode.get_selection(entry.current_align_mode),
|
||||
_g_EnumHelper_AlignMode.get_selection(entry.target_align_mode)
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
# get last entry in history to show
|
||||
entry: BBP_PG_legacy_align_history = self.align_history[-1]
|
||||
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
|
||||
# show axis
|
||||
col.label(text="Align Axis (Multi-selection)")
|
||||
row = col.row()
|
||||
row.prop(entry, "align_x", toggle = 1)
|
||||
row.prop(entry, "align_y", toggle = 1)
|
||||
row.prop(entry, "align_z", toggle = 1)
|
||||
|
||||
# show mode
|
||||
col.separator()
|
||||
col.label(text = 'Current Object (Active Object)')
|
||||
col.prop(entry, "current_align_mode", expand = True)
|
||||
col.label(text = 'Target Objects (Selected Objects)')
|
||||
col.prop(entry, "target_align_mode", expand = True)
|
||||
|
||||
# show apply button
|
||||
col.separator()
|
||||
conditional_disable_area = col.column()
|
||||
# only allow Apply when there is a selected axis
|
||||
conditional_disable_area.enabled = entry.align_x == True or entry.align_y == True or entry.align_z == True
|
||||
# show apply and counter
|
||||
conditional_disable_area.prop(self, 'apply_flag', text = 'Apply', icon = 'CHECKMARK', toggle = 1)
|
||||
conditional_disable_area.label(text = f'Total {len(self.align_history) - 1} applied alignments')
|
||||
|
||||
#region Core Functions
|
||||
|
||||
def _check_align_requirement() -> bool:
|
||||
# if we are not in object mode, do not do legacy align
|
||||
if not UTIL_functions.is_in_object_mode():
|
||||
return False
|
||||
|
||||
# check current obj
|
||||
if bpy.context.active_object is None:
|
||||
return False
|
||||
# check target obj with filter of current obj
|
||||
length = len(bpy.context.selected_objects)
|
||||
if bpy.context.active_object in bpy.context.selected_objects:
|
||||
length -= 1
|
||||
return length != 0
|
||||
|
||||
def _prepare_objects() -> tuple[bpy.types.Object, set[bpy.types.Object]]:
|
||||
# get current object
|
||||
current_obj: bpy.types.Object = bpy.context.active_object
|
||||
|
||||
# get target objects
|
||||
target_objs: set[bpy.types.Object] = set(bpy.context.selected_objects)
|
||||
# remove active one
|
||||
if current_obj in target_objs:
|
||||
target_objs.remove(current_obj)
|
||||
|
||||
# return value
|
||||
return (current_obj, target_objs)
|
||||
|
||||
def _align_objects(
|
||||
current_obj: bpy.types.Object, target_objs: set[bpy.types.Object],
|
||||
align_x: bool, align_y: bool, align_z: bool, current_mode: AlignMode, target_mode: AlignMode) -> None:
|
||||
# if no align, skip
|
||||
if not (align_x or align_y or align_z):
|
||||
return
|
||||
|
||||
# calc current object data
|
||||
current_obj_bbox: tuple[mathutils.Vector] = tuple(current_obj.matrix_world @ mathutils.Vector(corner) for corner in current_obj.bound_box)
|
||||
current_obj_ref: mathutils.Vector = _get_object_ref_point(current_obj, current_obj_bbox, current_mode)
|
||||
|
||||
# process each target obj
|
||||
for target_obj in target_objs:
|
||||
# calc target object data
|
||||
target_obj_bbox: tuple[mathutils.Vector] = tuple(target_obj.matrix_world @ mathutils.Vector(corner) for corner in target_obj.bound_box)
|
||||
target_obj_ref: mathutils.Vector = _get_object_ref_point(target_obj, target_obj_bbox, target_mode)
|
||||
# do align
|
||||
if align_x:
|
||||
target_obj.location.x += current_obj_ref.x - target_obj_ref.x
|
||||
if align_y:
|
||||
target_obj.location.y += current_obj_ref.y - target_obj_ref.y
|
||||
if align_z:
|
||||
target_obj.location.z += current_obj_ref.z - target_obj_ref.z
|
||||
|
||||
def _get_object_ref_point(obj: bpy.types.Object, corners: tuple[mathutils.Vector], mode: AlignMode) -> mathutils.Vector:
|
||||
ref_pos: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
||||
|
||||
match(mode):
|
||||
case AlignMode.Min:
|
||||
ref_pos.x = min((vec.x for vec in corners))
|
||||
ref_pos.y = min((vec.y for vec in corners))
|
||||
ref_pos.z = min((vec.z for vec in corners))
|
||||
case AlignMode.Max:
|
||||
ref_pos.x = max((vec.x for vec in corners))
|
||||
ref_pos.y = max((vec.y for vec in corners))
|
||||
ref_pos.z = max((vec.z for vec in corners))
|
||||
case AlignMode.BBoxCenter:
|
||||
max_vec_cache: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
||||
min_vec_cache: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
||||
|
||||
min_vec_cache.x = min((vec.x for vec in corners))
|
||||
min_vec_cache.y = min((vec.y for vec in corners))
|
||||
min_vec_cache.z = min((vec.z for vec in corners))
|
||||
max_vec_cache.x = max((vec.x for vec in corners))
|
||||
max_vec_cache.y = max((vec.y for vec in corners))
|
||||
max_vec_cache.z = max((vec.z for vec in corners))
|
||||
|
||||
ref_pos.x = (max_vec_cache.x + min_vec_cache.x) / 2
|
||||
ref_pos.y = (max_vec_cache.y + min_vec_cache.y) / 2
|
||||
ref_pos.z = (max_vec_cache.z + min_vec_cache.z) / 2
|
||||
case AlignMode.AxisCenter:
|
||||
ref_pos.x = obj.location.x
|
||||
ref_pos.y = obj.location.y
|
||||
ref_pos.z = obj.location.z
|
||||
case _:
|
||||
raise UTIL_functions.BBPException('inpossible align mode.')
|
||||
|
||||
return ref_pos
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_PG_legacy_align_history)
|
||||
bpy.utils.register_class(BBP_OT_legacy_align)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_legacy_align)
|
||||
bpy.utils.unregister_class(BBP_PG_legacy_align_history)
|
100
bbp_ng/OP_OBJECT_naming_convention.py
Normal file
100
bbp_ng/OP_OBJECT_naming_convention.py
Normal file
@ -0,0 +1,100 @@
|
||||
import bpy
|
||||
import typing
|
||||
from . import UTIL_naming_convension, UTIL_functions, UTIL_icons_manager
|
||||
|
||||
class BBP_OT_regulate_objects_name(bpy.types.Operator):
|
||||
"""Regulate Objects Name by Virtools Group and Naming Convention"""
|
||||
bl_idname = "bbp.regulate_objects_name"
|
||||
bl_label = "Regulate Objects Name"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_confirm(self, event)
|
||||
|
||||
def execute(self, context):
|
||||
_rename_core(
|
||||
UTIL_naming_convension.VirtoolsGroupConvention.parse_from_object,
|
||||
UTIL_naming_convension.YYCToolchainConvention.set_to_object
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_OT_auto_grouping(bpy.types.Operator):
|
||||
"""Auto Grouping Objects by Its Name and Name Convention"""
|
||||
bl_idname = "bbp.auto_grouping"
|
||||
bl_label = "Auto Grouping"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_confirm(self, event)
|
||||
|
||||
def execute(self, context):
|
||||
_rename_core(
|
||||
UTIL_naming_convension.YYCToolchainConvention.parse_from_object,
|
||||
UTIL_naming_convension.VirtoolsGroupConvention.set_to_object
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_OT_convert_to_imengyu(bpy.types.Operator):
|
||||
"""Convert Objects Name from YYC Convention to Imengyu Convention."""
|
||||
bl_idname = "bbp.convert_to_imengyu"
|
||||
bl_label = "Convert to Imengyu"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_confirm(self, event)
|
||||
|
||||
def execute(self, context):
|
||||
_rename_core(
|
||||
UTIL_naming_convension.YYCToolchainConvention.parse_from_object,
|
||||
UTIL_naming_convension.ImengyuConvention.set_to_object
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def _rename_core(
|
||||
fct_get_info: typing.Callable[[bpy.types.Object, UTIL_naming_convension.RenameErrorReporter], UTIL_naming_convension.BallanceObjectInfo | None],
|
||||
ftc_set_info: typing.Callable[[bpy.types.Object, UTIL_naming_convension.BallanceObjectInfo, UTIL_naming_convension.RenameErrorReporter], bool]
|
||||
) -> None:
|
||||
# get selected objects. allow nested collection
|
||||
selected_objects: typing.Iterable[bpy.types.Object] = bpy.context.view_layer.active_layer_collection.collection.all_objects
|
||||
|
||||
# create reporter
|
||||
with UTIL_naming_convension.RenameErrorReporter() as reporter:
|
||||
# iterate objects
|
||||
for obj in selected_objects:
|
||||
reporter.enter_object(obj)
|
||||
|
||||
# try get info
|
||||
info: UTIL_naming_convension.BallanceObjectInfo | None = fct_get_info(obj, reporter)
|
||||
if info is not None:
|
||||
# if info is valid, try assign it
|
||||
if not ftc_set_info(obj, info, reporter):
|
||||
reporter.add_error('Fail to set info to object.')
|
||||
else:
|
||||
reporter.add_error('Fail to get info from object.')
|
||||
|
||||
# end obj process
|
||||
reporter.leave_object(obj)
|
||||
|
||||
# report data
|
||||
UTIL_functions.message_box(
|
||||
(
|
||||
'View console to get more detail',
|
||||
f'All: {reporter.get_all_objs_count()}',
|
||||
f'Failed: {reporter.get_failed_objs_count()}'
|
||||
),
|
||||
'Rename System Report',
|
||||
UTIL_icons_manager.BlenderPresetIcons.Info.value
|
||||
)
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_regulate_objects_name)
|
||||
bpy.utils.register_class(BBP_OT_auto_grouping)
|
||||
bpy.utils.register_class(BBP_OT_convert_to_imengyu)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_convert_to_imengyu)
|
||||
bpy.utils.unregister_class(BBP_OT_auto_grouping)
|
||||
bpy.utils.unregister_class(BBP_OT_regulate_objects_name)
|
206
bbp_ng/OP_OBJECT_virtools_group.py
Normal file
206
bbp_ng/OP_OBJECT_virtools_group.py
Normal file
@ -0,0 +1,206 @@
|
||||
import bpy
|
||||
import enum
|
||||
from . import PROP_virtools_group
|
||||
from . import UTIL_functions
|
||||
|
||||
#region Select by Group
|
||||
|
||||
class SelectMode(enum.IntEnum):
|
||||
Set = enum.auto()
|
||||
Extend = enum.auto()
|
||||
Subtract = enum.auto()
|
||||
Difference = enum.auto()
|
||||
Intersect = enum.auto()
|
||||
_g_SelectModeDesc: dict[SelectMode, tuple[str, str, str]] = {
|
||||
SelectMode.Set: ('Set', 'Sets a new selection.', 'SELECT_SET'),
|
||||
SelectMode.Extend: ('Extend', 'Adds newly selected items to the existing selection.', 'SELECT_EXTEND'),
|
||||
SelectMode.Subtract: ('Subtract', 'Removes newly selected items from the existing selection.', 'SELECT_SUBTRACT'),
|
||||
SelectMode.Difference: ('Invert', 'Inverts the selection.', 'SELECT_DIFFERENCE'),
|
||||
SelectMode.Intersect: ('Intersect', 'Selects items that intersect with the existing selection.', 'SELECT_INTERSECT')
|
||||
}
|
||||
_g_EnumHelper_SelectMode: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
|
||||
SelectMode,
|
||||
lambda x: str(x.value),
|
||||
lambda x: SelectMode(int(x)),
|
||||
lambda x: _g_SelectModeDesc[x][0],
|
||||
lambda x: _g_SelectModeDesc[x][1],
|
||||
lambda x: _g_SelectModeDesc[x][2]
|
||||
)
|
||||
|
||||
class BBP_OT_select_object_by_virtools_group(bpy.types.Operator, PROP_virtools_group.SharedGroupNameInputProperties):
|
||||
"""Select Objects by Virtools Group"""
|
||||
bl_idname = "bbp.select_object_by_virtools_group"
|
||||
bl_label = "Select by Virtools Group"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
selection_mode: bpy.props.EnumProperty(
|
||||
name = "Mode",
|
||||
description = "Selection mode",
|
||||
items = _g_EnumHelper_SelectMode.generate_items(),
|
||||
default = _g_EnumHelper_SelectMode.to_selection(SelectMode.Intersect)
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return UTIL_functions.is_in_object_mode()
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
_select_object_by_virtools_group(
|
||||
self.general_get_group_name(),
|
||||
_g_EnumHelper_SelectMode.get_selection(self.selection_mode)
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text='Selection Mode')
|
||||
sublayout = layout.column() # make selection expand vertically, not horizontal.
|
||||
sublayout.prop(self, 'selection_mode', expand = True)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text='Group Parameters')
|
||||
self.draw_group_name_input(layout)
|
||||
|
||||
def _select_object_by_virtools_group(group_name: str, mode: SelectMode) -> None:
|
||||
match(mode):
|
||||
case SelectMode.Set:
|
||||
# iterate all objects and directly set
|
||||
for obj in bpy.context.scene.objects:
|
||||
# check group and decide whether select this obj
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
obj.select_set(gp.contain_group(group_name))
|
||||
case SelectMode.Extend:
|
||||
# also iterate all objects
|
||||
for obj in bpy.context.scene.objects:
|
||||
# but only increase selection, for selected object, skip check
|
||||
if obj.select_get(): continue
|
||||
# if not selected, check whether add it.
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
if gp.contain_group(group_name):
|
||||
obj.select_set(True)
|
||||
case SelectMode.Subtract:
|
||||
# subtract only involving selected item. so we get selected objest first
|
||||
# and copy it (because we need modify it)
|
||||
# and iterate it to reduce useless operations
|
||||
selected = bpy.context.selected_objects[:]
|
||||
for obj in selected:
|
||||
# remove matched only
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
if gp.contain_group(group_name):
|
||||
obj.select_set(False)
|
||||
case SelectMode.Difference:
|
||||
# construct a selected obj set for convenient operations
|
||||
selected_set = set(bpy.context.selected_objects)
|
||||
# iterate all objects
|
||||
for obj in bpy.context.scene.objects:
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
# use xor to select
|
||||
# in_selected XOR in_group
|
||||
obj.select_set((obj in selected_set) ^ gp.contain_group(group_name))
|
||||
case SelectMode.Intersect:
|
||||
# like subtract, only iterate selected obj
|
||||
selected = bpy.context.selected_objects[:]
|
||||
for obj in selected:
|
||||
# but remove not matched
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
if not gp.contain_group(group_name):
|
||||
obj.select_set(False)
|
||||
case _:
|
||||
raise UTIL_functions.BBPException('invalid selection mode')
|
||||
|
||||
#endregion
|
||||
|
||||
#region Objects Group Opers
|
||||
|
||||
class BBP_OT_add_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.SharedGroupNameInputProperties):
|
||||
"""Grouping Selected Objects"""
|
||||
bl_idname = "bbp.add_objects_virtools_group"
|
||||
bl_label = "Grouping Objects"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(bpy.context.selected_objects) != 0
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
group_name: str = self.general_get_group_name()
|
||||
for obj in bpy.context.selected_objects:
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
gp.add_group(group_name)
|
||||
self.report({'INFO'}, "Grouping objects successfully.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
self.draw_group_name_input(self.layout)
|
||||
|
||||
class BBP_OT_rm_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.SharedGroupNameInputProperties):
|
||||
"""Ungrouping Selected Objects"""
|
||||
bl_idname = "bbp.rm_objects_virtools_group"
|
||||
bl_label = "Ungrouping Objects"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(bpy.context.selected_objects) != 0
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
group_name: str = self.general_get_group_name()
|
||||
for obj in bpy.context.selected_objects:
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
gp.remove_group(group_name)
|
||||
self.report({'INFO'}, "Ungrouping objects successfully.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
self.draw_group_name_input(self.layout)
|
||||
|
||||
class BBP_OT_clear_objects_virtools_group(bpy.types.Operator):
|
||||
"""Clear Virtools Groups on Selected Objects"""
|
||||
bl_idname = "bbp.clear_objects_virtools_group"
|
||||
bl_label = "Clear All Groups"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(bpy.context.selected_objects) != 0
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_confirm(self, event)
|
||||
|
||||
def execute(self, context):
|
||||
# iterate object
|
||||
for obj in bpy.context.selected_objects:
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
gp.clear_groups()
|
||||
self.report({'INFO'}, "Clear objects groups successfully.")
|
||||
return {'FINISHED'}
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_select_object_by_virtools_group)
|
||||
|
||||
bpy.utils.register_class(BBP_OT_add_objects_virtools_group)
|
||||
bpy.utils.register_class(BBP_OT_rm_objects_virtools_group)
|
||||
bpy.utils.register_class(BBP_OT_clear_objects_virtools_group)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_clear_objects_virtools_group)
|
||||
bpy.utils.unregister_class(BBP_OT_rm_objects_virtools_group)
|
||||
bpy.utils.unregister_class(BBP_OT_add_objects_virtools_group)
|
||||
|
||||
bpy.utils.unregister_class(BBP_OT_select_object_by_virtools_group)
|
||||
|
575
bbp_ng/OP_UV_flatten_uv.py
Normal file
575
bbp_ng/OP_UV_flatten_uv.py
Normal file
@ -0,0 +1,575 @@
|
||||
import bpy, mathutils, bmesh
|
||||
import typing, enum, collections
|
||||
from . import UTIL_virtools_types, UTIL_functions
|
||||
|
||||
#region Param Struct
|
||||
|
||||
class FlattenMethod(enum.IntEnum):
|
||||
# The legacy flatten uv mode. Only just do space convertion for each individual faces.
|
||||
Raw = enum.auto()
|
||||
# The floor specific flatten uv.
|
||||
# This method will make sure the continuity in V axis in uv when flatten uv.
|
||||
# Only support rectangle faces.
|
||||
Floor = enum.auto()
|
||||
# The wood specific flatten uv.
|
||||
# Similar floor, but it will force all horizontal uv edge parallel with U axis.
|
||||
# Not only V axis, but also U axis' continuity will been make sure.
|
||||
Wood = enum.auto()
|
||||
|
||||
class NeighborType(enum.IntEnum):
|
||||
"""
|
||||
NeighborType is used by special flatten uv to describe the direction of neighbor.
|
||||
|
||||
Normally we find neighbor by +V, +U direction (in UV world), these neighbors are "forward" neighbors and marked as Forward.
|
||||
But if we try finding neighbor by -V, -U direction, we call these neighbors are "backward" neighbors,
|
||||
and marked as VerticalBackward or HorizontalBackward by its direction.
|
||||
|
||||
The UV of Backward neighbor need to be processed specially so we need distinguish them with Forward neighbors.
|
||||
"""
|
||||
# +V, +U direction neighbor.
|
||||
Forward = enum.auto()
|
||||
# -V direction neighbor.
|
||||
VerticalBackward = enum.auto()
|
||||
# -U direction neighbor.
|
||||
HorizontalBackward = enum.auto()
|
||||
|
||||
class FlattenParam():
|
||||
mReferenceEdge: int
|
||||
mUseRefPoint: bool
|
||||
mFlattenMethod: FlattenMethod
|
||||
|
||||
mScaleSize: float
|
||||
|
||||
mReferencePoint: int
|
||||
mReferenceUV: float
|
||||
|
||||
def __init__(self, use_ref_point: bool, reference_edge: int, flatten_method: FlattenMethod) -> None:
|
||||
self.mReferenceEdge = reference_edge
|
||||
self.mUseRefPoint = use_ref_point
|
||||
self.mFlattenMethod = flatten_method
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
"""Check whether flatten params is valid"""
|
||||
if self.mUseRefPoint:
|
||||
# ref point should be great than 1.
|
||||
# because 0 and 1 is located at the same line with reference edge.
|
||||
return self.mReferencePoint > 1
|
||||
else:
|
||||
# zero scale size make no sense.
|
||||
return round(self.mScaleSize, 7) != 0.0
|
||||
|
||||
@classmethod
|
||||
def create_by_scale_size(cls, reference_edge: int, flatten_method: FlattenMethod, scale_num: float):
|
||||
val = cls(False, reference_edge, flatten_method)
|
||||
val.mScaleSize = scale_num
|
||||
return val
|
||||
|
||||
@classmethod
|
||||
def create_by_ref_point(cls, reference_edge: int, flatten_method: FlattenMethod, ref_point: int, ref_point_uv: float):
|
||||
val = cls(True, reference_edge, flatten_method)
|
||||
val.mReferencePoint = ref_point
|
||||
val.mReferenceUV = ref_point_uv
|
||||
return val
|
||||
|
||||
#endregion
|
||||
|
||||
class BBP_OT_flatten_uv(bpy.types.Operator):
|
||||
"""Flatten selected face UV. Only works for convex face"""
|
||||
bl_idname = "bbp.flatten_uv"
|
||||
bl_label = "Flatten UV"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
reference_edge: bpy.props.IntProperty(
|
||||
name = "Reference Edge",
|
||||
description = "The references edge of UV.\nIt will be placed in V axis.",
|
||||
min = 0,
|
||||
soft_min = 0, soft_max = 3,
|
||||
default = 0,
|
||||
) # type: ignore
|
||||
|
||||
flatten_method: bpy.props.EnumProperty(
|
||||
name = "Flatten Method",
|
||||
items = [
|
||||
('RAW', "Raw", "Legacy flatten UV."),
|
||||
('FLOOR', "Floor", "Floor specific flatten UV."),
|
||||
('WOOD', "Wood", "Wood specific flatten UV."),
|
||||
],
|
||||
default = 'RAW'
|
||||
) # type: ignore
|
||||
|
||||
scale_mode: bpy.props.EnumProperty(
|
||||
name = "Scale Mode",
|
||||
items = [
|
||||
('NUM', "Scale Size", "Scale UV with specific number."),
|
||||
('REF', "Ref. Point", "Scale UV with Reference Point feature."),
|
||||
],
|
||||
default = 'NUM'
|
||||
) # type: ignore
|
||||
|
||||
scale_number: bpy.props.FloatProperty(
|
||||
name = "Scale Size",
|
||||
description = "The size which will be applied for scale.",
|
||||
min = 0,
|
||||
soft_min = 0, soft_max = 5,
|
||||
default = 5.0,
|
||||
step = 10,
|
||||
precision = 1,
|
||||
) # type: ignore
|
||||
|
||||
reference_point: bpy.props.IntProperty(
|
||||
name = "Reference Point",
|
||||
description = "The references point of UV.\nIt's U component will be set to the number specified by Reference Point UV.\nThis point index is related to the start point of reference edge.",
|
||||
min = 2, # 0 and 1 is invalid. we can not order the reference edge to be set on the outside of uv axis
|
||||
soft_min = 2, soft_max = 3,
|
||||
default = 2,
|
||||
) # type: ignore
|
||||
|
||||
reference_uv: bpy.props.FloatProperty(
|
||||
name = "Reference Point UV",
|
||||
description = "The U component which should be applied to references point in UV.",
|
||||
soft_min = 0, soft_max = 1,
|
||||
default = 0.5,
|
||||
step = 10,
|
||||
precision = 2,
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = bpy.context.active_object
|
||||
if obj is None:
|
||||
return False
|
||||
if obj.type != 'MESH':
|
||||
return False
|
||||
if obj.mode != 'EDIT':
|
||||
return False
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
# construct scale data
|
||||
flatten_method_: FlattenMethod
|
||||
match(self.flatten_method):
|
||||
case 'RAW': flatten_method_ = FlattenMethod.Raw
|
||||
case 'FLOOR': flatten_method_ = FlattenMethod.Floor
|
||||
case 'WOOD': flatten_method_ = FlattenMethod.Wood
|
||||
case _: return {'CANCELLED'}
|
||||
|
||||
flatten_param_: FlattenParam
|
||||
if self.scale_mode == 'NUM':
|
||||
flatten_param_ = FlattenParam.create_by_scale_size(self.reference_edge, flatten_method_, self.scale_number)
|
||||
else:
|
||||
flatten_param_ = FlattenParam.create_by_ref_point(self.reference_edge, flatten_method_, self.reference_point, self.reference_uv)
|
||||
if not flatten_param_.is_valid():
|
||||
return {'CANCELLED'}
|
||||
|
||||
# do flatten uv and report
|
||||
failed: int = _flatten_uv_wrapper(bpy.context.active_object.data, flatten_param_)
|
||||
if failed != 0:
|
||||
print(f'[Flatten UV] {failed} faces are not be processed correctly because process failed.')
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.emboss = 'NORMAL'
|
||||
layout.label(text = "Flatten Method")
|
||||
sublayout = layout.row()
|
||||
sublayout.prop(self, "flatten_method", expand = True)
|
||||
layout.prop(self, "reference_edge")
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Scale Mode")
|
||||
sublayout = layout.row()
|
||||
sublayout.prop(self, "scale_mode", expand = True)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Scale Config")
|
||||
if self.scale_mode == 'NUM':
|
||||
layout.prop(self, "scale_number")
|
||||
else:
|
||||
layout.prop(self, "reference_point")
|
||||
layout.prop(self, "reference_uv")
|
||||
|
||||
#region BMesh Visitor Helper
|
||||
|
||||
def _set_face_vertex_uv(face: bmesh.types.BMFace, uv_layer: bmesh.types.BMLayerItem, idx: int, uv: UTIL_virtools_types.ConstVxVector2) -> None:
|
||||
"""
|
||||
Help function to set UV data for face.
|
||||
|
||||
@param face[in] The face to be set.
|
||||
@param uv_layer[in] The corresponding uv layer. Hint: it was gotten from BMesh.loops.layers.uv.verify()
|
||||
@param idx[in] The index of trying setting vertex.
|
||||
@param uv[in] The set UV data
|
||||
"""
|
||||
face.loops[idx][uv_layer].uv = uv
|
||||
|
||||
def _get_face_vertex_uv(face: bmesh.types.BMFace, uv_layer: bmesh.types.BMLayerItem, idx: int) -> UTIL_virtools_types.ConstVxVector2:
|
||||
"""
|
||||
Help function to get UV data for face.
|
||||
|
||||
@param face[in] The face to be set.
|
||||
@param uv_layer[in] The corresponding uv layer. Hint: it was gotten from BMesh.loops.layers.uv.verify()
|
||||
@param idx[in] The index of trying setting vertex.
|
||||
@return The UV data
|
||||
"""
|
||||
v: mathutils.Vector = face.loops[idx][uv_layer].uv
|
||||
return (v[0], v[1])
|
||||
|
||||
def _get_face_vertex_pos(face: bmesh.types.BMFace, idx: int) -> UTIL_virtools_types.ConstVxVector3:
|
||||
"""
|
||||
Help function to get vertex position from face by provided index.
|
||||
No index overflow checker. Caller must make sure the provided index is not overflow.
|
||||
|
||||
@param face[in] Bmesh face struct.
|
||||
@param idx[in] The index of trying getting vertex.
|
||||
@return The gotten vertex position.
|
||||
"""
|
||||
v: mathutils.Vector = face.loops[idx].vert.co
|
||||
return (v[0], v[1], v[2])
|
||||
|
||||
def _circular_clamp_index(v: int, vmax: int) -> int:
|
||||
"""
|
||||
Circular clamp face vertex index.
|
||||
Used by _real_flatten_uv.
|
||||
|
||||
@param v[in] The index to clamp
|
||||
@param vmax[in] The count of used face vertex. At least 3.
|
||||
@return The circular clamped value ranging from 0 to vmax.
|
||||
"""
|
||||
return v % vmax
|
||||
|
||||
#endregion
|
||||
|
||||
#region Real Worker Functions
|
||||
|
||||
def _flatten_uv_wrapper(mesh: bpy.types.Mesh, flatten_param: FlattenParam) -> int:
|
||||
# create bmesh modifier
|
||||
bm: bmesh.types.BMesh = bmesh.from_edit_mesh(mesh)
|
||||
# use verify() to make sure there is a uv layer to write data
|
||||
# verify() will return existing one or create one if no layer existing.
|
||||
uv_layers: bmesh.types.BMLayerCollection = bm.loops.layers.uv
|
||||
uv_layer: bmesh.types.BMLayerItem = uv_layers.verify()
|
||||
|
||||
# invoke core
|
||||
failed: int
|
||||
match(flatten_param.mFlattenMethod):
|
||||
case FlattenMethod.Raw:
|
||||
failed = _raw_flatten_uv(bm, uv_layer, flatten_param)
|
||||
case FlattenMethod.Floor | FlattenMethod.Wood:
|
||||
failed = _specific_flatten_uv(bm, uv_layer, flatten_param)
|
||||
|
||||
# show the updates in the viewport
|
||||
bmesh.update_edit_mesh(mesh)
|
||||
# return process result
|
||||
return failed
|
||||
|
||||
def _raw_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerItem, flatten_param: FlattenParam) -> int:
|
||||
# failed counter
|
||||
failed: int = 0
|
||||
# raw flatten uv always use zero offset
|
||||
c_ZeroOffset: mathutils.Vector = mathutils.Vector((0, 0))
|
||||
|
||||
# process each face
|
||||
face: bmesh.types.BMFace
|
||||
for face in bm.faces:
|
||||
# check requirement
|
||||
# skip not selected face
|
||||
if not face.select: continue
|
||||
# skip the face that not fufill reference edge requirement
|
||||
edge_count: int = len(face.loops)
|
||||
if flatten_param.mReferenceEdge >= edge_count:
|
||||
failed += 1
|
||||
continue
|
||||
# skip ref point overflow when using ref point mode
|
||||
if flatten_param.mUseRefPoint and (flatten_param.mReferencePoint >= edge_count):
|
||||
failed += 1
|
||||
continue
|
||||
|
||||
# process this face
|
||||
_flatten_face_uv(face, uv_layer, flatten_param, c_ZeroOffset)
|
||||
|
||||
return failed
|
||||
|
||||
def _specific_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerItem, flatten_param: FlattenParam) -> int:
|
||||
# failed counter
|
||||
failed: int = 0
|
||||
|
||||
# reset selected face's tag to False to indicate these face is not processed
|
||||
face: bmesh.types.BMFace
|
||||
for face in bm.faces:
|
||||
if face.select:
|
||||
face.tag = False
|
||||
|
||||
# prepare a function to check whether face is valid
|
||||
def face_validator(f: bmesh.types.BMFace) -> bool:
|
||||
# specify using external failed counter
|
||||
nonlocal failed
|
||||
# a valid face must be
|
||||
# selected, not processed, and should be rectangle
|
||||
# we check selection first
|
||||
# then check tag. if tag == True, it mean this face has been processed.
|
||||
if not f.select or f.tag: return False
|
||||
# now this face can be processed, we need check whether it is rectangle
|
||||
if len(f.loops) == 4:
|
||||
# yes it is rectangle
|
||||
return True
|
||||
else:
|
||||
# no, it is not rectangle
|
||||
# we need mark its tag as True to prevent any possible recursive checking
|
||||
# because it definately can not be processed in future.
|
||||
f.tag = True
|
||||
# then we report this face failed
|
||||
failed = failed + 1
|
||||
# return false
|
||||
return False
|
||||
# prepare face getter which will be used when stack is empty
|
||||
face_getter: typing.Iterator[bmesh.types.BMFace] = filter(
|
||||
lambda f: face_validator(f),
|
||||
typing.cast(typing.Iterable[bmesh.types.BMFace], bm.faces)
|
||||
)
|
||||
# prepare a neighbor getter.
|
||||
# this function will help finding the valid neighbor of specified face
|
||||
# `loop_idx` is the index of loop getting from given face.
|
||||
# `exp_loop_idx` is the expected index of neighbor loop in neighbor face.
|
||||
def face_neighbor_getter(f: bmesh.types.BMFace, loop_idx: int, exp_loop_idx: int) -> bmesh.types.BMFace | None:
|
||||
# get this face's loop
|
||||
this_loop: bmesh.types.BMLoop = f.loops[loop_idx]
|
||||
# check requirement for this loop
|
||||
# this edge should be shared exactly by 2 faces.
|
||||
#
|
||||
# Manifold: For a mesh to be manifold, every edge must have exactly two adjacent faces.
|
||||
# Ref: https://github.com/rlguy/Blender-FLIP-Fluids/wiki/Manifold-Meshes
|
||||
if not this_loop.edge.is_manifold:
|
||||
return None
|
||||
|
||||
# get neighbor loop
|
||||
neighbor_loop: bmesh.types.BMLoop = this_loop.link_loop_radial_next
|
||||
# get neighbor face and check it
|
||||
neighbor_f: bmesh.types.BMFace = neighbor_loop.face
|
||||
if not face_validator(neighbor_f):
|
||||
return None
|
||||
|
||||
# check expected neighbor index
|
||||
if neighbor_loop != neighbor_f.loops[exp_loop_idx]:
|
||||
return None
|
||||
|
||||
# all check done, return face
|
||||
return neighbor_f
|
||||
# prepare face stack.
|
||||
# NOTE: all face inserted into this stack should be marked as processed first.
|
||||
face_stack: collections.deque[tuple[bmesh.types.BMFace, mathutils.Vector, NeighborType]] = collections.deque()
|
||||
# start process faces
|
||||
while True:
|
||||
# if no item in face stack, pick one from face getter and mark it as processed
|
||||
# if face getter failed, it mean that no more face, exit.
|
||||
if len(face_stack) == 0:
|
||||
try:
|
||||
f = next(face_getter)
|
||||
f.tag = True
|
||||
face_stack.append((f, mathutils.Vector((0, 0)), NeighborType.Forward))
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
# pick one face from stack and process it
|
||||
(face, face_offset, face_backward) = face_stack.pop()
|
||||
_flatten_face_uv(face, uv_layer, flatten_param, face_offset)
|
||||
|
||||
# get 4 point uv because we need use them later
|
||||
# NOTE: 4 uv point following this order
|
||||
# +-----------+
|
||||
# |(1) |(2)
|
||||
# | |
|
||||
# |(0) |(3)
|
||||
# +-----------+
|
||||
# So the loop index is
|
||||
# (1)
|
||||
# +---------->+
|
||||
# ^ |
|
||||
# |(0) |(2)
|
||||
# | v
|
||||
# +<----------+
|
||||
# (3)
|
||||
ind0 = _circular_clamp_index(flatten_param.mReferenceEdge, 4)
|
||||
ind1 = _circular_clamp_index(flatten_param.mReferenceEdge + 1, 4)
|
||||
ind2 = _circular_clamp_index(flatten_param.mReferenceEdge + 2, 4)
|
||||
ind3 = _circular_clamp_index(flatten_param.mReferenceEdge + 3, 4)
|
||||
uv0 = _get_face_vertex_uv(face, uv_layer, ind0)
|
||||
uv1 = _get_face_vertex_uv(face, uv_layer, ind1)
|
||||
uv2 = _get_face_vertex_uv(face, uv_layer, ind2)
|
||||
uv3 = _get_face_vertex_uv(face, uv_layer, ind3)
|
||||
|
||||
# correct rectangle shape when in wood mode
|
||||
if flatten_param.mFlattenMethod == FlattenMethod.Wood:
|
||||
# make its uv geometry to rectangle from a trapezium.
|
||||
# get the average U factor from its right edge.
|
||||
# and make top + bottom uv edge be parallel with U axis by using left edge V factor.
|
||||
average_u = (uv2[0] + uv3[0]) / 2
|
||||
uv2 = (average_u, uv1[1])
|
||||
uv3 = (average_u, uv0[1])
|
||||
_set_face_vertex_uv(face, uv_layer, ind2, uv2)
|
||||
_set_face_vertex_uv(face, uv_layer, ind3, uv3)
|
||||
|
||||
# do backward correction
|
||||
# in backward mode, we can not know how many space backward one will occupied,
|
||||
# thus we can not pass it by offset because we don't know the offset,
|
||||
# so we only can patch it after computing its real size.
|
||||
if face_backward != NeighborType.Forward:
|
||||
if face_backward == NeighborType.VerticalBackward:
|
||||
# in vertical backward patch,
|
||||
# minus self height for all uv.
|
||||
self_height: float = uv1[1] - uv0[1]
|
||||
uv0 = (uv0[0], uv0[1] - self_height)
|
||||
uv1 = (uv1[0], uv1[1] - self_height)
|
||||
uv2 = (uv2[0], uv2[1] - self_height)
|
||||
uv3 = (uv3[0], uv3[1] - self_height)
|
||||
if face_backward == NeighborType.HorizontalBackward:
|
||||
# in horizontal backward patch, minus self width for all uv.
|
||||
# because we have process rectangle shape issue before this,
|
||||
# so we can pick uv2 or uv3 to get width directly.
|
||||
self_width: float = uv3[0] - uv0[0]
|
||||
uv0 = (uv0[0] - self_width, uv0[1])
|
||||
uv1 = (uv1[0] - self_width, uv1[1])
|
||||
uv2 = (uv2[0] - self_width, uv2[1])
|
||||
uv3 = (uv3[0] - self_width, uv3[1])
|
||||
# set modified uv to geometry
|
||||
_set_face_vertex_uv(face, uv_layer, ind0, uv0)
|
||||
_set_face_vertex_uv(face, uv_layer, ind1, uv1)
|
||||
_set_face_vertex_uv(face, uv_layer, ind2, uv2)
|
||||
_set_face_vertex_uv(face, uv_layer, ind3, uv3)
|
||||
|
||||
# insert horizontal neighbor only in wood mode.
|
||||
if flatten_param.mFlattenMethod == FlattenMethod.Wood:
|
||||
# insert right neighbor (forward)
|
||||
r_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind2, ind0)
|
||||
if r_face is not None:
|
||||
# mark it as processed
|
||||
r_face.tag = True
|
||||
# insert face with extra horizontal offset.
|
||||
face_stack.append((r_face, mathutils.Vector((uv3[0], uv3[1])), NeighborType.Forward))
|
||||
# insert left neighbor (backward)
|
||||
# swap the index param of neighbor getter
|
||||
l_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind0, ind2)
|
||||
if l_face is not None:
|
||||
l_face.tag = True
|
||||
# pass origin pos, and order backward correction
|
||||
face_stack.append((l_face, mathutils.Vector((uv0[0], uv0[1])), NeighborType.HorizontalBackward))
|
||||
|
||||
# insert vertical neighbor
|
||||
# insert top neighbor (forward)
|
||||
t_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind1, ind3)
|
||||
if t_face is not None:
|
||||
# mark it as processed
|
||||
t_face.tag = True
|
||||
# insert face with extra vertical offset.
|
||||
face_stack.append((t_face, mathutils.Vector((uv1[0], uv1[1])), NeighborType.Forward))
|
||||
# insert bottom neighbor (backward)
|
||||
# swap the index param of neighbor getter
|
||||
b_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind3, ind1)
|
||||
if b_face is not None:
|
||||
b_face.tag = True
|
||||
# pass origin pos, and order backward correction
|
||||
face_stack.append((b_face, mathutils.Vector((uv0[0], uv0[1])), NeighborType.VerticalBackward))
|
||||
|
||||
return failed
|
||||
|
||||
def _flatten_face_uv(face: bmesh.types.BMFace, uv_layer: bmesh.types.BMLayerItem, flatten_param: FlattenParam, offset: mathutils.Vector) -> None:
|
||||
# ========== get correct new corrdinate system ==========
|
||||
# yyc mark:
|
||||
# we use 3 points located in this face to calc
|
||||
# the base of this local uv corredinate system.
|
||||
# however if this 3 points are set in a line,
|
||||
# this method will cause a error, zero vector error.
|
||||
#
|
||||
# if z axis is zero vector, we will try using face normal instead
|
||||
# to try getting correct data.
|
||||
#
|
||||
# zero base is not important. because it will not raise any math exception
|
||||
# just a weird uv. user will notice this problem.
|
||||
|
||||
# get point
|
||||
all_point: int = len(face.loops)
|
||||
pidx_start: int = _circular_clamp_index(flatten_param.mReferenceEdge, all_point)
|
||||
p1: mathutils.Vector = mathutils.Vector(_get_face_vertex_pos(face, pidx_start))
|
||||
p2: mathutils.Vector = mathutils.Vector(_get_face_vertex_pos(face, _circular_clamp_index(flatten_param.mReferenceEdge + 1, all_point)))
|
||||
p3: mathutils.Vector = mathutils.Vector(_get_face_vertex_pos(face, _circular_clamp_index(flatten_param.mReferenceEdge + 2, all_point)))
|
||||
|
||||
# get y axis
|
||||
new_y_axis: mathutils.Vector = p2 - p1
|
||||
new_y_axis.normalize()
|
||||
vec1: mathutils.Vector = p3 - p2
|
||||
vec1.normalize()
|
||||
|
||||
# get z axis
|
||||
new_z_axis: mathutils.Vector = vec1.cross(new_y_axis)
|
||||
new_z_axis.normalize()
|
||||
# if z is a zero vector, use face normal instead
|
||||
# please note we need use inverted face normal.
|
||||
if not any(round(v, 7) for v in new_z_axis):
|
||||
new_z_axis = typing.cast(mathutils.Vector, face.normal).normalized()
|
||||
new_z_axis.negate()
|
||||
|
||||
# get x axis
|
||||
new_x_axis: mathutils.Vector = new_y_axis.cross(new_z_axis)
|
||||
new_x_axis.normalize()
|
||||
|
||||
# construct rebase matrix
|
||||
origin_base: mathutils.Matrix = mathutils.Matrix((
|
||||
(1.0, 0, 0),
|
||||
(0, 1.0, 0),
|
||||
(0, 0, 1.0)
|
||||
))
|
||||
origin_base.invert_safe()
|
||||
new_base: mathutils.Matrix = mathutils.Matrix((
|
||||
(new_x_axis.x, new_y_axis.x, new_z_axis.x),
|
||||
(new_x_axis.y, new_y_axis.y, new_z_axis.y),
|
||||
(new_x_axis.z, new_y_axis.z, new_z_axis.z)
|
||||
))
|
||||
transition_matrix: mathutils.Matrix = typing.cast(mathutils.Matrix, origin_base @ new_base)
|
||||
transition_matrix.invert_safe()
|
||||
|
||||
# ===== rescale correction =====
|
||||
rescale: float = 0.0
|
||||
if flatten_param.mUseRefPoint:
|
||||
# ref point method
|
||||
# get reference point from loop
|
||||
pidx_refp: int = _circular_clamp_index(pidx_start + flatten_param.mReferencePoint, all_point)
|
||||
pref: mathutils.Vector = mathutils.Vector(_get_face_vertex_pos(face, pidx_refp)) - p1
|
||||
|
||||
# calc its U component
|
||||
vec_u: float = abs(typing.cast(mathutils.Vector, transition_matrix @ pref).x)
|
||||
if round(vec_u, 7) == 0.0:
|
||||
rescale = 1.0 # fallback. rescale = 1 will not affect anything
|
||||
else:
|
||||
rescale = flatten_param.mReferenceUV / vec_u
|
||||
else:
|
||||
# scale size method
|
||||
# apply rescale directly
|
||||
rescale = 1.0 / flatten_param.mScaleSize
|
||||
|
||||
# construct matrix
|
||||
# we only rescale U component (X component)
|
||||
# and constant 5.0 scale for V component (Y component)
|
||||
scale_matrix: mathutils.Matrix = mathutils.Matrix((
|
||||
(rescale, 0, 0),
|
||||
(0, 1.0 / 5.0, 0),
|
||||
(0, 0, 1.0)
|
||||
))
|
||||
# order can not be changed. we order do transition first, then scale it.
|
||||
rescale_transition_matrix: mathutils.Matrix = typing.cast(mathutils.Matrix, scale_matrix @ transition_matrix)
|
||||
|
||||
# ========== process each face ==========
|
||||
for idx in range(all_point):
|
||||
# compute uv
|
||||
pp: mathutils.Vector = mathutils.Vector(_get_face_vertex_pos(face, idx)) - p1
|
||||
ppuv: mathutils.Vector = typing.cast(mathutils.Vector, rescale_transition_matrix @ pp)
|
||||
# u and v component has been calculated properly. no extra process needed.
|
||||
# just get abs for the u component
|
||||
ppuv.x = abs(ppuv.x)
|
||||
# add offset and assign to uv
|
||||
_set_face_vertex_uv(face, uv_layer, idx, (ppuv.x + offset.x, ppuv.y + offset.y))
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_flatten_uv)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_flatten_uv)
|
188
bbp_ng/OP_UV_rail_uv.py
Normal file
188
bbp_ng/OP_UV_rail_uv.py
Normal file
@ -0,0 +1,188 @@
|
||||
import bpy, bmesh, mathutils
|
||||
import typing
|
||||
from . import PROP_ptrprop_resolver
|
||||
from . import UTIL_virtools_types, UTIL_icons_manager, UTIL_functions
|
||||
|
||||
class BBP_OT_rail_uv(bpy.types.Operator):
|
||||
"""Create UV for Rail as Ballance Showen (TT_ReflectionMapping)"""
|
||||
bl_idname = "bbp.rail_uv"
|
||||
bl_label = "Rail UV"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return _check_rail_target()
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm: bpy.types.WindowManager = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
# check material
|
||||
mtl: bpy.types.Material = PROP_ptrprop_resolver.get_rail_uv_material()
|
||||
if mtl is None:
|
||||
UTIL_functions.message_box(
|
||||
("No specific material", ),
|
||||
"Lost Parameter",
|
||||
UTIL_icons_manager.BlenderPresetIcons.Error.value
|
||||
)
|
||||
return {'CANCELLED'}
|
||||
|
||||
# apply rail uv
|
||||
_create_rail_uv(_get_rail_target(), mtl)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
PROP_ptrprop_resolver.draw_rail_uv_material(layout)
|
||||
|
||||
#region Real Worker Functions
|
||||
|
||||
def _check_rail_target() -> bool:
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj.type != 'MESH':
|
||||
continue
|
||||
if obj.mode != 'OBJECT':
|
||||
continue
|
||||
if obj.data is None:
|
||||
continue
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_rail_target() -> typing.Iterable[bpy.types.Mesh]:
|
||||
# collect objects
|
||||
meshes: list[bpy.types.Mesh] = []
|
||||
error_objname: list[str] = []
|
||||
for obj in bpy.context.selected_objects:
|
||||
if obj.type != 'MESH':
|
||||
error_objname.append(obj.name)
|
||||
continue
|
||||
if obj.mode != 'OBJECT':
|
||||
error_objname.append(obj.name)
|
||||
continue
|
||||
if obj.data is None:
|
||||
error_objname.append(obj.name)
|
||||
continue
|
||||
|
||||
meshes.append(obj.data)
|
||||
|
||||
# display warning window if necessary
|
||||
if len(error_objname) != 0:
|
||||
# show dialog
|
||||
UTIL_functions.message_box(
|
||||
("Some objects is not processed, see Console for more infos.", ),
|
||||
"Object Type Warning",
|
||||
UTIL_icons_manager.BlenderPresetIcons.Warning.value
|
||||
)
|
||||
|
||||
# output to console
|
||||
print('')
|
||||
print('=====')
|
||||
print('Following objects are not processed by Rail UV because they do not meet the requirements of Rail UV.')
|
||||
for objname in error_objname:
|
||||
print(objname)
|
||||
print('=====')
|
||||
print('')
|
||||
|
||||
# return valid
|
||||
return meshes
|
||||
|
||||
def _tt_reflection_mapping_compute(
|
||||
point_: UTIL_virtools_types.ConstVxVector3,
|
||||
nml_: UTIL_virtools_types.ConstVxVector3,
|
||||
refobj_: UTIL_virtools_types.ConstVxVector3) -> UTIL_virtools_types.ConstVxVector2:
|
||||
# switch blender coord to virtools coord for convenient calc
|
||||
point: mathutils.Vector = mathutils.Vector((point_[0], point_[2], point_[1]))
|
||||
nml: mathutils.Vector = mathutils.Vector((nml_[0], nml_[2], nml_[1])).normalized()
|
||||
refobj: mathutils.Vector = mathutils.Vector((refobj_[0], refobj_[2], refobj_[1]))
|
||||
|
||||
p: mathutils.Vector = (refobj - point).normalized()
|
||||
b: mathutils.Vector = (((2 * (p * nml)) * nml) - p)
|
||||
b.normalize()
|
||||
|
||||
# convert back to blender coord
|
||||
return ((b.x + 1.0) / 2.0, -(b.z + 1.0) / 2.0)
|
||||
|
||||
|
||||
def _set_face_vertex_uv(face: bpy.types.MeshPolygon, uv_layer: bpy.types.MeshUVLoopLayer, idx: int, uv: UTIL_virtools_types.ConstVxVector2) -> None:
|
||||
"""
|
||||
Help function to set face vertex uv by index.
|
||||
|
||||
@param face[in] The face to be set.
|
||||
@param uv_layer[in] The uv layer to be set gotten from `Mesh.uv_layers.active`
|
||||
@param idx[in] The index related to face to set uv.
|
||||
@param uv[in] The uv data.
|
||||
"""
|
||||
uv_layer.uv[face.loop_start + idx].vector = uv
|
||||
|
||||
def _get_face_vertex_pos(face: bpy.types.MeshPolygon, loops: bpy.types.MeshLoops, vecs: bpy.types.MeshVertices, idx: int) -> UTIL_virtools_types.ConstVxVector3:
|
||||
"""
|
||||
Help function. Get face referenced vertex position data by index
|
||||
|
||||
@param face[in] The face to be set.
|
||||
@param loops[in] Mesh loops gotten from `Mesh.loops`
|
||||
@param vecs[in] Mesh vertices gotten from `Mesh.vertices`
|
||||
@param idx[in] The index related to face to get position.
|
||||
"""
|
||||
v: mathutils.Vector = vecs[loops[face.loop_start + idx].vertex_index].co
|
||||
return (v[0], v[1], v[2])
|
||||
|
||||
def _get_face_vertex_nml(face: bpy.types.MeshPolygon, loops: bpy.types.MeshLoops, idx: int) -> UTIL_virtools_types.ConstVxVector3:
|
||||
"""
|
||||
Help function to get face vertex normal.
|
||||
|
||||
Similar to _get_face_vertex_pos, just get normal, not position.
|
||||
|
||||
@param face[in] The face to be set.
|
||||
@param loops[in] Mesh loops gotten from `Mesh.loops`
|
||||
@param idx[in] The index related to face to get normal.
|
||||
"""
|
||||
v: mathutils.Vector = loops[face.loop_start + idx].normal
|
||||
return (v[0], v[1], v[2])
|
||||
|
||||
def _get_face_vertex_count(face: bpy.types.MeshPolygon) -> int:
|
||||
"""
|
||||
Help function to get how many vertex used by this face.
|
||||
|
||||
@return The count of used vertex. At least 3.
|
||||
"""
|
||||
return face.loop_total
|
||||
|
||||
def _create_rail_uv(meshes: typing.Iterable[bpy.types.Mesh], mtl: bpy.types.Material):
|
||||
for mesh in meshes:
|
||||
# clean it material and set rail first
|
||||
mesh.materials.clear()
|
||||
mesh.materials.append(mtl)
|
||||
# and validate face mtl idx ref
|
||||
mesh.validate_material_indices()
|
||||
|
||||
# get uv and make sure at least one uv
|
||||
if mesh.uv_layers.active is None:
|
||||
mesh.uv_layers.new(do_init = False)
|
||||
uv_layer: bpy.types.MeshUVLoopLayer = mesh.uv_layers.active
|
||||
# get other useful data
|
||||
loops: bpy.types.MeshLoops = mesh.loops
|
||||
vecs: bpy.types.MeshVertices = mesh.vertices
|
||||
|
||||
refobj: UTIL_virtools_types.ConstVxVector3 = (0.0, 0.0, 0.0)
|
||||
for face in mesh.polygons:
|
||||
for idx in range(_get_face_vertex_count(face)):
|
||||
_set_face_vertex_uv(
|
||||
face,
|
||||
uv_layer,
|
||||
idx,
|
||||
_tt_reflection_mapping_compute(
|
||||
_get_face_vertex_pos(face, loops, vecs, idx),
|
||||
_get_face_vertex_nml(face, loops, idx),
|
||||
refobj
|
||||
)
|
||||
)
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_rail_uv)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_rail_uv)
|
||||
|
415
bbp_ng/PROP_ballance_element.py
Normal file
415
bbp_ng/PROP_ballance_element.py
Normal file
@ -0,0 +1,415 @@
|
||||
import bpy
|
||||
import os, typing, enum, array
|
||||
from . import PROP_virtools_mesh
|
||||
from . import UTIL_functions, UTIL_file_io, UTIL_blender_mesh, UTIL_virtools_types, UTIL_icons_manager
|
||||
|
||||
#region Raw Elements Operations
|
||||
|
||||
class BallanceElementType(enum.IntEnum):
|
||||
P_Extra_Life = 0
|
||||
P_Extra_Point = 1
|
||||
P_Trafo_Paper = 2
|
||||
P_Trafo_Stone = 3
|
||||
P_Trafo_Wood = 4
|
||||
P_Ball_Paper = 5
|
||||
P_Ball_Stone = 6
|
||||
P_Ball_Wood = 7
|
||||
P_Box = 8
|
||||
P_Dome = 9
|
||||
P_Modul_01 = 10
|
||||
P_Modul_03 = 11
|
||||
P_Modul_08 = 12
|
||||
P_Modul_17 = 13
|
||||
P_Modul_18 = 14
|
||||
P_Modul_19 = 15
|
||||
P_Modul_25 = 16
|
||||
P_Modul_26 = 17
|
||||
P_Modul_29 = 18
|
||||
P_Modul_30 = 19
|
||||
P_Modul_34 = 20
|
||||
P_Modul_37 = 21
|
||||
P_Modul_41 = 22
|
||||
PC_TwoFlames = 23
|
||||
PE_Balloon = 24
|
||||
PR_Resetpoint = 25
|
||||
PS_FourFlames = 26
|
||||
|
||||
_g_ElementCount: int = len(BallanceElementType)
|
||||
|
||||
def get_ballance_element_type_from_id(id: int) -> BallanceElementType | None:
|
||||
"""
|
||||
Get Ballance element type by its id.
|
||||
|
||||
@param id[in] The id of element
|
||||
@return the type of this Ballance element name distributed by this plugin. or None if providing id is invalid.
|
||||
"""
|
||||
try:
|
||||
return BallanceElementType(id) # https://docs.python.org/zh-cn/3/library/enum.html#enum.EnumType.__call__
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
def get_ballance_element_type_from_name(name: str) -> BallanceElementType | None:
|
||||
"""
|
||||
Get Ballance element type by its name.
|
||||
|
||||
@param name[in] The name of element
|
||||
@return the type of this Ballance element name distributed by this plugin. or None if providing name is invalid.
|
||||
"""
|
||||
try:
|
||||
return BallanceElementType[name] # https://docs.python.org/zh-cn/3/library/enum.html#enum.EnumType.__getitem__
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
def get_ballance_element_id(ty: BallanceElementType) -> int:
|
||||
"""
|
||||
Get Ballance element id by its type
|
||||
|
||||
@param ty[in] The type of element
|
||||
@return the id of this Ballance element.
|
||||
"""
|
||||
return ty.value
|
||||
|
||||
def get_ballance_element_name(ty: BallanceElementType) -> str:
|
||||
"""
|
||||
Get Ballance element name by its type
|
||||
|
||||
@param ty[in] The type of element
|
||||
@return the name of this Ballance element.
|
||||
"""
|
||||
return ty.name
|
||||
|
||||
def is_ballance_element(name: str) -> bool:
|
||||
"""
|
||||
Check whether providing name is Ballance element.
|
||||
|
||||
Just a wrapper of get_ballance_element_id
|
||||
|
||||
@param name[in] The name of element
|
||||
@return True if providing name is Ballance element name.
|
||||
"""
|
||||
return get_ballance_element_type_from_name(name) is not None
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ballance Elements Define & Visitor
|
||||
|
||||
class BBP_PG_ballance_element(bpy.types.PropertyGroup):
|
||||
element_id: bpy.props.IntProperty(
|
||||
name = "Element Id",
|
||||
default = 0
|
||||
)
|
||||
|
||||
mesh_ptr: bpy.props.PointerProperty(
|
||||
name = "Mesh",
|
||||
type = bpy.types.Mesh
|
||||
)
|
||||
|
||||
def get_ballance_elements(scene: bpy.types.Scene) -> bpy.types.CollectionProperty:
|
||||
return scene.ballance_elements
|
||||
|
||||
#endregion
|
||||
|
||||
#region Element Loader
|
||||
|
||||
def _save_element(mesh: bpy.types.Mesh, filename: str) -> None:
|
||||
# todo: if we need add element placeholder save operator,
|
||||
# write this function and call this function in operator.
|
||||
pass
|
||||
|
||||
def _load_element(mesh: bpy.types.Mesh, element_type: BallanceElementType) -> None:
|
||||
# resolve mesh path
|
||||
element_name: str = get_ballance_element_name(element_type)
|
||||
element_filename: str = os.path.join(
|
||||
os.path.dirname(__file__),
|
||||
"meshes",
|
||||
element_name + '.bin'
|
||||
)
|
||||
|
||||
# open file and read
|
||||
with open(element_filename, 'rb') as fmesh:
|
||||
# prepare container
|
||||
vpos: array.array = array.array('f')
|
||||
vnml: array.array = array.array('f')
|
||||
face: array.array = array.array('L')
|
||||
|
||||
# read data
|
||||
# position is vector3
|
||||
vpos_count = UTIL_file_io.read_uint32(fmesh)
|
||||
vpos.extend(UTIL_file_io.read_float_array(fmesh, vpos_count * 3))
|
||||
# normal is vector3
|
||||
vnml_count = UTIL_file_io.read_uint32(fmesh)
|
||||
vnml.extend(UTIL_file_io.read_float_array(fmesh, vnml_count * 3))
|
||||
# each face use 6 uint32 to describe,
|
||||
# they are: pos1, nml1, pos2, nml2, pos3, nml3.
|
||||
# each item is a 0 based index refering to corresponding list
|
||||
face_count = UTIL_file_io.read_uint32(fmesh)
|
||||
face.extend(UTIL_file_io.read_uint32_array(fmesh, face_count * 6))
|
||||
|
||||
# open mesh writer and write data
|
||||
with UTIL_blender_mesh.MeshWriter(mesh) as writer:
|
||||
# prepare writer essential function
|
||||
mesh_part: UTIL_blender_mesh.MeshWriterIngredient = UTIL_blender_mesh.MeshWriterIngredient()
|
||||
def vpos_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||
v: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
||||
for i in range(vpos_count):
|
||||
idx: int = i * 3
|
||||
v.x = vpos[idx]
|
||||
v.y = vpos[idx + 1]
|
||||
v.z = vpos[idx + 2]
|
||||
# conv co
|
||||
UTIL_virtools_types.vxvector3_conv_co(v)
|
||||
yield v
|
||||
mesh_part.mVertexPosition = vpos_iterator()
|
||||
def vnml_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||
v: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
||||
for i in range(vnml_count):
|
||||
idx: int = i * 3
|
||||
v.x = vnml[idx]
|
||||
v.y = vnml[idx + 1]
|
||||
v.z = vnml[idx + 2]
|
||||
# conv co
|
||||
UTIL_virtools_types.vxvector3_conv_co(v)
|
||||
yield v
|
||||
mesh_part.mVertexNormal = vnml_iterator()
|
||||
def vuv_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector2]:
|
||||
# no uv, no need to conv co
|
||||
v: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2()
|
||||
yield v
|
||||
mesh_part.mVertexUV = vuv_iterator()
|
||||
mesh_part.mMaterial = iter(tuple())
|
||||
def face_iterator() -> typing.Iterator[UTIL_blender_mesh.FaceData]:
|
||||
# create face data with 3 placeholder
|
||||
f: UTIL_blender_mesh.FaceData = UTIL_blender_mesh.FaceData(
|
||||
[UTIL_blender_mesh.FaceVertexData() for i in range(3)]
|
||||
)
|
||||
for i in range(face_count):
|
||||
idx: int = i * 6
|
||||
f.mIndices[0].mPosIdx = face[idx]
|
||||
f.mIndices[0].mNmlIdx = face[idx + 1]
|
||||
f.mIndices[1].mPosIdx = face[idx + 2]
|
||||
f.mIndices[1].mNmlIdx = face[idx + 3]
|
||||
f.mIndices[2].mPosIdx = face[idx + 4]
|
||||
f.mIndices[2].mNmlIdx = face[idx + 5]
|
||||
# conv co
|
||||
f.conv_co()
|
||||
yield f
|
||||
mesh_part.mFace = face_iterator()
|
||||
|
||||
writer.add_ingredient(mesh_part)
|
||||
|
||||
# end of with writer
|
||||
# write mesh data
|
||||
|
||||
# set other mesh settings
|
||||
# generated mesh always use lit mode.
|
||||
mesh_settings: PROP_virtools_mesh.RawVirtoolsMesh = PROP_virtools_mesh.RawVirtoolsMesh()
|
||||
mesh_settings.mLitMode = UTIL_virtools_types.VXMESH_LITMODE.VX_LITMESH
|
||||
PROP_virtools_mesh.set_raw_virtools_mesh(mesh, mesh_settings)
|
||||
|
||||
# end of with fmesh
|
||||
# close file
|
||||
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ballance Elements Operation Help Class & Functions
|
||||
|
||||
class BallanceElementsHelper():
|
||||
"""
|
||||
The helper of Ballance elements processing.
|
||||
|
||||
All element operations, including getting or setting, must be manipulated by this class.
|
||||
You should NOT operate Ballance Elements property (in Scene) directly.
|
||||
|
||||
This class should only have 1 instance at the same time. This class support `with` syntax to achieve this.
|
||||
This class frequently used in importing stage to create element placeholder.
|
||||
"""
|
||||
__mSingletonMutex: typing.ClassVar[bool] = False
|
||||
__mIsValid: bool
|
||||
__mAssocScene: bpy.types.Scene
|
||||
__mElementMap: dict[BallanceElementType, bpy.types.Mesh]
|
||||
|
||||
def __init__(self, assoc: bpy.types.Scene):
|
||||
self.__mElementMap = {}
|
||||
self.__mAssocScene = assoc
|
||||
|
||||
# check singleton
|
||||
if BallanceElementsHelper.__mSingletonMutex:
|
||||
self.__mIsValid = False
|
||||
raise UTIL_functions.BBPException('BallanceElementsHelper is mutex.')
|
||||
|
||||
# set validation and read ballance elements property
|
||||
BallanceElementsHelper.__mSingletonMutex = True
|
||||
self.__mIsValid = True
|
||||
self.__read_from_ballance_element()
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self.__mIsValid
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.dispose()
|
||||
|
||||
def dispose(self) -> None:
|
||||
if self.is_valid():
|
||||
# write to ballance elements property and reset validation
|
||||
self.__write_to_ballance_elements()
|
||||
self.__mIsValid = False
|
||||
BallanceElementsHelper.__mSingletonMutex = False
|
||||
|
||||
def get_element(self, element_type: BallanceElementType) -> bpy.types.Mesh:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('calling invalid BallanceElementsHelper')
|
||||
|
||||
# get exist one
|
||||
mesh: bpy.types.Mesh | None = self.__mElementMap.get(element_type, None)
|
||||
if mesh is not None:
|
||||
return mesh
|
||||
|
||||
# if no existing one, create new one
|
||||
new_mesh_name: str = get_ballance_element_name(element_type)
|
||||
new_mesh: bpy.types.Mesh = bpy.data.meshes.new(new_mesh_name)
|
||||
|
||||
_load_element(new_mesh, element_type)
|
||||
self.__mElementMap[element_type] = new_mesh
|
||||
return new_mesh
|
||||
|
||||
def __write_to_ballance_elements(self) -> None:
|
||||
elements: bpy.types.CollectionProperty = get_ballance_elements(self.__mAssocScene)
|
||||
elements.clear()
|
||||
|
||||
for elety, elemesh in self.__mElementMap.items():
|
||||
item: BBP_PG_ballance_element = elements.add()
|
||||
item.element_id = get_ballance_element_id(elety)
|
||||
item.mesh_ptr = elemesh
|
||||
|
||||
def __read_from_ballance_element(self) -> None:
|
||||
elements: bpy.types.CollectionProperty = get_ballance_elements(self.__mAssocScene)
|
||||
self.__mElementMap.clear()
|
||||
|
||||
item: BBP_PG_ballance_element
|
||||
for item in elements:
|
||||
# check requirements
|
||||
if item.mesh_ptr is None: continue
|
||||
element_type: BallanceElementType | None = get_ballance_element_type_from_id(item.element_id)
|
||||
if element_type is None: continue
|
||||
|
||||
# add into map
|
||||
self.__mElementMap[element_type] = item.mesh_ptr
|
||||
|
||||
def reset_ballance_elements(scene: bpy.types.Scene) -> None:
|
||||
invalid_idx: list[int] = []
|
||||
elements: bpy.types.CollectionProperty = get_ballance_elements(scene)
|
||||
|
||||
# re-load all elements
|
||||
index: int = 0
|
||||
item: BBP_PG_ballance_element
|
||||
for item in elements:
|
||||
elety: BallanceElementType | None = get_ballance_element_type_from_id(item.element_id)
|
||||
|
||||
# load or record invalid entry
|
||||
if elety is None or item.mesh_ptr is None:
|
||||
invalid_idx.append(index)
|
||||
else:
|
||||
_load_element(item.mesh_ptr, elety)
|
||||
|
||||
# inc counter
|
||||
index += 1
|
||||
|
||||
# remove invalid one with reversed order
|
||||
invalid_idx.reverse()
|
||||
for idx in invalid_idx:
|
||||
elements.remove(idx)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ballance Elements Representation
|
||||
|
||||
class BBP_UL_ballance_elements(bpy.types.UIList):
|
||||
def draw_item(self, context, layout: bpy.types.UILayout, data, item: BBP_PG_ballance_element, icon, active_data, active_propname):
|
||||
# check requirements
|
||||
elety: BallanceElementType | None = get_ballance_element_type_from_id(item.element_id)
|
||||
if elety is None or item.mesh_ptr is None: return
|
||||
|
||||
# draw list item
|
||||
layout.label(text = get_ballance_element_name(elety), translate = False)
|
||||
layout.label(text = item.mesh_ptr.name, translate = False, icon = 'MESH_DATA')
|
||||
|
||||
class BBP_OT_reset_ballance_elements(bpy.types.Operator):
|
||||
"""Reset all Meshes of Loaded Ballance Elements to Original Geometry."""
|
||||
bl_idname = "bbp.reset_ballance_elements"
|
||||
bl_label = "Reset Ballance Elements"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
reset_ballance_elements(context.scene)
|
||||
# show a window to let user know, not silence
|
||||
UTIL_functions.message_box(
|
||||
('Reset OK.', ),
|
||||
"Reset Result",
|
||||
UTIL_icons_manager.BlenderPresetIcons.Info.value
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_PT_ballance_elements(bpy.types.Panel):
|
||||
"""Show Ballance Elements Properties."""
|
||||
bl_label = "Ballance Elements"
|
||||
bl_idname = "BBP_PT_ballance_elements"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene is not None
|
||||
|
||||
def draw(self, context):
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
target: bpy.types.Scene = context.scene
|
||||
col = layout.column()
|
||||
|
||||
# show restore operator
|
||||
opercol = col.column()
|
||||
opercol.operator(BBP_OT_reset_ballance_elements.bl_idname, icon='LOOP_BACK')
|
||||
|
||||
# show list but not allowed to edit
|
||||
listcol = col.column()
|
||||
listcol.enabled = False
|
||||
listcol.template_list(
|
||||
"BBP_UL_ballance_elements", "",
|
||||
target, "ballance_elements",
|
||||
target, "active_ballance_elements",
|
||||
# default row height is a half of the count of all elements
|
||||
# limit the max row height to the the count of all elements
|
||||
rows = _g_ElementCount // 2,
|
||||
maxrows = _g_ElementCount,
|
||||
)
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
# register all classes
|
||||
bpy.utils.register_class(BBP_PG_ballance_element)
|
||||
bpy.utils.register_class(BBP_UL_ballance_elements)
|
||||
bpy.utils.register_class(BBP_OT_reset_ballance_elements)
|
||||
bpy.utils.register_class(BBP_PT_ballance_elements)
|
||||
|
||||
# add into scene metadata
|
||||
bpy.types.Scene.ballance_elements = bpy.props.CollectionProperty(type = BBP_PG_ballance_element)
|
||||
bpy.types.Scene.active_ballance_elements = bpy.props.IntProperty()
|
||||
|
||||
def unregister() -> None:
|
||||
# del from scene metadata
|
||||
del bpy.types.Scene.active_ballance_elements
|
||||
del bpy.types.Scene.ballance_elements
|
||||
|
||||
bpy.utils.unregister_class(BBP_PT_ballance_elements)
|
||||
bpy.utils.unregister_class(BBP_OT_reset_ballance_elements)
|
||||
bpy.utils.unregister_class(BBP_UL_ballance_elements)
|
||||
bpy.utils.unregister_class(BBP_PG_ballance_element)
|
81
bbp_ng/PROP_ballance_map_info.py
Normal file
81
bbp_ng/PROP_ballance_map_info.py
Normal file
@ -0,0 +1,81 @@
|
||||
import bpy
|
||||
import typing
|
||||
from . import UTIL_functions
|
||||
|
||||
class RawBallanceMapInfo():
|
||||
cSectorCount: typing.ClassVar[int] = 1
|
||||
|
||||
mSectorCount: int
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.mSectorCount = kwargs.get("mSectorCount", RawBallanceMapInfo.cSectorCount)
|
||||
|
||||
def regulate(self):
|
||||
self.mSectorCount = UTIL_functions.clamp_int(self.mSectorCount, 1, 999)
|
||||
|
||||
#region Prop Decl & Getter Setter
|
||||
|
||||
class BBP_PG_ballance_map_info(bpy.types.PropertyGroup):
|
||||
sector_count: bpy.props.IntProperty(
|
||||
name = "Sector",
|
||||
description = "The sector count of this Ballance map which is used in exporting map and may be changed when importing map.",
|
||||
default = 1,
|
||||
max = 999, min = 1,
|
||||
soft_max = 8, soft_min = 1,
|
||||
step = 1
|
||||
) # type: ignore
|
||||
|
||||
def get_ballance_map_info(scene: bpy.types.Scene) -> BBP_PG_ballance_map_info:
|
||||
return scene.ballance_map_info
|
||||
|
||||
def get_raw_ballance_map_info(scene: bpy.types.Scene) -> RawBallanceMapInfo:
|
||||
props: BBP_PG_ballance_map_info = get_ballance_map_info(scene)
|
||||
rawdata: RawBallanceMapInfo = RawBallanceMapInfo()
|
||||
|
||||
rawdata.mSectorCount = props.sector_count
|
||||
|
||||
rawdata.regulate()
|
||||
return rawdata
|
||||
|
||||
def set_raw_ballance_map_info(scene: bpy.types.Scene, rawdata: RawBallanceMapInfo) -> None:
|
||||
props: BBP_PG_ballance_map_info = get_ballance_map_info(scene)
|
||||
|
||||
props.sector_count = rawdata.mSectorCount
|
||||
|
||||
#endregion
|
||||
|
||||
class BBP_PT_ballance_map_info(bpy.types.Panel):
|
||||
"""Show Ballance Map Infos."""
|
||||
bl_label = "Ballance Map"
|
||||
bl_idname = "BBP_PT_ballance_map_info"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene is not None
|
||||
|
||||
def draw(self, context):
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
target: bpy.types.Scene = context.scene
|
||||
props: BBP_PG_ballance_map_info = get_ballance_map_info(target)
|
||||
|
||||
# show map sector count numberbox
|
||||
layout.prop(props, 'sector_count')
|
||||
|
||||
def register() -> None:
|
||||
# register
|
||||
bpy.utils.register_class(BBP_PG_ballance_map_info)
|
||||
bpy.utils.register_class(BBP_PT_ballance_map_info)
|
||||
|
||||
# add into scene metadata
|
||||
bpy.types.Scene.ballance_map_info = bpy.props.PointerProperty(type = BBP_PG_ballance_map_info)
|
||||
|
||||
def unregister() -> None:
|
||||
# del from scene metadata
|
||||
del bpy.types.Scene.ballance_map_info
|
||||
|
||||
# unregister
|
||||
bpy.utils.unregister_class(BBP_PG_ballance_map_info)
|
||||
bpy.utils.unregister_class(BBP_PT_ballance_map_info)
|
306
bbp_ng/PROP_bme_material.py
Normal file
306
bbp_ng/PROP_bme_material.py
Normal file
@ -0,0 +1,306 @@
|
||||
import bpy
|
||||
import typing, enum, copy
|
||||
from . import PROP_virtools_material, PROP_virtools_texture
|
||||
from . import UTIL_ballance_texture, UTIL_functions, UTIL_icons_manager
|
||||
|
||||
#region BME Material Presets
|
||||
|
||||
class _BMEMaterialPreset():
|
||||
## Associated Ballance texture file name, including file extension.
|
||||
mTexName: str
|
||||
## Predefined mtl preset in virtools material module
|
||||
mRawMtl: PROP_virtools_material.RawVirtoolsMaterial
|
||||
|
||||
def __init__(self, texname: str, rawmtl: PROP_virtools_material.RawVirtoolsMaterial):
|
||||
self.mTexName = texname
|
||||
self.mRawMtl = rawmtl
|
||||
|
||||
_g_BMEMaterialPresets: dict[str, _BMEMaterialPreset] = {
|
||||
'FloorSide': _BMEMaterialPreset(
|
||||
'Floor_Side.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.FloorSide).mData
|
||||
),
|
||||
'LightingFloorTopBorder': _BMEMaterialPreset(
|
||||
'Floor_Top_Border.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.FloorSide).mData
|
||||
),
|
||||
'LightingFloorTopBorderless': _BMEMaterialPreset(
|
||||
'Floor_Top_Borderless.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.FloorSide).mData
|
||||
),
|
||||
|
||||
'FloorTopBorder': _BMEMaterialPreset(
|
||||
'Floor_Top_Border.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.FloorTop).mData
|
||||
),
|
||||
'FloorTopBorderless': _BMEMaterialPreset(
|
||||
'Floor_Top_Borderless.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.FloorTop).mData
|
||||
),
|
||||
'FloorTopFlat': _BMEMaterialPreset(
|
||||
'Floor_Top_Flat.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.FloorTop).mData
|
||||
),
|
||||
'FloorTopProfil': _BMEMaterialPreset(
|
||||
'Floor_Top_Profil.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.FloorTop).mData
|
||||
),
|
||||
'FloorTopProfilFlat': _BMEMaterialPreset(
|
||||
'Floor_Top_ProfilFlat.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.FloorTop).mData
|
||||
),
|
||||
|
||||
'BallPaper': _BMEMaterialPreset(
|
||||
'Ball_Paper.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.TrafoPaper).mData
|
||||
),
|
||||
|
||||
'BallStone': _BMEMaterialPreset(
|
||||
'Ball_Stone.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.TraforWoodStone).mData
|
||||
),
|
||||
'BallWood': _BMEMaterialPreset(
|
||||
'Ball_Wood.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.TraforWoodStone).mData
|
||||
),
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region BME Material Define & Visitor
|
||||
|
||||
class BBP_PG_bme_material(bpy.types.PropertyGroup):
|
||||
bme_material_name: bpy.props.StringProperty(
|
||||
name = "Name",
|
||||
default = ""
|
||||
)
|
||||
|
||||
material_ptr: bpy.props.PointerProperty(
|
||||
name = "Material",
|
||||
type = bpy.types.Material
|
||||
)
|
||||
|
||||
def get_bme_materials(scene: bpy.types.Scene) -> bpy.types.CollectionProperty:
|
||||
return scene.bme_materials
|
||||
|
||||
#endregion
|
||||
|
||||
#region Material Preset Loader
|
||||
|
||||
def _load_bme_material_preset(mtl: bpy.types.Material, preset_name: str) -> None:
|
||||
# get preset first
|
||||
preset: _BMEMaterialPreset = _g_BMEMaterialPresets[preset_name]
|
||||
|
||||
# get raw mtl and do a shallow copy
|
||||
# because we will change something later. but do not want to affect preset self.
|
||||
raw_mtl: PROP_virtools_material.RawVirtoolsMaterial = copy.copy(preset.mRawMtl)
|
||||
|
||||
# load ballance texture
|
||||
blctex: bpy.types.Image = UTIL_ballance_texture.load_ballance_texture(preset.mTexName)
|
||||
# apply texture props
|
||||
PROP_virtools_texture.set_raw_virtools_texture(blctex, PROP_virtools_texture.get_ballance_texture_preset(preset.mTexName))
|
||||
# set loaded texture to shallow copied raw mtl
|
||||
raw_mtl.mTexture = blctex
|
||||
|
||||
# set raw mtl
|
||||
PROP_virtools_material.set_raw_virtools_material(mtl, raw_mtl)
|
||||
# apply vt mtl to blender mtl
|
||||
PROP_virtools_material.apply_to_blender_material(mtl)
|
||||
|
||||
#endregion
|
||||
|
||||
#region BME Material Operation Help Class & Functions
|
||||
|
||||
class BMEMaterialsHelper():
|
||||
"""
|
||||
The helper of BME materials processing.
|
||||
|
||||
All BME materials operations, including getting or setting, must be manipulated by this class.
|
||||
You should NOT operate BME Materials property (in Scene) directly.
|
||||
|
||||
This class should only have 1 instance at the same time. This class support `with` syntax to achieve this.
|
||||
This class frequently used in creating BME meshes.
|
||||
"""
|
||||
__mSingletonMutex: typing.ClassVar[bool] = False
|
||||
__mIsValid: bool
|
||||
__mAssocScene: bpy.types.Scene
|
||||
__mMaterialMap: dict[str, bpy.types.Material]
|
||||
|
||||
def __init__(self, assoc: bpy.types.Scene):
|
||||
self.__mMaterialMap = {}
|
||||
self.__mAssocScene = assoc
|
||||
|
||||
# check singleton
|
||||
if BMEMaterialsHelper.__mSingletonMutex:
|
||||
self.__mIsValid = False
|
||||
raise UTIL_functions.BBPException('BMEMaterialsHelper is mutex.')
|
||||
|
||||
# set validation and read ballance elements property
|
||||
BMEMaterialsHelper.__mSingletonMutex = True
|
||||
self.__mIsValid = True
|
||||
self.__read_from_bme_materials()
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self.__mIsValid
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.dispose()
|
||||
|
||||
def dispose(self) -> None:
|
||||
if self.is_valid():
|
||||
# write to ballance elements property and reset validation
|
||||
self.__write_to_bme_materials()
|
||||
self.__mIsValid = False
|
||||
BMEMaterialsHelper.__mSingletonMutex = False
|
||||
|
||||
def get_material(self, preset_name: str) -> bpy.types.Material:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('calling invalid BMEMaterialsHelper')
|
||||
|
||||
# get exist one
|
||||
mtl: bpy.types.Material | None = self.__mMaterialMap.get(preset_name, None)
|
||||
if mtl is not None:
|
||||
return mtl
|
||||
|
||||
# if no existing one, create new one
|
||||
new_mtl_name: str = 'BME' + preset_name
|
||||
new_mtl: bpy.types.Material = bpy.data.materials.new(new_mtl_name)
|
||||
|
||||
_load_bme_material_preset(new_mtl, preset_name)
|
||||
self.__mMaterialMap[preset_name] = new_mtl
|
||||
return new_mtl
|
||||
|
||||
def __write_to_bme_materials(self) -> None:
|
||||
mtls: bpy.types.CollectionProperty = get_bme_materials(self.__mAssocScene)
|
||||
mtls.clear()
|
||||
|
||||
for preset_name, mtl in self.__mMaterialMap.items():
|
||||
item: BBP_PG_bme_material = mtls.add()
|
||||
item.bme_material_name = preset_name
|
||||
item.material_ptr = mtl
|
||||
|
||||
def __read_from_bme_materials(self) -> None:
|
||||
mtls: bpy.types.CollectionProperty = get_bme_materials(self.__mAssocScene)
|
||||
self.__mMaterialMap.clear()
|
||||
|
||||
item: BBP_PG_bme_material
|
||||
for item in mtls:
|
||||
# check requirements
|
||||
if item.material_ptr is None: continue
|
||||
# add into map
|
||||
self.__mMaterialMap[item.bme_material_name] = item.material_ptr
|
||||
|
||||
def reset_bme_materials(scene: bpy.types.Scene) -> None:
|
||||
invalid_idx: list[int] = []
|
||||
mtls: bpy.types.CollectionProperty = get_bme_materials(scene)
|
||||
|
||||
# re-load all elements
|
||||
index: int = 0
|
||||
item: BBP_PG_bme_material
|
||||
for item in mtls:
|
||||
# load or record invalid entry
|
||||
if item.material_ptr is None:
|
||||
invalid_idx.append(index)
|
||||
else:
|
||||
_load_bme_material_preset(item.material_ptr, item.bme_material_name)
|
||||
|
||||
# inc counter
|
||||
index += 1
|
||||
|
||||
# remove invalid one with reversed order
|
||||
invalid_idx.reverse()
|
||||
for idx in invalid_idx:
|
||||
mtls.remove(idx)
|
||||
|
||||
#endregion
|
||||
|
||||
#region BME Materials Representation
|
||||
|
||||
class BBP_UL_bme_materials(bpy.types.UIList):
|
||||
def draw_item(self, context, layout: bpy.types.UILayout, data, item: BBP_PG_bme_material, icon, active_data, active_propname):
|
||||
# check requirements
|
||||
if item.material_ptr is None: return
|
||||
# draw list item
|
||||
layout.label(text = item.bme_material_name, translate = False)
|
||||
layout.label(text = item.material_ptr.name, translate = False, icon = 'MATERIAL')
|
||||
|
||||
class BBP_OT_reset_bme_materials(bpy.types.Operator):
|
||||
"""Reset all BME Materials to Default Settings."""
|
||||
bl_idname = "bbp.reset_bme_materials"
|
||||
bl_label = "Reset BME Materials"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
reset_bme_materials(context.scene)
|
||||
# show a window to let user know, not silence
|
||||
UTIL_functions.message_box(
|
||||
('Reset OK.', ),
|
||||
"Reset Result",
|
||||
UTIL_icons_manager.BlenderPresetIcons.Info.value
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_PT_bme_materials(bpy.types.Panel):
|
||||
"""Show BME Materials Properties."""
|
||||
bl_label = "BME Materials"
|
||||
bl_idname = "BBP_PT_bme_materials"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "scene"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene is not None
|
||||
|
||||
def draw(self, context):
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
target: bpy.types.Scene = context.scene
|
||||
col = layout.column()
|
||||
|
||||
# show restore operator
|
||||
opercol = col.column()
|
||||
opercol.operator(BBP_OT_reset_bme_materials.bl_idname, icon='LOOP_BACK')
|
||||
|
||||
# show list but not allowed to edit
|
||||
listcol = col.column()
|
||||
listcol.enabled = False
|
||||
listcol.template_list(
|
||||
"BBP_UL_bme_materials", "",
|
||||
target, "bme_materials",
|
||||
target, "active_bme_materials",
|
||||
# default row height is a half of the count of all presets
|
||||
# limit the max row height to the the count of all presets
|
||||
rows = len(_g_BMEMaterialPresets) // 2,
|
||||
maxrows = len(_g_BMEMaterialPresets),
|
||||
)
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
# register all classes
|
||||
bpy.utils.register_class(BBP_PG_bme_material)
|
||||
bpy.utils.register_class(BBP_UL_bme_materials)
|
||||
bpy.utils.register_class(BBP_OT_reset_bme_materials)
|
||||
bpy.utils.register_class(BBP_PT_bme_materials)
|
||||
|
||||
# add into scene metadata
|
||||
bpy.types.Scene.bme_materials = bpy.props.CollectionProperty(type = BBP_PG_bme_material)
|
||||
bpy.types.Scene.active_bme_materials = bpy.props.IntProperty()
|
||||
|
||||
def unregister() -> None:
|
||||
# del from scene metadata
|
||||
del bpy.types.Scene.active_bme_materials
|
||||
del bpy.types.Scene.bme_materials
|
||||
|
||||
bpy.utils.unregister_class(BBP_PT_bme_materials)
|
||||
bpy.utils.unregister_class(BBP_OT_reset_bme_materials)
|
||||
bpy.utils.unregister_class(BBP_UL_bme_materials)
|
||||
bpy.utils.unregister_class(BBP_PG_bme_material)
|
||||
|
61
bbp_ng/PROP_preferences.py
Normal file
61
bbp_ng/PROP_preferences.py
Normal file
@ -0,0 +1,61 @@
|
||||
import bpy
|
||||
import os, typing
|
||||
from . import UTIL_naming_convension
|
||||
|
||||
class RawPreferences():
|
||||
cBallanceTextureFolder: typing.ClassVar[str] = ""
|
||||
cNoComponentCollection: typing.ClassVar[str] = ""
|
||||
|
||||
mBallanceTextureFolder: str
|
||||
mNoComponentCollection: str
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.mBallanceTextureFolder = kwargs.get("mBallanceTextureFolder", "")
|
||||
self.mNoComponentCollection = kwargs.get("mNoComponentCollection", "")
|
||||
|
||||
def has_valid_blc_tex_folder(self) -> bool:
|
||||
return os.path.isdir(self.mBallanceTextureFolder)
|
||||
|
||||
class BBPPreferences(bpy.types.AddonPreferences):
|
||||
bl_idname = __package__
|
||||
|
||||
ballance_texture_folder: bpy.props.StringProperty(
|
||||
name = "Ballance Texture Folder",
|
||||
description = "The path to folder which will be used by this plugin to get external Ballance texture.",
|
||||
subtype='DIR_PATH',
|
||||
default = RawPreferences.cBallanceTextureFolder,
|
||||
)
|
||||
|
||||
no_component_collection: bpy.props.StringProperty(
|
||||
name = "No Component Collection",
|
||||
description = "(Import) The object which stored in this collectiion will not be saved as component. (Export) All forced no component objects will be stored in this collection",
|
||||
default = RawPreferences.cNoComponentCollection,
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
row = layout.row()
|
||||
col = row.column()
|
||||
col.label(text = "Ballance Texture Folder")
|
||||
col.prop(self, "ballance_texture_folder", text = "")
|
||||
col.label(text = "No Component Collection")
|
||||
col.prop(self, "no_component_collection", text = "")
|
||||
|
||||
def get_preferences() -> BBPPreferences:
|
||||
return bpy.context.preferences.addons[__package__].preferences
|
||||
|
||||
def get_raw_preferences() -> RawPreferences:
|
||||
pref: BBPPreferences = get_preferences()
|
||||
rawdata: RawPreferences = RawPreferences()
|
||||
|
||||
rawdata.mBallanceTextureFolder = pref.ballance_texture_folder
|
||||
rawdata.mNoComponentCollection = pref.no_component_collection
|
||||
|
||||
return rawdata
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBPPreferences)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBPPreferences)
|
51
bbp_ng/PROP_ptrprop_resolver.py
Normal file
51
bbp_ng/PROP_ptrprop_resolver.py
Normal file
@ -0,0 +1,51 @@
|
||||
import bpy
|
||||
|
||||
## Intent
|
||||
# Operator is not allowed to register Pointer Properties.
|
||||
# The solution is register pointer properties in Scene and reference it when drawing operator window.
|
||||
# This module contains all pointer properties used by other operators.
|
||||
|
||||
class BBP_PG_ptrprop_resolver(bpy.types.PropertyGroup):
|
||||
rail_uv_material: bpy.props.PointerProperty(
|
||||
name = "Material",
|
||||
description = "The material used for rail",
|
||||
type = bpy.types.Material,
|
||||
)
|
||||
|
||||
export_collection: bpy.props.PointerProperty(
|
||||
type = bpy.types.Collection,
|
||||
name = "Collection",
|
||||
description = "The collection exported. Nested collections allowed."
|
||||
)
|
||||
|
||||
export_object: bpy.props.PointerProperty(
|
||||
type = bpy.types.Object,
|
||||
name = "Object",
|
||||
description = "The object exported"
|
||||
)
|
||||
|
||||
def get_ptrprop_resolver() -> BBP_PG_ptrprop_resolver:
|
||||
return bpy.context.scene.bbp_ptrprop_resolver
|
||||
|
||||
def get_rail_uv_material() -> bpy.types.Material:
|
||||
return get_ptrprop_resolver().rail_uv_material
|
||||
def draw_rail_uv_material(layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(get_ptrprop_resolver(), 'rail_uv_material')
|
||||
|
||||
def get_export_collection() -> bpy.types.Collection:
|
||||
return get_ptrprop_resolver().export_collection
|
||||
def draw_export_collection(layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(get_ptrprop_resolver(), 'export_collection')
|
||||
|
||||
def get_export_object() -> bpy.types.Object:
|
||||
return get_ptrprop_resolver().export_object
|
||||
def draw_export_object(layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(get_ptrprop_resolver(), 'export_object')
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_PG_ptrprop_resolver)
|
||||
bpy.types.Scene.bbp_ptrprop_resolver = bpy.props.PointerProperty(type = BBP_PG_ptrprop_resolver)
|
||||
|
||||
def unregister() -> None:
|
||||
del bpy.types.Scene.bbp_ptrprop_resolver
|
||||
bpy.utils.unregister_class(BBP_PG_ptrprop_resolver)
|
422
bbp_ng/PROP_virtools_group.py
Normal file
422
bbp_ng/PROP_virtools_group.py
Normal file
@ -0,0 +1,422 @@
|
||||
import bpy
|
||||
import typing, enum
|
||||
from . import UTIL_functions, UTIL_icons_manager
|
||||
|
||||
#region Virtools Groups Define & Help Class
|
||||
|
||||
class BBP_PG_virtools_group(bpy.types.PropertyGroup):
|
||||
group_name: bpy.props.StringProperty(
|
||||
name = "Group Name",
|
||||
default = ""
|
||||
) # type: ignore
|
||||
|
||||
def get_virtools_groups(obj: bpy.types.Object) -> bpy.types.CollectionProperty:
|
||||
return obj.virtools_groups
|
||||
|
||||
def get_active_virtools_groups(obj: bpy.types.Object) -> int:
|
||||
return obj.active_virtools_groups
|
||||
|
||||
def set_active_virtools_groups(obj: bpy.types.Object, val: int) -> None:
|
||||
obj.active_virtools_groups = val
|
||||
|
||||
class VirtoolsGroupsHelper():
|
||||
"""
|
||||
A helper for object's Virtools groups adding, removal and checking.
|
||||
|
||||
All Virtools group operations should be done by this class.
|
||||
Do NOT manipulate object's Virtools group properties directly.
|
||||
"""
|
||||
__mSingletonMutex: typing.ClassVar[bool] = False
|
||||
__mIsValid: bool
|
||||
__mNoChange: bool ##< A bool indicate whether any change happended during lifetime. If no change, skip the writing when exiting.
|
||||
__mAssocObj: bpy.types.Object
|
||||
__mGroupsSet: set[str]
|
||||
|
||||
def __init__(self, assoc: bpy.types.Object):
|
||||
self.__mGroupsSet = set()
|
||||
self.__mAssocObj = assoc
|
||||
self.__mNoChange = True
|
||||
|
||||
# check singleton
|
||||
if VirtoolsGroupsHelper.__mSingletonMutex:
|
||||
self.__mIsValid = False
|
||||
raise UTIL_functions.BBPException('VirtoolsGroupsHelper is mutex.')
|
||||
|
||||
# set validation and read ballance elements property
|
||||
VirtoolsGroupsHelper.__mSingletonMutex = True
|
||||
self.__mIsValid = True
|
||||
self.__read_from_virtools_groups()
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self.__mIsValid
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.dispose()
|
||||
|
||||
def dispose(self) -> None:
|
||||
if self.is_valid():
|
||||
# if have changes,
|
||||
# write to ballance elements property and reset validation
|
||||
if not self.__mNoChange:
|
||||
self.__write_to_virtools_groups()
|
||||
self.__mIsValid = False
|
||||
VirtoolsGroupsHelper.__mSingletonMutex = False
|
||||
|
||||
def __check_valid(self) -> None:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('calling invalid VirtoolsGroupsHelper')
|
||||
|
||||
def add_group(self, gname: str) -> None:
|
||||
self.__check_valid()
|
||||
self.__mNoChange = False
|
||||
self.__mGroupsSet.add(gname)
|
||||
|
||||
def add_groups(self, gnames: typing.Iterable[str]) -> None:
|
||||
self.__check_valid()
|
||||
self.__mNoChange = False
|
||||
self.__mGroupsSet.update(gnames)
|
||||
|
||||
def remove_group(self, gname: str) -> None:
|
||||
self.__check_valid()
|
||||
self.__mNoChange = False
|
||||
self.__mGroupsSet.discard(gname)
|
||||
|
||||
def remove_groups(self, gnames: typing.Iterable[str]) -> None:
|
||||
self.__check_valid()
|
||||
self.__mNoChange = False
|
||||
for gname in gnames:
|
||||
self.__mGroupsSet.discard(gname)
|
||||
|
||||
def contain_group(self, gname: str) -> bool:
|
||||
self.__check_valid()
|
||||
return gname in self.__mGroupsSet
|
||||
|
||||
def contain_groups(self, gnames: typing.Iterable[str]) -> bool:
|
||||
"""
|
||||
Check existing intersection between group names and given collection.
|
||||
|
||||
In other words, check whether group name of given paramter is in group names with OR operator.
|
||||
|
||||
@param gnames[in] Iterable group names to check.
|
||||
@return return True if the length of the intersection between group names and given group names is not zero.
|
||||
"""
|
||||
self.__check_valid()
|
||||
for gname in gnames:
|
||||
if gname in self.__mGroupsSet:
|
||||
return True
|
||||
return False
|
||||
|
||||
def intersect_groups(self, gnames: set[str]) -> set[str]:
|
||||
self.__check_valid()
|
||||
return self.__mGroupsSet.intersection(gnames)
|
||||
|
||||
def iterate_groups(self) -> typing.Iterator[str]:
|
||||
self.__check_valid()
|
||||
return iter(self.__mGroupsSet)
|
||||
|
||||
def clear_groups(self) -> None:
|
||||
self.__check_valid()
|
||||
self.__mNoChange = False
|
||||
self.__mGroupsSet.clear()
|
||||
|
||||
def get_count(self) -> int:
|
||||
self.__check_valid()
|
||||
return len(self.__mGroupsSet)
|
||||
|
||||
def __write_to_virtools_groups(self) -> None:
|
||||
groups: bpy.types.CollectionProperty = get_virtools_groups(self.__mAssocObj)
|
||||
sel: int = get_active_virtools_groups(self.__mAssocObj)
|
||||
groups.clear()
|
||||
|
||||
for gname in self.__mGroupsSet:
|
||||
item: BBP_PG_virtools_group = groups.add()
|
||||
item.group_name = gname
|
||||
|
||||
# restore selection if necessary
|
||||
if sel >= len(self.__mGroupsSet):
|
||||
sel = len(self.__mGroupsSet) - 1
|
||||
if sel < 0:
|
||||
sel = 0
|
||||
set_active_virtools_groups(self.__mAssocObj, sel)
|
||||
|
||||
def __read_from_virtools_groups(self) -> None:
|
||||
groups: bpy.types.CollectionProperty = get_virtools_groups(self.__mAssocObj)
|
||||
self.__mGroupsSet.clear()
|
||||
|
||||
item: BBP_PG_virtools_group
|
||||
for item in groups:
|
||||
self.__mGroupsSet.add(item.group_name)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Preset Group Names
|
||||
|
||||
class VirtoolsGroupsPreset(enum.Enum):
|
||||
Sector_01 = "Sector_01"
|
||||
Sector_02 = "Sector_02"
|
||||
Sector_03 = "Sector_03"
|
||||
Sector_04 = "Sector_04"
|
||||
Sector_05 = "Sector_05"
|
||||
Sector_06 = "Sector_06"
|
||||
Sector_07 = "Sector_07"
|
||||
Sector_08 = "Sector_08"
|
||||
|
||||
P_Extra_Life = "P_Extra_Life"
|
||||
P_Extra_Point = "P_Extra_Point"
|
||||
P_Trafo_Paper = "P_Trafo_Paper"
|
||||
P_Trafo_Stone = "P_Trafo_Stone"
|
||||
P_Trafo_Wood = "P_Trafo_Wood"
|
||||
P_Ball_Paper = "P_Ball_Paper"
|
||||
P_Ball_Stone = "P_Ball_Stone"
|
||||
P_Ball_Wood = "P_Ball_Wood"
|
||||
P_Box = "P_Box"
|
||||
P_Dome = "P_Dome"
|
||||
P_Modul_01 = "P_Modul_01"
|
||||
P_Modul_03 = "P_Modul_03"
|
||||
P_Modul_08 = "P_Modul_08"
|
||||
P_Modul_17 = "P_Modul_17"
|
||||
P_Modul_18 = "P_Modul_18"
|
||||
P_Modul_19 = "P_Modul_19"
|
||||
P_Modul_25 = "P_Modul_25"
|
||||
P_Modul_26 = "P_Modul_26"
|
||||
P_Modul_29 = "P_Modul_29"
|
||||
P_Modul_30 = "P_Modul_30"
|
||||
P_Modul_34 = "P_Modul_34"
|
||||
P_Modul_37 = "P_Modul_37"
|
||||
P_Modul_41 = "P_Modul_41"
|
||||
|
||||
PS_Levelstart = "PS_Levelstart"
|
||||
PE_Levelende = "PE_Levelende"
|
||||
PC_Checkpoints = "PC_Checkpoints"
|
||||
PR_Resetpoints = "PR_Resetpoints"
|
||||
|
||||
Sound_HitID_01 = "Sound_HitID_01"
|
||||
Sound_RollID_01 = "Sound_RollID_01"
|
||||
Sound_HitID_02 = "Sound_HitID_02"
|
||||
Sound_RollID_02 = "Sound_RollID_02"
|
||||
Sound_HitID_03 = "Sound_HitID_03"
|
||||
Sound_RollID_03 = "Sound_RollID_03"
|
||||
|
||||
DepthTestCubes = "DepthTestCubes"
|
||||
|
||||
Phys_Floors = "Phys_Floors"
|
||||
Phys_FloorRails = "Phys_FloorRails"
|
||||
Phys_FloorStopper = "Phys_FloorStopper"
|
||||
|
||||
Shadow = "Shadow"
|
||||
|
||||
_g_VtGrpPresetValues: tuple[str] = tuple(map(lambda x: x.value, VirtoolsGroupsPreset))
|
||||
|
||||
## Some of group names are not matched with icon name
|
||||
# So we create a convertion map to convert them.
|
||||
_g_GroupIconNameConvMap: dict[str, str] = {
|
||||
"PS_Levelstart": "PS_FourFlames",
|
||||
"PE_Levelende": "PE_Balloon",
|
||||
"PC_Checkpoints": "PC_TwoFlames",
|
||||
"PR_Resetpoints": "PR_Resetpoint",
|
||||
|
||||
"Sound_HitID_01": "SoundID_01",
|
||||
"Sound_RollID_01": "SoundID_01",
|
||||
"Sound_HitID_02": "SoundID_02",
|
||||
"Sound_RollID_02": "SoundID_02",
|
||||
"Sound_HitID_03": "SoundID_03",
|
||||
"Sound_RollID_03": "SoundID_03"
|
||||
}
|
||||
def _get_group_icon_by_name(gp_name: str) -> int:
|
||||
# try converting group name
|
||||
# if not found, return self
|
||||
gp_name = _g_GroupIconNameConvMap.get(gp_name, gp_name)
|
||||
|
||||
# get from extra group icon first
|
||||
value: int | None = UTIL_icons_manager.get_group_icon(gp_name)
|
||||
if value is not None: return value
|
||||
|
||||
# if failed, get from component. if still failed, return empty icon
|
||||
value = UTIL_icons_manager.get_component_icon(gp_name)
|
||||
if value is not None: return value
|
||||
else: return UTIL_icons_manager.get_empty_icon()
|
||||
# blender group name prop helper
|
||||
_g_EnumHelper_Group: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
|
||||
VirtoolsGroupsPreset,
|
||||
lambda x: x.value, # member is string self
|
||||
lambda x: VirtoolsGroupsPreset(x), # convert directly because it is StrEnum.
|
||||
lambda x: x.value,
|
||||
lambda _: '',
|
||||
lambda x: _get_group_icon_by_name(x.value)
|
||||
)
|
||||
|
||||
class SharedGroupNameInputProperties():
|
||||
group_name_source: bpy.props.EnumProperty(
|
||||
name = "Group Name Source",
|
||||
items = (
|
||||
('DEFINED', "Predefined", "Pre-defined group name."),
|
||||
('CUSTOM', "Custom", "User specified group name."),
|
||||
),
|
||||
)
|
||||
|
||||
preset_group_name: bpy.props.EnumProperty(
|
||||
name = "Group Name",
|
||||
description = "Pick vanilla Ballance group name.",
|
||||
items = _g_EnumHelper_Group.generate_items(),
|
||||
)
|
||||
|
||||
custom_group_name: bpy.props.StringProperty(
|
||||
name = "Custom Group Name",
|
||||
description = "Input your custom group name.",
|
||||
default = "",
|
||||
)
|
||||
|
||||
def draw_group_name_input(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(self, 'group_name_source', expand = True)
|
||||
if (self.group_name_source == 'CUSTOM'):
|
||||
layout.prop(self, 'custom_group_name')
|
||||
else:
|
||||
layout.prop(self, 'preset_group_name')
|
||||
|
||||
def general_get_group_name(self) -> str:
|
||||
if self.group_name_source == 'CUSTOM':
|
||||
return self.custom_group_name
|
||||
else:
|
||||
return _g_EnumHelper_Group.get_selection(self.preset_group_name).value
|
||||
|
||||
#endregion
|
||||
|
||||
#region Display Panel and Simple Operator
|
||||
|
||||
class BBP_UL_virtools_groups(bpy.types.UIList):
|
||||
def draw_item(self, context, layout: bpy.types.UILayout, data, item: BBP_PG_virtools_group, icon, active_data, active_propname):
|
||||
layout.label(text = item.group_name, translate = False, icon_value = _get_group_icon_by_name(item.group_name))
|
||||
|
||||
class BBP_OT_add_virtools_group(bpy.types.Operator, SharedGroupNameInputProperties):
|
||||
"""Add a Virtools Group for Active Object."""
|
||||
bl_idname = "bbp.add_virtools_groups"
|
||||
bl_label = "Add to Virtools Groups"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(self, context: bpy.types.Context):
|
||||
return context.object is not None
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
def execute(self, context):
|
||||
# add group
|
||||
with VirtoolsGroupsHelper(context.object) as hlp:
|
||||
hlp.add_group(self.general_get_group_name())
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
self.draw_group_name_input(self.layout)
|
||||
|
||||
class BBP_OT_rm_virtools_group(bpy.types.Operator):
|
||||
"""Remove a Virtools Group for Active Object."""
|
||||
bl_idname = "bbp.rm_virtools_groups"
|
||||
bl_label = "Remove from Virtools Groups"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
## This class is slightly unique.
|
||||
# Because we need get user selected group name first.
|
||||
# Then pass it to helper.
|
||||
|
||||
@classmethod
|
||||
def poll(self, context: bpy.types.Context):
|
||||
if context.object is None:
|
||||
return False
|
||||
|
||||
obj = context.object
|
||||
gp = get_virtools_groups(obj)
|
||||
active_gp = get_active_virtools_groups(obj)
|
||||
return active_gp >= 0 and active_gp < len(gp)
|
||||
|
||||
def execute(self, context):
|
||||
# get selected group name first
|
||||
obj = context.object
|
||||
item: BBP_PG_virtools_group = get_virtools_groups(obj)[get_active_virtools_groups(obj)]
|
||||
gname: str = item.group_name
|
||||
# then delete it
|
||||
with VirtoolsGroupsHelper(obj) as hlp:
|
||||
hlp.remove_group(gname)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_OT_clear_virtools_groups(bpy.types.Operator):
|
||||
"""Clear All Virtools Group for Active Object."""
|
||||
bl_idname = "bbp.clear_virtools_groups"
|
||||
bl_label = "Clear Virtools Groups"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(self, context: bpy.types.Context):
|
||||
return context.object is not None
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_confirm(self, event)
|
||||
|
||||
def execute(self, context):
|
||||
with VirtoolsGroupsHelper(context.object) as hlp:
|
||||
hlp.clear_groups()
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_PT_virtools_groups(bpy.types.Panel):
|
||||
"""Show Virtools Groups Properties."""
|
||||
bl_label = "Virtools Groups"
|
||||
bl_idname = "BBP_PT_virtools_groups"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object is not None
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
target = bpy.context.active_object
|
||||
|
||||
row = layout.row()
|
||||
row.template_list(
|
||||
"BBP_UL_virtools_groups", "",
|
||||
target, "virtools_groups",
|
||||
target, "active_virtools_groups",
|
||||
rows = 6,
|
||||
maxrows = 6,
|
||||
)
|
||||
|
||||
col = row.column(align=True)
|
||||
col.operator(BBP_OT_add_virtools_group.bl_idname, icon='ADD', text="")
|
||||
col.operator(BBP_OT_rm_virtools_group.bl_idname, icon='REMOVE', text="")
|
||||
col.separator()
|
||||
col.operator(BBP_OT_clear_virtools_groups.bl_idname, icon='TRASH', text="")
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
# register all classes
|
||||
bpy.utils.register_class(BBP_PG_virtools_group)
|
||||
bpy.utils.register_class(BBP_UL_virtools_groups)
|
||||
bpy.utils.register_class(BBP_OT_add_virtools_group)
|
||||
bpy.utils.register_class(BBP_OT_rm_virtools_group)
|
||||
bpy.utils.register_class(BBP_OT_clear_virtools_groups)
|
||||
bpy.utils.register_class(BBP_PT_virtools_groups)
|
||||
|
||||
# add into object metadata
|
||||
bpy.types.Object.virtools_groups = bpy.props.CollectionProperty(type = BBP_PG_virtools_group)
|
||||
bpy.types.Object.active_virtools_groups = bpy.props.IntProperty()
|
||||
|
||||
def unregister() -> None:
|
||||
# del from object metadata
|
||||
del bpy.types.Object.active_virtools_groups
|
||||
del bpy.types.Object.virtools_groups
|
||||
|
||||
bpy.utils.unregister_class(BBP_PT_virtools_groups)
|
||||
bpy.utils.unregister_class(BBP_OT_clear_virtools_groups)
|
||||
bpy.utils.unregister_class(BBP_OT_rm_virtools_group)
|
||||
bpy.utils.unregister_class(BBP_OT_add_virtools_group)
|
||||
bpy.utils.unregister_class(BBP_UL_virtools_groups)
|
||||
bpy.utils.unregister_class(BBP_PG_virtools_group)
|
1103
bbp_ng/PROP_virtools_material.py
Normal file
1103
bbp_ng/PROP_virtools_material.py
Normal file
File diff suppressed because it is too large
Load Diff
85
bbp_ng/PROP_virtools_mesh.py
Normal file
85
bbp_ng/PROP_virtools_mesh.py
Normal file
@ -0,0 +1,85 @@
|
||||
import bpy
|
||||
import typing, enum
|
||||
from . import UTIL_functions, UTIL_virtools_types
|
||||
|
||||
# Raw Data
|
||||
|
||||
class RawVirtoolsMesh():
|
||||
# Instance Member Declarations
|
||||
mLitMode: UTIL_virtools_types.VXMESH_LITMODE
|
||||
# Default Value Declarations
|
||||
cDefaultLitMode: typing.ClassVar[UTIL_virtools_types.VXMESH_LITMODE] = UTIL_virtools_types.VXMESH_LITMODE.VX_LITMESH
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# assign default value for each component
|
||||
self.mLitMode = kwargs.get('mLitMode', RawVirtoolsMesh.cDefaultLitMode)
|
||||
|
||||
# blender enum prop helper defines
|
||||
_g_Helper_VXMESH_LITMODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXMESH_LITMODE)
|
||||
|
||||
# Blender Property Group
|
||||
|
||||
class BBP_PG_virtools_mesh(bpy.types.PropertyGroup):
|
||||
lit_mode: bpy.props.EnumProperty(
|
||||
name = "Lit Mode",
|
||||
description = "Lighting mode of the mesh.",
|
||||
items = _g_Helper_VXMESH_LITMODE.generate_items(),
|
||||
default = _g_Helper_VXMESH_LITMODE.to_selection(RawVirtoolsMesh.cDefaultLitMode)
|
||||
)
|
||||
|
||||
# Getter Setter
|
||||
|
||||
def get_virtools_mesh(mesh: bpy.types.Mesh) -> BBP_PG_virtools_mesh:
|
||||
return mesh.virtools_mesh
|
||||
|
||||
def get_raw_virtools_mesh(mesh: bpy.types.Mesh) -> RawVirtoolsMesh:
|
||||
props: BBP_PG_virtools_mesh = get_virtools_mesh(mesh)
|
||||
rawdata: RawVirtoolsMesh = RawVirtoolsMesh()
|
||||
|
||||
rawdata.mLitMode = _g_Helper_VXMESH_LITMODE.get_selection(props.lit_mode)
|
||||
|
||||
return rawdata
|
||||
|
||||
def set_raw_virtools_mesh(mesh: bpy.types.Mesh, rawdata: RawVirtoolsMesh) -> None:
|
||||
props: BBP_PG_virtools_mesh = get_virtools_mesh(mesh)
|
||||
|
||||
props.lit_mode = _g_Helper_VXMESH_LITMODE.to_selection(rawdata.mLitMode)
|
||||
|
||||
# Display Panel
|
||||
|
||||
class BBP_PT_virtools_mesh(bpy.types.Panel):
|
||||
"""Show Virtools Mesh Properties."""
|
||||
bl_label = "Virtools Mesh"
|
||||
bl_idname = "BBP_PT_virtools_mesh"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "data" # idk why blender use `data` as the mesh tab.
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.mesh is not None
|
||||
|
||||
def draw(self, context):
|
||||
# get layout and target
|
||||
layout = self.layout
|
||||
props: BBP_PG_virtools_mesh = get_virtools_mesh(context.mesh)
|
||||
|
||||
# draw data
|
||||
layout.prop(props, 'lit_mode')
|
||||
|
||||
# Register
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_PG_virtools_mesh)
|
||||
bpy.utils.register_class(BBP_PT_virtools_mesh)
|
||||
|
||||
# add into mesh metadata
|
||||
bpy.types.Mesh.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh)
|
||||
|
||||
def unregister() -> None:
|
||||
# remove from metadata
|
||||
del bpy.types.Mesh.virtools_mesh
|
||||
|
||||
bpy.utils.unregister_class(BBP_PT_virtools_mesh)
|
||||
bpy.utils.unregister_class(BBP_PG_virtools_mesh)
|
||||
|
205
bbp_ng/PROP_virtools_texture.py
Normal file
205
bbp_ng/PROP_virtools_texture.py
Normal file
@ -0,0 +1,205 @@
|
||||
import bpy
|
||||
import typing
|
||||
from . import UTIL_virtools_types, UTIL_functions
|
||||
|
||||
class RawVirtoolsTexture():
|
||||
|
||||
# Instance Member Declarations
|
||||
|
||||
mSaveOptions: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS
|
||||
mVideoFormat: UTIL_virtools_types.VX_PIXELFORMAT
|
||||
|
||||
# Default Value Declarations
|
||||
|
||||
cDefaultSaveOptions: typing.ClassVar[UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS] = UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_RAWDATA
|
||||
cDefaultVideoFormat: typing.ClassVar[UTIL_virtools_types.VX_PIXELFORMAT] = UTIL_virtools_types.VX_PIXELFORMAT._16_ARGB1555
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# assign default value for each component
|
||||
self.mSaveOptions = kwargs.get('mSaveOptions', RawVirtoolsTexture.cDefaultSaveOptions)
|
||||
self.mVideoFormat = kwargs.get('mVideoFormat', RawVirtoolsTexture.cDefaultVideoFormat)
|
||||
|
||||
# blender enum prop helper defines
|
||||
_g_Helper_CK_TEXTURE_SAVEOPTIONS: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS)
|
||||
_g_Helper_VX_PIXELFORMAT: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VX_PIXELFORMAT)
|
||||
|
||||
class BBP_PG_virtools_texture(bpy.types.PropertyGroup):
|
||||
|
||||
save_options: bpy.props.EnumProperty(
|
||||
name = "Save Options",
|
||||
description = "When saving a composition textures or sprites can be kept as reference to external files or converted to a given format and saved inside the composition file.",
|
||||
items = _g_Helper_CK_TEXTURE_SAVEOPTIONS.generate_items(),
|
||||
default = _g_Helper_CK_TEXTURE_SAVEOPTIONS.to_selection(RawVirtoolsTexture.cDefaultSaveOptions)
|
||||
)
|
||||
|
||||
video_format: bpy.props.EnumProperty(
|
||||
name = "Video Format",
|
||||
description = "The desired surface pixel format in video memory.",
|
||||
items = _g_Helper_VX_PIXELFORMAT.generate_items(),
|
||||
default = _g_Helper_VX_PIXELFORMAT.to_selection(RawVirtoolsTexture.cDefaultVideoFormat)
|
||||
)
|
||||
|
||||
#region Virtools Texture Getter Setter
|
||||
|
||||
def get_virtools_texture(img: bpy.types.Image) -> BBP_PG_virtools_texture:
|
||||
return img.virtools_texture
|
||||
|
||||
def get_raw_virtools_texture(img: bpy.types.Image) -> RawVirtoolsTexture:
|
||||
props: BBP_PG_virtools_texture = get_virtools_texture(img)
|
||||
rawdata: RawVirtoolsTexture = RawVirtoolsTexture()
|
||||
|
||||
rawdata.mSaveOptions = _g_Helper_CK_TEXTURE_SAVEOPTIONS.get_selection(props.save_options)
|
||||
rawdata.mVideoFormat = _g_Helper_VX_PIXELFORMAT.get_selection(props.video_format)
|
||||
return rawdata
|
||||
|
||||
def set_raw_virtools_texture(img: bpy.types.Image, rawdata: RawVirtoolsTexture) -> None:
|
||||
props: BBP_PG_virtools_texture = get_virtools_texture(img)
|
||||
|
||||
props.save_options = _g_Helper_CK_TEXTURE_SAVEOPTIONS.to_selection(rawdata.mSaveOptions)
|
||||
props.video_format = _g_Helper_VX_PIXELFORMAT.to_selection(rawdata.mVideoFormat)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Virtools Texture Drawer
|
||||
|
||||
"""!
|
||||
@remark
|
||||
Because Image do not have its unique properties window
|
||||
so we only can draw virtools texture properties in other window
|
||||
we provide various function to help draw property.
|
||||
"""
|
||||
|
||||
def draw_virtools_texture(img: bpy.types.Image, layout: bpy.types.UILayout):
|
||||
props: BBP_PG_virtools_texture = get_virtools_texture(img)
|
||||
|
||||
layout.prop(props, 'save_options')
|
||||
layout.prop(props, 'video_format')
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ballance Texture Preset
|
||||
|
||||
_g_OpaqueBallanceTexturePreset: RawVirtoolsTexture = RawVirtoolsTexture(
|
||||
mSaveOptions = UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL,
|
||||
mVideoFormat = UTIL_virtools_types.VX_PIXELFORMAT._16_ARGB1555,
|
||||
)
|
||||
_g_TransparentBallanceTexturePreset: RawVirtoolsTexture = RawVirtoolsTexture(
|
||||
mSaveOptions = UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL,
|
||||
mVideoFormat = UTIL_virtools_types.VX_PIXELFORMAT._32_ARGB8888,
|
||||
)
|
||||
_g_NonBallanceTexturePreset: RawVirtoolsTexture = RawVirtoolsTexture(
|
||||
mSaveOptions = UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_RAWDATA,
|
||||
mVideoFormat = UTIL_virtools_types.VX_PIXELFORMAT._32_ARGB8888,
|
||||
)
|
||||
|
||||
## The preset collection of all Ballance texture.
|
||||
# Key is texture name and can be used as file name checking.
|
||||
# Value is its preset which can be assigned.
|
||||
_g_BallanceTexturePresets: dict[str, RawVirtoolsTexture] = {
|
||||
# "atari.avi": _g_TransparentBallanceTexturePreset,
|
||||
"atari.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Ball_LightningSphere1.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Ball_LightningSphere2.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Ball_LightningSphere3.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Ball_Paper.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Ball_Stone.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Ball_Wood.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Brick.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Button01_deselect.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Button01_select.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Button01_special.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Column_beige.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Column_beige_fade.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Column_blue.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Cursor.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Dome.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"DomeEnvironment.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"DomeShadow.tga": _g_TransparentBallanceTexturePreset,
|
||||
"ExtraBall.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"ExtraParticle.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"E_Holzbeschlag.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"FloorGlow.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Floor_Side.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Floor_Top_Border.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Floor_Top_Borderless.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Floor_Top_Checkpoint.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Floor_Top_Flat.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Floor_Top_Profil.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Floor_Top_ProfilFlat.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Font_1.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Gravitylogo_intro.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"HardShadow.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Laterne_Glas.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Laterne_Schatten.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Laterne_Verlauf.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Logo.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Metal_stained.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Misc_Ufo.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Misc_UFO_Flash.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Modul03_Floor.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Modul03_Wall.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Modul11_13_Wood.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Modul11_Wood.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Modul15.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Modul16.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Modul18.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Modul18_Gitter.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Modul30_d_Seiten.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Particle_Flames.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Particle_Smoke.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"PE_Bal_balloons.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"PE_Bal_platform.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"PE_Ufo_env.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Pfeil.tga": _g_TransparentBallanceTexturePreset,
|
||||
"P_Extra_Life_Oil.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"P_Extra_Life_Particle.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"P_Extra_Life_Shadow.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Rail_Environment.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"sandsack.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"SkyLayer.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Sky_Vortex.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Stick_Bottom.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Stick_Stripes.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Target.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Tower_Roof.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Trafo_Environment.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Trafo_FlashField.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Trafo_Shadow_Big.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Tut_Pfeil01.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Tut_Pfeil_Hoch.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Wolken_intro.tga": _g_TransparentBallanceTexturePreset,
|
||||
"Wood_Metal.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Wood_MetalStripes.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Wood_Misc.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Wood_Nailed.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Wood_Old.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Wood_Panel.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Wood_Plain.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Wood_Plain2.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
"Wood_Raft.bmp": _g_OpaqueBallanceTexturePreset,
|
||||
}
|
||||
|
||||
def get_ballance_texture_preset(texname: str) -> RawVirtoolsTexture:
|
||||
try_preset: RawVirtoolsTexture | None = _g_BallanceTexturePresets.get(texname, None)
|
||||
if try_preset is None:
|
||||
# fallback to non-ballance one
|
||||
try_preset = _g_NonBallanceTexturePreset
|
||||
|
||||
return try_preset
|
||||
|
||||
def get_nonballance_texture_preset() -> RawVirtoolsTexture:
|
||||
return _g_NonBallanceTexturePreset
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_PG_virtools_texture)
|
||||
|
||||
# add into image metadata
|
||||
bpy.types.Image.virtools_texture = bpy.props.PointerProperty(type = BBP_PG_virtools_texture)
|
||||
|
||||
def unregister() -> None:
|
||||
# del from image metadata
|
||||
del bpy.types.Image.virtools_texture
|
||||
|
||||
bpy.utils.unregister_class(BBP_PG_virtools_texture)
|
3
bbp_ng/PyBMap/README.md
Normal file
3
bbp_ng/PyBMap/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# PyBMap Binding
|
||||
|
||||
Please note that this folder is a part of [libcmo21](https://github.com/yyc12345/libcmo21). And all Python scripts is copied from source project. If any issues raised in this sub module, please correct it in parent project and this project will sync to parent project's work.
|
0
bbp_ng/PyBMap/__init__.py
Normal file
0
bbp_ng/PyBMap/__init__.py
Normal file
785
bbp_ng/PyBMap/bmap.py
Normal file
785
bbp_ng/PyBMap/bmap.py
Normal file
@ -0,0 +1,785 @@
|
||||
import ctypes, os, sys, typing
|
||||
|
||||
#region Type Defines
|
||||
|
||||
class BMapException(Exception):
|
||||
"""
|
||||
The exception thrown by BMap bindings.
|
||||
"""
|
||||
pass
|
||||
|
||||
bm_CKSTRING = ctypes.c_char_p
|
||||
bm_CKSTRING_p = ctypes.POINTER(bm_CKSTRING)
|
||||
bm_CKDWORD = ctypes.c_uint32
|
||||
bm_CKDWORD_p = ctypes.POINTER(bm_CKDWORD)
|
||||
bm_CKDWORD_pp = ctypes.POINTER(bm_CKDWORD_p)
|
||||
bm_CKWORD = ctypes.c_uint16
|
||||
bm_CKWORD_p = ctypes.POINTER(bm_CKWORD)
|
||||
bm_CKWORD_pp = ctypes.POINTER(bm_CKWORD_p)
|
||||
bm_CKID = ctypes.c_uint32
|
||||
bm_CKID_p = ctypes.POINTER(bm_CKID)
|
||||
bm_CKID_pp = ctypes.POINTER(bm_CKID_p)
|
||||
bm_CKFLOAT = ctypes.c_float
|
||||
bm_CKFLOAT_p = ctypes.POINTER(bm_CKFLOAT)
|
||||
bm_CKINT = ctypes.c_int32
|
||||
bm_CKBYTE = ctypes.c_uint8
|
||||
bm_CKBYTE_p = ctypes.POINTER(bm_CKBYTE)
|
||||
|
||||
bm_enum = bm_CKDWORD
|
||||
bm_enum_p = ctypes.POINTER(bm_enum)
|
||||
bm_bool = ctypes.c_bool
|
||||
bm_bool_p = ctypes.POINTER(bm_bool)
|
||||
bm_void_p = ctypes.c_void_p
|
||||
bm_void_pp = ctypes.POINTER(ctypes.c_void_p)
|
||||
|
||||
bm_callback = ctypes.CFUNCTYPE(None, bm_CKSTRING)
|
||||
|
||||
class bm_VxVector2(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('x', bm_CKFLOAT),
|
||||
('y', bm_CKFLOAT),
|
||||
]
|
||||
bm_VxVector2_p = ctypes.POINTER(bm_VxVector2)
|
||||
bm_VxVector2_pp = ctypes.POINTER(bm_VxVector2_p)
|
||||
class bm_VxVector3(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('x', bm_CKFLOAT),
|
||||
('y', bm_CKFLOAT),
|
||||
('z', bm_CKFLOAT),
|
||||
]
|
||||
bm_VxVector3_p = ctypes.POINTER(bm_VxVector3)
|
||||
bm_VxVector3_pp = ctypes.POINTER(bm_VxVector3_p)
|
||||
class bm_VxColor(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('r', bm_CKFLOAT),
|
||||
('g', bm_CKFLOAT),
|
||||
('b', bm_CKFLOAT),
|
||||
('a', bm_CKFLOAT),
|
||||
]
|
||||
bm_VxColor_p = ctypes.POINTER(bm_VxColor)
|
||||
class bm_VxMatrix(ctypes.Structure):
|
||||
_fields_ = list(
|
||||
(f'i{idx}', bm_CKFLOAT) for idx in range(16)
|
||||
)
|
||||
bm_VxMatrix_p = ctypes.POINTER(bm_VxMatrix)
|
||||
|
||||
#endregion
|
||||
|
||||
#region BMap Loader
|
||||
|
||||
_g_BMapLibName: str
|
||||
|
||||
if sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
||||
_g_BMapLibName = "BMap.dll"
|
||||
elif sys.platform.startswith('linux') or sys.platform.startswith('freebsd'):
|
||||
_g_BMapLibName = "BMap.so"
|
||||
elif sys.platform.startswith('darwin'):
|
||||
_g_BMapLibName = "BMap.dylib"
|
||||
else:
|
||||
_g_BMapLibName = "BMap.bin"
|
||||
|
||||
_g_BMapModule: ctypes.CDLL | None = None
|
||||
try:
|
||||
_g_BMapModule = ctypes.cdll.LoadLibrary(
|
||||
os.path.join(os.path.dirname(__file__), _g_BMapLibName)
|
||||
)
|
||||
except:
|
||||
_g_BMapModule = None
|
||||
|
||||
def is_bmap_available() -> bool:
|
||||
return _g_BMapModule is not None
|
||||
|
||||
def _bmap_error_check(result: bool, func, args):
|
||||
if not result:
|
||||
raise BMapException("BMap operation failed.")
|
||||
return result
|
||||
|
||||
def _create_bmap_func(fct_name: str, fct_params: list[typing.Any]) -> typing.Callable[..., bm_bool]:
|
||||
if _g_BMapModule is None: return None
|
||||
|
||||
cache: typing.Callable[..., bm_bool] = getattr(_g_BMapModule, fct_name)
|
||||
cache.argtypes = fct_params
|
||||
cache.restype = bm_bool
|
||||
cache.errcheck = _bmap_error_check
|
||||
return cache
|
||||
|
||||
#endregion
|
||||
|
||||
#region Function Defines
|
||||
|
||||
## BMInit
|
||||
# @return True if no error, otherwise False.
|
||||
BMInit = _create_bmap_func('BMInit', [])
|
||||
## BMDispose
|
||||
# @return True if no error, otherwise False.
|
||||
BMDispose = _create_bmap_func('BMDispose', [])
|
||||
## BMFile_Load
|
||||
# @param file_name[in] Type: LibCmo::CKSTRING.
|
||||
# @param temp_folder[in] Type: LibCmo::CKSTRING.
|
||||
# @param texture_folder[in] Type: LibCmo::CKSTRING.
|
||||
# @param raw_callback[in] Type: BMap::NakedOutputCallback.
|
||||
# @param encoding_count[in] Type: LibCmo::CKDWORD.
|
||||
# @param encodings[in] Type: LibCmo::CKSTRING*.
|
||||
# @param out_file[out] Type: BMap::BMFile*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Load = _create_bmap_func('BMFile_Load', [bm_CKSTRING, bm_CKSTRING, bm_CKSTRING, bm_callback, bm_CKDWORD, bm_CKSTRING_p, bm_void_pp])
|
||||
## BMFile_Create
|
||||
# @param temp_folder[in] Type: LibCmo::CKSTRING.
|
||||
# @param texture_folder[in] Type: LibCmo::CKSTRING.
|
||||
# @param raw_callback[in] Type: BMap::NakedOutputCallback.
|
||||
# @param encoding_count[in] Type: LibCmo::CKDWORD.
|
||||
# @param encodings[in] Type: LibCmo::CKSTRING*.
|
||||
# @param out_file[out] Type: BMap::BMFile*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Create = _create_bmap_func('BMFile_Create', [bm_CKSTRING, bm_CKSTRING, bm_callback, bm_CKDWORD, bm_CKSTRING_p, bm_void_pp])
|
||||
## BMFile_Save
|
||||
# @param map_file[in] Type: BMap::BMFile*.
|
||||
# @param file_name[in] Type: LibCmo::CKSTRING.
|
||||
# @param texture_save_opt[in] Type: LibCmo::CK2::CK_TEXTURE_SAVEOPTIONS.
|
||||
# @param use_compress[in] Type: bool.
|
||||
# @param compreess_level[in] Type: LibCmo::CKINT.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Save = _create_bmap_func('BMFile_Save', [bm_void_p, bm_CKSTRING, bm_enum, bm_bool, bm_CKINT])
|
||||
## BMFile_Free
|
||||
# @param map_file[in] Type: BMap::BMFile*.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Free = _create_bmap_func('BMFile_Free', [bm_void_p])
|
||||
## BMFile_GetGroupCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetGroupCount = _create_bmap_func('BMFile_GetGroupCount', [bm_void_p, bm_CKDWORD_p])
|
||||
## BMFile_GetGroup
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param idx[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetGroup = _create_bmap_func('BMFile_GetGroup', [bm_void_p, bm_CKDWORD, bm_CKID_p])
|
||||
## BMFile_CreateGroup
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_CreateGroup = _create_bmap_func('BMFile_CreateGroup', [bm_void_p, bm_CKID_p])
|
||||
## BMFile_Get3dObjectCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Get3dObjectCount = _create_bmap_func('BMFile_Get3dObjectCount', [bm_void_p, bm_CKDWORD_p])
|
||||
## BMFile_Get3dObject
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param idx[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Get3dObject = _create_bmap_func('BMFile_Get3dObject', [bm_void_p, bm_CKDWORD, bm_CKID_p])
|
||||
## BMFile_Create3dObject
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Create3dObject = _create_bmap_func('BMFile_Create3dObject', [bm_void_p, bm_CKID_p])
|
||||
## BMFile_GetMeshCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetMeshCount = _create_bmap_func('BMFile_GetMeshCount', [bm_void_p, bm_CKDWORD_p])
|
||||
## BMFile_GetMesh
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param idx[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetMesh = _create_bmap_func('BMFile_GetMesh', [bm_void_p, bm_CKDWORD, bm_CKID_p])
|
||||
## BMFile_CreateMesh
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_CreateMesh = _create_bmap_func('BMFile_CreateMesh', [bm_void_p, bm_CKID_p])
|
||||
## BMFile_GetMaterialCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetMaterialCount = _create_bmap_func('BMFile_GetMaterialCount', [bm_void_p, bm_CKDWORD_p])
|
||||
## BMFile_GetMaterial
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param idx[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetMaterial = _create_bmap_func('BMFile_GetMaterial', [bm_void_p, bm_CKDWORD, bm_CKID_p])
|
||||
## BMFile_CreateMaterial
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_CreateMaterial = _create_bmap_func('BMFile_CreateMaterial', [bm_void_p, bm_CKID_p])
|
||||
## BMFile_GetTextureCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetTextureCount = _create_bmap_func('BMFile_GetTextureCount', [bm_void_p, bm_CKDWORD_p])
|
||||
## BMFile_GetTexture
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param idx[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetTexture = _create_bmap_func('BMFile_GetTexture', [bm_void_p, bm_CKDWORD, bm_CKID_p])
|
||||
## BMFile_CreateTexture
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_CreateTexture = _create_bmap_func('BMFile_CreateTexture', [bm_void_p, bm_CKID_p])
|
||||
## BMMeshTrans_New
|
||||
# @param out_trans[out] Type: BMap::BMMeshTransition*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_New = _create_bmap_func('BMMeshTrans_New', [bm_void_pp])
|
||||
## BMMeshTrans_Delete
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_Delete = _create_bmap_func('BMMeshTrans_Delete', [bm_void_p])
|
||||
## BMMeshTrans_PrepareVertexCount
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareVertexCount = _create_bmap_func('BMMeshTrans_PrepareVertexCount', [bm_void_p, bm_CKDWORD])
|
||||
## BMMeshTrans_PrepareVertex
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::VxMath::VxVector3*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareVertex = _create_bmap_func('BMMeshTrans_PrepareVertex', [bm_void_p, bm_VxVector3_pp])
|
||||
## BMMeshTrans_PrepareNormalCount
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareNormalCount = _create_bmap_func('BMMeshTrans_PrepareNormalCount', [bm_void_p, bm_CKDWORD])
|
||||
## BMMeshTrans_PrepareNormal
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::VxMath::VxVector3*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareNormal = _create_bmap_func('BMMeshTrans_PrepareNormal', [bm_void_p, bm_VxVector3_pp])
|
||||
## BMMeshTrans_PrepareUVCount
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareUVCount = _create_bmap_func('BMMeshTrans_PrepareUVCount', [bm_void_p, bm_CKDWORD])
|
||||
## BMMeshTrans_PrepareUV
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::VxMath::VxVector2*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareUV = _create_bmap_func('BMMeshTrans_PrepareUV', [bm_void_p, bm_VxVector2_pp])
|
||||
## BMMeshTrans_PrepareMtlSlotCount
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareMtlSlotCount = _create_bmap_func('BMMeshTrans_PrepareMtlSlotCount', [bm_void_p, bm_CKDWORD])
|
||||
## BMMeshTrans_PrepareMtlSlot
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::CK2::CK_ID*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareMtlSlot = _create_bmap_func('BMMeshTrans_PrepareMtlSlot', [bm_void_p, bm_CKID_pp])
|
||||
## BMMeshTrans_PrepareFaceCount
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareFaceCount = _create_bmap_func('BMMeshTrans_PrepareFaceCount', [bm_void_p, bm_CKDWORD])
|
||||
## BMMeshTrans_PrepareFaceVertexIndices
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::CKDWORD*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareFaceVertexIndices = _create_bmap_func('BMMeshTrans_PrepareFaceVertexIndices', [bm_void_p, bm_CKDWORD_pp])
|
||||
## BMMeshTrans_PrepareFaceNormalIndices
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::CKDWORD*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareFaceNormalIndices = _create_bmap_func('BMMeshTrans_PrepareFaceNormalIndices', [bm_void_p, bm_CKDWORD_pp])
|
||||
## BMMeshTrans_PrepareFaceUVIndices
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::CKDWORD*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareFaceUVIndices = _create_bmap_func('BMMeshTrans_PrepareFaceUVIndices', [bm_void_p, bm_CKDWORD_pp])
|
||||
## BMMeshTrans_PrepareFaceMtlSlot
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::CKDWORD*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareFaceMtlSlot = _create_bmap_func('BMMeshTrans_PrepareFaceMtlSlot', [bm_void_p, bm_CKDWORD_pp])
|
||||
## BMMeshTrans_Parse
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param bmfile[in] Type: BMap::BMFile*.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_Parse = _create_bmap_func('BMMeshTrans_Parse', [bm_void_p, bm_void_p, bm_CKID])
|
||||
## BMObject_GetName
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_name[out] Type: LibCmo::CKSTRING. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMObject_GetName = _create_bmap_func('BMObject_GetName', [bm_void_p, bm_CKID, bm_CKSTRING_p])
|
||||
## BMObject_SetName
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param name[in] Type: LibCmo::CKSTRING.
|
||||
# @return True if no error, otherwise False.
|
||||
BMObject_SetName = _create_bmap_func('BMObject_SetName', [bm_void_p, bm_CKID, bm_CKSTRING])
|
||||
## BMGroup_AddObject
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param memberid[in] Type: LibCmo::CK2::CK_ID.
|
||||
# @return True if no error, otherwise False.
|
||||
BMGroup_AddObject = _create_bmap_func('BMGroup_AddObject', [bm_void_p, bm_CKID, bm_CKID])
|
||||
## BMGroup_GetObjectCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMGroup_GetObjectCount = _create_bmap_func('BMGroup_GetObjectCount', [bm_void_p, bm_CKID, bm_CKDWORD_p])
|
||||
## BMGroup_GetObject
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param pos[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_objid[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMGroup_GetObject = _create_bmap_func('BMGroup_GetObject', [bm_void_p, bm_CKID, bm_CKDWORD, bm_CKID_p])
|
||||
## BMTexture_GetFileName
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_filename[out] Type: LibCmo::CKSTRING. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_GetFileName = _create_bmap_func('BMTexture_GetFileName', [bm_void_p, bm_CKID, bm_CKSTRING_p])
|
||||
## BMTexture_LoadImage
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param filename[in] Type: LibCmo::CKSTRING.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_LoadImage = _create_bmap_func('BMTexture_LoadImage', [bm_void_p, bm_CKID, bm_CKSTRING])
|
||||
## BMTexture_SaveImage
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param filename[in] Type: LibCmo::CKSTRING.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_SaveImage = _create_bmap_func('BMTexture_SaveImage', [bm_void_p, bm_CKID, bm_CKSTRING])
|
||||
## BMTexture_GetSaveOptions
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_saveopt[out] Type: LibCmo::CK2::CK_TEXTURE_SAVEOPTIONS. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_GetSaveOptions = _create_bmap_func('BMTexture_GetSaveOptions', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMTexture_SetSaveOptions
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param saveopt[in] Type: LibCmo::CK2::CK_TEXTURE_SAVEOPTIONS.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_SetSaveOptions = _create_bmap_func('BMTexture_SetSaveOptions', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMTexture_GetVideoFormat
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_vfmt[out] Type: LibCmo::VxMath::VX_PIXELFORMAT. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_GetVideoFormat = _create_bmap_func('BMTexture_GetVideoFormat', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMTexture_SetVideoFormat
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param vfmt[in] Type: LibCmo::VxMath::VX_PIXELFORMAT.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_SetVideoFormat = _create_bmap_func('BMTexture_SetVideoFormat', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetDiffuse
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VxColor. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetDiffuse = _create_bmap_func('BMMaterial_GetDiffuse', [bm_void_p, bm_CKID, bm_VxColor_p])
|
||||
## BMMaterial_SetDiffuse
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param col[in] Type: LibCmo::VxMath::VxColor.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetDiffuse = _create_bmap_func('BMMaterial_SetDiffuse', [bm_void_p, bm_CKID, bm_VxColor])
|
||||
## BMMaterial_GetAmbient
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VxColor. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetAmbient = _create_bmap_func('BMMaterial_GetAmbient', [bm_void_p, bm_CKID, bm_VxColor_p])
|
||||
## BMMaterial_SetAmbient
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param col[in] Type: LibCmo::VxMath::VxColor.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetAmbient = _create_bmap_func('BMMaterial_SetAmbient', [bm_void_p, bm_CKID, bm_VxColor])
|
||||
## BMMaterial_GetSpecular
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VxColor. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetSpecular = _create_bmap_func('BMMaterial_GetSpecular', [bm_void_p, bm_CKID, bm_VxColor_p])
|
||||
## BMMaterial_SetSpecular
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param col[in] Type: LibCmo::VxMath::VxColor.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetSpecular = _create_bmap_func('BMMaterial_SetSpecular', [bm_void_p, bm_CKID, bm_VxColor])
|
||||
## BMMaterial_GetEmissive
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VxColor. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetEmissive = _create_bmap_func('BMMaterial_GetEmissive', [bm_void_p, bm_CKID, bm_VxColor_p])
|
||||
## BMMaterial_SetEmissive
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param col[in] Type: LibCmo::VxMath::VxColor.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetEmissive = _create_bmap_func('BMMaterial_SetEmissive', [bm_void_p, bm_CKID, bm_VxColor])
|
||||
## BMMaterial_GetSpecularPower
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::CKFLOAT. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetSpecularPower = _create_bmap_func('BMMaterial_GetSpecularPower', [bm_void_p, bm_CKID, bm_CKFLOAT_p])
|
||||
## BMMaterial_SetSpecularPower
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::CKFLOAT.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetSpecularPower = _create_bmap_func('BMMaterial_SetSpecularPower', [bm_void_p, bm_CKID, bm_CKFLOAT])
|
||||
## BMMaterial_GetTexture
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_texid[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTexture = _create_bmap_func('BMMaterial_GetTexture', [bm_void_p, bm_CKID, bm_CKID_p])
|
||||
## BMMaterial_SetTexture
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param texid[in] Type: LibCmo::CK2::CK_ID.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTexture = _create_bmap_func('BMMaterial_SetTexture', [bm_void_p, bm_CKID, bm_CKID])
|
||||
## BMMaterial_GetTextureBorderColor
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTextureBorderColor = _create_bmap_func('BMMaterial_GetTextureBorderColor', [bm_void_p, bm_CKID, bm_CKDWORD_p])
|
||||
## BMMaterial_SetTextureBorderColor
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTextureBorderColor = _create_bmap_func('BMMaterial_SetTextureBorderColor', [bm_void_p, bm_CKID, bm_CKDWORD])
|
||||
## BMMaterial_GetTextureBlendMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXTEXTURE_BLENDMODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTextureBlendMode = _create_bmap_func('BMMaterial_GetTextureBlendMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetTextureBlendMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXTEXTURE_BLENDMODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTextureBlendMode = _create_bmap_func('BMMaterial_SetTextureBlendMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetTextureMinMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXTEXTURE_FILTERMODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTextureMinMode = _create_bmap_func('BMMaterial_GetTextureMinMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetTextureMinMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXTEXTURE_FILTERMODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTextureMinMode = _create_bmap_func('BMMaterial_SetTextureMinMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetTextureMagMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXTEXTURE_FILTERMODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTextureMagMode = _create_bmap_func('BMMaterial_GetTextureMagMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetTextureMagMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXTEXTURE_FILTERMODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTextureMagMode = _create_bmap_func('BMMaterial_SetTextureMagMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetTextureAddressMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXTEXTURE_ADDRESSMODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTextureAddressMode = _create_bmap_func('BMMaterial_GetTextureAddressMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetTextureAddressMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXTEXTURE_ADDRESSMODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTextureAddressMode = _create_bmap_func('BMMaterial_SetTextureAddressMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetSourceBlend
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXBLEND_MODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetSourceBlend = _create_bmap_func('BMMaterial_GetSourceBlend', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetSourceBlend
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXBLEND_MODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetSourceBlend = _create_bmap_func('BMMaterial_SetSourceBlend', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetDestBlend
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXBLEND_MODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetDestBlend = _create_bmap_func('BMMaterial_GetDestBlend', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetDestBlend
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXBLEND_MODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetDestBlend = _create_bmap_func('BMMaterial_SetDestBlend', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetFillMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXFILL_MODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetFillMode = _create_bmap_func('BMMaterial_GetFillMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetFillMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXFILL_MODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetFillMode = _create_bmap_func('BMMaterial_SetFillMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetShadeMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXSHADE_MODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetShadeMode = _create_bmap_func('BMMaterial_GetShadeMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetShadeMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXSHADE_MODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetShadeMode = _create_bmap_func('BMMaterial_SetShadeMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetAlphaTestEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetAlphaTestEnabled = _create_bmap_func('BMMaterial_GetAlphaTestEnabled', [bm_void_p, bm_CKID, bm_bool_p])
|
||||
## BMMaterial_SetAlphaTestEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param enabled[in] Type: bool.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetAlphaTestEnabled = _create_bmap_func('BMMaterial_SetAlphaTestEnabled', [bm_void_p, bm_CKID, bm_bool])
|
||||
## BMMaterial_GetAlphaBlendEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetAlphaBlendEnabled = _create_bmap_func('BMMaterial_GetAlphaBlendEnabled', [bm_void_p, bm_CKID, bm_bool_p])
|
||||
## BMMaterial_SetAlphaBlendEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param enabled[in] Type: bool.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetAlphaBlendEnabled = _create_bmap_func('BMMaterial_SetAlphaBlendEnabled', [bm_void_p, bm_CKID, bm_bool])
|
||||
## BMMaterial_GetPerspectiveCorrectionEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetPerspectiveCorrectionEnabled = _create_bmap_func('BMMaterial_GetPerspectiveCorrectionEnabled', [bm_void_p, bm_CKID, bm_bool_p])
|
||||
## BMMaterial_SetPerspectiveCorrectionEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param enabled[in] Type: bool.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetPerspectiveCorrectionEnabled = _create_bmap_func('BMMaterial_SetPerspectiveCorrectionEnabled', [bm_void_p, bm_CKID, bm_bool])
|
||||
## BMMaterial_GetZWriteEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetZWriteEnabled = _create_bmap_func('BMMaterial_GetZWriteEnabled', [bm_void_p, bm_CKID, bm_bool_p])
|
||||
## BMMaterial_SetZWriteEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param enabled[in] Type: bool.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetZWriteEnabled = _create_bmap_func('BMMaterial_SetZWriteEnabled', [bm_void_p, bm_CKID, bm_bool])
|
||||
## BMMaterial_GetTwoSidedEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTwoSidedEnabled = _create_bmap_func('BMMaterial_GetTwoSidedEnabled', [bm_void_p, bm_CKID, bm_bool_p])
|
||||
## BMMaterial_SetTwoSidedEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param enabled[in] Type: bool.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTwoSidedEnabled = _create_bmap_func('BMMaterial_SetTwoSidedEnabled', [bm_void_p, bm_CKID, bm_bool])
|
||||
## BMMaterial_GetAlphaRef
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::CKBYTE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetAlphaRef = _create_bmap_func('BMMaterial_GetAlphaRef', [bm_void_p, bm_CKID, bm_CKBYTE_p])
|
||||
## BMMaterial_SetAlphaRef
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::CKBYTE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetAlphaRef = _create_bmap_func('BMMaterial_SetAlphaRef', [bm_void_p, bm_CKID, bm_CKBYTE])
|
||||
## BMMaterial_GetAlphaFunc
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXCMPFUNC. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetAlphaFunc = _create_bmap_func('BMMaterial_GetAlphaFunc', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetAlphaFunc
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXCMPFUNC.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetAlphaFunc = _create_bmap_func('BMMaterial_SetAlphaFunc', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetZFunc
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXCMPFUNC. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetZFunc = _create_bmap_func('BMMaterial_GetZFunc', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetZFunc
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXCMPFUNC.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetZFunc = _create_bmap_func('BMMaterial_SetZFunc', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMesh_GetLitMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mode[out] Type: LibCmo::VxMath::VXMESH_LITMODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetLitMode = _create_bmap_func('BMMesh_GetLitMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMesh_SetLitMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param mode[in] Type: LibCmo::VxMath::VXMESH_LITMODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_SetLitMode = _create_bmap_func('BMMesh_SetLitMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMesh_GetVertexCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetVertexCount = _create_bmap_func('BMMesh_GetVertexCount', [bm_void_p, bm_CKID, bm_CKDWORD_p])
|
||||
## BMMesh_SetVertexCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_SetVertexCount = _create_bmap_func('BMMesh_SetVertexCount', [bm_void_p, bm_CKID, bm_CKDWORD])
|
||||
## BMMesh_GetVertexPositions
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mem[out] Type: LibCmo::VxMath::VxVector3*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetVertexPositions = _create_bmap_func('BMMesh_GetVertexPositions', [bm_void_p, bm_CKID, bm_VxVector3_pp])
|
||||
## BMMesh_GetVertexNormals
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mem[out] Type: LibCmo::VxMath::VxVector3*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetVertexNormals = _create_bmap_func('BMMesh_GetVertexNormals', [bm_void_p, bm_CKID, bm_VxVector3_pp])
|
||||
## BMMesh_GetVertexUVs
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mem[out] Type: LibCmo::VxMath::VxVector2*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetVertexUVs = _create_bmap_func('BMMesh_GetVertexUVs', [bm_void_p, bm_CKID, bm_VxVector2_pp])
|
||||
## BMMesh_GetFaceCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetFaceCount = _create_bmap_func('BMMesh_GetFaceCount', [bm_void_p, bm_CKID, bm_CKDWORD_p])
|
||||
## BMMesh_SetFaceCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_SetFaceCount = _create_bmap_func('BMMesh_SetFaceCount', [bm_void_p, bm_CKID, bm_CKDWORD])
|
||||
## BMMesh_GetFaceIndices
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mem[out] Type: LibCmo::CKWORD*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetFaceIndices = _create_bmap_func('BMMesh_GetFaceIndices', [bm_void_p, bm_CKID, bm_CKWORD_pp])
|
||||
## BMMesh_GetFaceMaterialSlotIndexs
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mem[out] Type: LibCmo::CKWORD*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetFaceMaterialSlotIndexs = _create_bmap_func('BMMesh_GetFaceMaterialSlotIndexs', [bm_void_p, bm_CKID, bm_CKWORD_pp])
|
||||
## BMMesh_GetMaterialSlotCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetMaterialSlotCount = _create_bmap_func('BMMesh_GetMaterialSlotCount', [bm_void_p, bm_CKID, bm_CKDWORD_p])
|
||||
## BMMesh_SetMaterialSlotCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_SetMaterialSlotCount = _create_bmap_func('BMMesh_SetMaterialSlotCount', [bm_void_p, bm_CKID, bm_CKDWORD])
|
||||
## BMMesh_GetMaterialSlot
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param index[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_mtlid[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetMaterialSlot = _create_bmap_func('BMMesh_GetMaterialSlot', [bm_void_p, bm_CKID, bm_CKDWORD, bm_CKID_p])
|
||||
## BMMesh_SetMaterialSlot
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param index[in] Type: LibCmo::CKDWORD.
|
||||
# @param mtlid[in] Type: LibCmo::CK2::CK_ID.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_SetMaterialSlot = _create_bmap_func('BMMesh_SetMaterialSlot', [bm_void_p, bm_CKID, bm_CKDWORD, bm_CKID])
|
||||
## BM3dObject_GetWorldMatrix
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mat[out] Type: LibCmo::VxMath::VxMatrix. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BM3dObject_GetWorldMatrix = _create_bmap_func('BM3dObject_GetWorldMatrix', [bm_void_p, bm_CKID, bm_VxMatrix_p])
|
||||
## BM3dObject_SetWorldMatrix
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param mat[in] Type: LibCmo::VxMath::VxMatrix.
|
||||
# @return True if no error, otherwise False.
|
||||
BM3dObject_SetWorldMatrix = _create_bmap_func('BM3dObject_SetWorldMatrix', [bm_void_p, bm_CKID, bm_VxMatrix])
|
||||
## BM3dObject_GetCurrentMesh
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_meshid[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BM3dObject_GetCurrentMesh = _create_bmap_func('BM3dObject_GetCurrentMesh', [bm_void_p, bm_CKID, bm_CKID_p])
|
||||
## BM3dObject_SetCurrentMesh
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param meshid[in] Type: LibCmo::CK2::CK_ID.
|
||||
# @return True if no error, otherwise False.
|
||||
BM3dObject_SetCurrentMesh = _create_bmap_func('BM3dObject_SetCurrentMesh', [bm_void_p, bm_CKID, bm_CKID])
|
||||
## BM3dObject_GetVisibility
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_isVisible[out] Type: bool. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BM3dObject_GetVisibility = _create_bmap_func('BM3dObject_GetVisibility', [bm_void_p, bm_CKID, bm_bool_p])
|
||||
## BM3dObject_SetVisibility
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param is_visible[in] Type: bool.
|
||||
# @return True if no error, otherwise False.
|
||||
BM3dObject_SetVisibility = _create_bmap_func('BM3dObject_SetVisibility', [bm_void_p, bm_CKID, bm_bool])
|
||||
|
||||
#endregion
|
||||
|
865
bbp_ng/PyBMap/bmap_wrapper.py
Normal file
865
bbp_ng/PyBMap/bmap_wrapper.py
Normal file
@ -0,0 +1,865 @@
|
||||
import ctypes, typing, atexit
|
||||
from . import bmap, virtools_types
|
||||
|
||||
#region Basic Class & Constant Defines
|
||||
|
||||
g_InvalidPtr: bmap.bm_void_p = bmap.bm_void_p(0)
|
||||
g_InvalidCKID: int = 0
|
||||
g_BMapEncoding: str = "utf-8"
|
||||
|
||||
def _python_callback(strl: bytes):
|
||||
"""
|
||||
The Python type callback for BMFile.
|
||||
Simply add a prefix when output.
|
||||
Need a convertion before passing to BMFile.
|
||||
"""
|
||||
# the passing value is bytes, not bmap.bm_CKSTRING.
|
||||
# i think Python do a auto convertion here.
|
||||
if strl is not None:
|
||||
print(f'[PyBMap] {strl.decode(g_BMapEncoding)}')
|
||||
_g_RawCallback: bmap.bm_callback = bmap.bm_callback(_python_callback)
|
||||
|
||||
class _AbstractPointer():
|
||||
__mRawPointer: int
|
||||
|
||||
def __init__(self, raw_pointer: bmap.bm_void_p):
|
||||
self._set_pointer(raw_pointer)
|
||||
|
||||
def _is_valid(self) -> bool:
|
||||
return self.__mRawPointer != 0
|
||||
|
||||
def _get_pointer(self) -> bmap.bm_void_p:
|
||||
return bmap.bm_void_p(self.__mRawPointer)
|
||||
|
||||
def _set_pointer(self, raw_pointer: bmap.bm_void_p):
|
||||
if raw_pointer.value is None:
|
||||
self.__mRawPointer = 0
|
||||
else:
|
||||
self.__mRawPointer = raw_pointer.value
|
||||
|
||||
def __eq__(self, obj: object) -> bool:
|
||||
if isinstance(obj, self.__class__):
|
||||
return obj.__mRawPointer == self.__mRawPointer
|
||||
else:
|
||||
return False
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.__mRawPointer)
|
||||
|
||||
class _AbstractCKObject(_AbstractPointer):
|
||||
__mCKID: int
|
||||
|
||||
def __init__(self, raw_pointer: bmap.bm_void_p, ckid: bmap.bm_CKID):
|
||||
_AbstractPointer.__init__(self, raw_pointer)
|
||||
self.__mCKID = ckid.value
|
||||
|
||||
def _is_valid(self) -> bool:
|
||||
return _AbstractPointer._is_valid(self) and self.__mCKID != 0
|
||||
|
||||
def _get_ckid(self) -> bmap.bm_CKID:
|
||||
return bmap.bm_CKID(self.__mCKID)
|
||||
|
||||
def __eq__(self, obj: object) -> bool:
|
||||
if not _AbstractPointer.__eq__(self, obj): return False
|
||||
|
||||
if isinstance(obj, self.__class__):
|
||||
return obj.__mCKID == self.__mCKID
|
||||
else:
|
||||
return False
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((_AbstractPointer.__hash__(self), self.__mCKID))
|
||||
|
||||
#endregion
|
||||
|
||||
#region Help Function & Type Define
|
||||
|
||||
TCKObj = typing.TypeVar('TCKObj', bound = _AbstractCKObject)
|
||||
|
||||
def _vxvector3_assigner(pvector: bmap.bm_VxVector3_p, count: int, itor: typing.Iterator[virtools_types.VxVector3]) -> None:
|
||||
pfloat: bmap.bm_CKFLOAT_p = ctypes.cast(pvector, bmap.bm_CKFLOAT_p)
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
uservector: virtools_types.VxVector3 = next(itor)
|
||||
pfloat[idx] = uservector.x
|
||||
pfloat[idx + 1] = uservector.y
|
||||
pfloat[idx + 2] = uservector.z
|
||||
idx += 3
|
||||
|
||||
def _vxvector3_iterator(pvector: bmap.bm_VxVector3_p, count: int) -> typing.Iterator[virtools_types.VxVector3]:
|
||||
ret: virtools_types.VxVector3 = virtools_types.VxVector3()
|
||||
pfloat: bmap.bm_CKFLOAT_p = ctypes.cast(pvector, bmap.bm_CKFLOAT_p)
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
ret.x = pfloat[idx]
|
||||
ret.y = pfloat[idx + 1]
|
||||
ret.z = pfloat[idx + 2]
|
||||
idx += 3
|
||||
yield ret
|
||||
|
||||
def _vxvector2_assigner(pvector: bmap.bm_VxVector2_p, count: int, itor: typing.Iterator[virtools_types.VxVector2]) -> None:
|
||||
pfloat: bmap.bm_CKFLOAT_p = ctypes.cast(pvector, bmap.bm_CKFLOAT_p)
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
uservector: virtools_types.VxVector2 = next(itor)
|
||||
pfloat[idx] = uservector.x
|
||||
pfloat[idx + 1] = uservector.y
|
||||
idx += 2
|
||||
|
||||
def _vxvector2_iterator(pvector: bmap.bm_VxVector2_p, count: int) -> typing.Iterator[virtools_types.VxVector2]:
|
||||
ret: virtools_types.VxVector2 = virtools_types.VxVector2()
|
||||
pfloat: bmap.bm_CKFLOAT_p = ctypes.cast(pvector, bmap.bm_CKFLOAT_p)
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
ret.x = pfloat[idx]
|
||||
ret.y = pfloat[idx + 1]
|
||||
idx += 2
|
||||
yield ret
|
||||
|
||||
# bmap.bm_CKWORD_p | bmap.bm_CKDWORD_p is just a type hint
|
||||
# wo do not need distinguish them in code.
|
||||
# because the type of pindices is decided by runtime.
|
||||
|
||||
def _ckfaceindices_assigner(pindices: bmap.bm_CKWORD_p | bmap.bm_CKDWORD_p, count: int, itor: typing.Iterator[virtools_types.CKFaceIndices]) -> None:
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
userindices: virtools_types.CKFaceIndices = next(itor)
|
||||
pindices[idx] = userindices.i1
|
||||
pindices[idx + 1] = userindices.i2
|
||||
pindices[idx + 2] = userindices.i3
|
||||
idx += 3
|
||||
|
||||
def _ckfaceindices_iterator(pindices: bmap.bm_CKWORD_p | bmap.bm_CKDWORD_p, count: int) -> typing.Iterator[virtools_types.CKFaceIndices]:
|
||||
ret: virtools_types.CKFaceIndices = virtools_types.CKFaceIndices()
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
ret.i1 = pindices[idx]
|
||||
ret.i2 = pindices[idx + 1]
|
||||
ret.i3 = pindices[idx + 2]
|
||||
idx += 3
|
||||
yield ret
|
||||
|
||||
#endregion
|
||||
|
||||
#region Valid Check, Init and Dispose
|
||||
|
||||
def is_bmap_available() -> bool:
|
||||
return bmap.is_bmap_available()
|
||||
|
||||
# init module self and register exit function
|
||||
if is_bmap_available():
|
||||
bmap.BMInit()
|
||||
|
||||
def _auto_exit():
|
||||
bmap.BMDispose()
|
||||
atexit.register(_auto_exit)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Real Type Defines
|
||||
|
||||
"""!
|
||||
@remark
|
||||
BMFileReader, BMFileWriter, and BMMeshTrans can be create by given constructor.
|
||||
But they must be destroyed by calling dispose(). Otherwise it may cause memory leak.
|
||||
You also can use python `with` statement to achieve this automatically.
|
||||
|
||||
BMObject, BMTexture, BMMaterial, BMMesh, and BM3dObject should NOT be constructed from given constructor.
|
||||
They must be obtained from BMFileReader, BMFileWriter, and BMMeshTrans.
|
||||
Thus BMObject, BMTexture, BMMaterial, BMMesh, and BM3dObject also do not need to free
|
||||
because these resources are sotred in BMFileReader, BMFileWriter, and BMMeshTrans.
|
||||
We just provide them as a visitor.
|
||||
"""
|
||||
|
||||
class BMObject(_AbstractCKObject):
|
||||
def get_name(self) -> str | None:
|
||||
name: bmap.bm_CKSTRING = bmap.bm_CKSTRING()
|
||||
bmap.BMObject_GetName(self._get_pointer(), self._get_ckid(), ctypes.byref(name))
|
||||
if name.value is None:
|
||||
return None
|
||||
else:
|
||||
return name.value.decode(g_BMapEncoding)
|
||||
|
||||
def set_name(self, name_: str | None) -> None:
|
||||
name: bmap.bm_CKSTRING
|
||||
if name_ is None:
|
||||
name = bmap.bm_CKSTRING(0)
|
||||
else:
|
||||
name = bmap.bm_CKSTRING(name_.encode(g_BMapEncoding))
|
||||
bmap.BMObject_SetName(self._get_pointer(), self._get_ckid(), name)
|
||||
|
||||
class BMTexture(BMObject):
|
||||
def get_file_name(self) -> str | None:
|
||||
filename: bmap.bm_CKSTRING = bmap.bm_CKSTRING()
|
||||
bmap.BMTexture_GetFileName(self._get_pointer(), self._get_ckid(), ctypes.byref(filename))
|
||||
if filename.value is None:
|
||||
return None
|
||||
else:
|
||||
return filename.value.decode(g_BMapEncoding)
|
||||
|
||||
def load_image(self, filepath: str) -> None:
|
||||
filename: bmap.bm_CKSTRING = bmap.bm_CKSTRING(filepath.encode(g_BMapEncoding))
|
||||
bmap.BMTexture_LoadImage(self._get_pointer(), self._get_ckid(), filename)
|
||||
|
||||
def save_image(self, filepath: str) -> None:
|
||||
filename: bmap.bm_CKSTRING = bmap.bm_CKSTRING(filepath.encode(g_BMapEncoding))
|
||||
bmap.BMTexture_SaveImage(self._get_pointer(), self._get_ckid(), filename)
|
||||
|
||||
def get_save_options(self) -> virtools_types.CK_TEXTURE_SAVEOPTIONS:
|
||||
opt: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMTexture_GetSaveOptions(self._get_pointer(), self._get_ckid(), ctypes.byref(opt))
|
||||
return virtools_types.CK_TEXTURE_SAVEOPTIONS(opt.value)
|
||||
|
||||
def set_save_options(self, opt_: virtools_types.CK_TEXTURE_SAVEOPTIONS) -> None:
|
||||
opt: bmap.bm_enum = bmap.bm_enum(opt_.value)
|
||||
bmap.BMTexture_SetSaveOptions(self._get_pointer(), self._get_ckid(), opt)
|
||||
|
||||
def get_video_format(self) -> virtools_types.VX_PIXELFORMAT:
|
||||
fmt: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMTexture_GetVideoFormat(self._get_pointer(), self._get_ckid(), ctypes.byref(fmt))
|
||||
return virtools_types.VX_PIXELFORMAT(fmt.value)
|
||||
|
||||
def set_video_format(self, fmt_: virtools_types.VX_PIXELFORMAT) -> None:
|
||||
fmt: bmap.bm_enum = bmap.bm_enum(fmt_.value)
|
||||
bmap.BMTexture_SetVideoFormat(self._get_pointer(), self._get_ckid(), fmt)
|
||||
|
||||
class BMMaterial(BMObject):
|
||||
def _set_vxcolor(self,
|
||||
setter_: typing.Callable[[bmap.bm_void_p, bmap.bm_CKID, bmap.bm_VxColor], bool],
|
||||
col_: virtools_types.VxColor) -> None:
|
||||
# set to raw color
|
||||
col: bmap.bm_VxColor = bmap.bm_VxColor()
|
||||
col.r = col_.r
|
||||
col.g = col_.g
|
||||
col.b = col_.b
|
||||
col.a = col_.a
|
||||
# assign
|
||||
setter_(self._get_pointer(), self._get_ckid(), col)
|
||||
def _get_vxcolor(self,
|
||||
getter_: typing.Callable[[bmap.bm_void_p, bmap.bm_CKID, bmap.bm_VxColor_p], bool]) -> virtools_types.VxColor:
|
||||
# get raw color
|
||||
col: bmap.bm_VxColor = bmap.bm_VxColor()
|
||||
getter_(self._get_pointer(), self._get_ckid(), ctypes.byref(col))
|
||||
# get from raw color
|
||||
ret: virtools_types.VxColor = virtools_types.VxColor()
|
||||
ret.r = col.r
|
||||
ret.g = col.g
|
||||
ret.b = col.b
|
||||
ret.a = col.a
|
||||
return ret
|
||||
|
||||
def get_diffuse(self) -> virtools_types.VxColor:
|
||||
return self._get_vxcolor(bmap.BMMaterial_GetDiffuse)
|
||||
def set_diffuse(self, col: virtools_types.VxColor) -> None:
|
||||
self._set_vxcolor(bmap.BMMaterial_SetDiffuse, col)
|
||||
def get_ambient(self) -> virtools_types.VxColor:
|
||||
return self._get_vxcolor(bmap.BMMaterial_GetAmbient)
|
||||
def set_ambient(self, col: virtools_types.VxColor) -> None:
|
||||
self._set_vxcolor(bmap.BMMaterial_SetAmbient, col)
|
||||
def get_specular(self) -> virtools_types.VxColor:
|
||||
return self._get_vxcolor(bmap.BMMaterial_GetSpecular)
|
||||
def set_specular(self, col: virtools_types.VxColor) -> None:
|
||||
self._set_vxcolor(bmap.BMMaterial_SetSpecular, col)
|
||||
def get_emissive(self) -> virtools_types.VxColor:
|
||||
return self._get_vxcolor(bmap.BMMaterial_GetEmissive)
|
||||
def set_emissive(self, col: virtools_types.VxColor) -> None:
|
||||
self._set_vxcolor(bmap.BMMaterial_SetEmissive, col)
|
||||
|
||||
def get_specular_power(self) -> float:
|
||||
power: bmap.bm_CKFLOAT = bmap.bm_CKFLOAT()
|
||||
bmap.BMMaterial_GetSpecularPower(self._get_pointer(), self._get_ckid(), ctypes.byref(power))
|
||||
return power.value
|
||||
def set_specular_power(self, power_: float) -> None:
|
||||
power: bmap.bm_CKFLOAT = bmap.bm_CKFLOAT(power_)
|
||||
bmap.BMMaterial_SetSpecularPower(self._get_pointer(), self._get_ckid(), power)
|
||||
|
||||
def get_texture(self) -> BMTexture | None:
|
||||
objid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
bmap.BMMaterial_GetTexture(self._get_pointer(), self._get_ckid(), ctypes.byref(objid))
|
||||
if objid.value == g_InvalidCKID:
|
||||
return None
|
||||
else:
|
||||
return BMTexture(self._get_pointer(), objid)
|
||||
|
||||
def set_texture(self, tex_: BMTexture | None) -> None:
|
||||
objid: bmap.bm_CKID = bmap.bm_CKID(g_InvalidCKID)
|
||||
if tex_ is not None:
|
||||
objid = tex_._get_ckid()
|
||||
bmap.BMMaterial_SetTexture(self._get_pointer(), self._get_ckid(), objid)
|
||||
|
||||
def get_texture_border_color(self) -> virtools_types.VxColor:
|
||||
col: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
bmap.BMMaterial_GetTextureBorderColor(self._get_pointer(), self._get_ckid(), ctypes.byref(col))
|
||||
ret: virtools_types.VxColor = virtools_types.VxColor()
|
||||
ret.from_dword(col.value)
|
||||
return ret
|
||||
def set_texture_border_color(self, col_: virtools_types.VxColor) -> None:
|
||||
col: bmap.bm_CKDWORD = bmap.bm_CKDWORD(col_.to_dword())
|
||||
bmap.BMMaterial_SetTextureBorderColor(self._get_pointer(), self._get_ckid(), col)
|
||||
|
||||
def get_texture_blend_mode(self) -> virtools_types.VXTEXTURE_BLENDMODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetTextureBlendMode(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXTEXTURE_BLENDMODE(data.value)
|
||||
def set_texture_blend_mode(self, data_: virtools_types.VXTEXTURE_BLENDMODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetTextureBlendMode(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_texture_min_mode(self) -> virtools_types.VXTEXTURE_FILTERMODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetTextureMinMode(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXTEXTURE_FILTERMODE(data.value)
|
||||
def set_texture_min_mode(self, data_: virtools_types.VXTEXTURE_FILTERMODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetTextureMinMode(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_texture_mag_mode(self) -> virtools_types.VXTEXTURE_FILTERMODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetTextureMagMode(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXTEXTURE_FILTERMODE(data.value)
|
||||
def set_texture_mag_mode(self, data_: virtools_types.VXTEXTURE_FILTERMODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetTextureMagMode(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_texture_address_mode(self) -> virtools_types.VXTEXTURE_ADDRESSMODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetTextureAddressMode(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXTEXTURE_ADDRESSMODE(data.value)
|
||||
def set_texture_address_mode(self, data_: virtools_types.VXTEXTURE_ADDRESSMODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetTextureAddressMode(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_source_blend(self) -> virtools_types.VXBLEND_MODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetSourceBlend(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXBLEND_MODE(data.value)
|
||||
def set_source_blend(self, data_: virtools_types.VXBLEND_MODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetSourceBlend(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_dest_blend(self) -> virtools_types.VXBLEND_MODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetDestBlend(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXBLEND_MODE(data.value)
|
||||
def set_dest_blend(self, data_: virtools_types.VXBLEND_MODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetDestBlend(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_fill_mode(self) -> virtools_types.VXFILL_MODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetFillMode(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXFILL_MODE(data.value)
|
||||
def set_fill_mode(self, data_: virtools_types.VXFILL_MODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetFillMode(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_shade_mode(self) -> virtools_types.VXSHADE_MODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetShadeMode(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXSHADE_MODE(data.value)
|
||||
def set_shade_mode(self, data_: virtools_types.VXSHADE_MODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetShadeMode(self._get_pointer(), self._get_ckid(), data)
|
||||
|
||||
def get_alpha_test_enabled(self) -> bool:
|
||||
data: bmap.bm_bool = bmap.bm_bool()
|
||||
bmap.BMMaterial_GetAlphaTestEnabled(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return data.value
|
||||
def set_alpha_test_enabled(self, data_: bool) -> None:
|
||||
data: bmap.bm_bool = bmap.bm_bool(data_)
|
||||
bmap.BMMaterial_SetAlphaTestEnabled(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_alpha_blend_enabled(self) -> bool:
|
||||
data: bmap.bm_bool = bmap.bm_bool()
|
||||
bmap.BMMaterial_GetAlphaBlendEnabled(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return data.value
|
||||
def set_alpha_blend_enabled(self, data_: bool) -> None:
|
||||
data: bmap.bm_bool = bmap.bm_bool(data_)
|
||||
bmap.BMMaterial_SetAlphaBlendEnabled(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_perspective_correction_enabled(self) -> bool:
|
||||
data: bmap.bm_bool = bmap.bm_bool()
|
||||
bmap.BMMaterial_GetPerspectiveCorrectionEnabled(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return data.value
|
||||
def set_perspective_correction_enabled(self, data_: bool) -> None:
|
||||
data: bmap.bm_bool = bmap.bm_bool(data_)
|
||||
bmap.BMMaterial_SetPerspectiveCorrectionEnabled(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_z_write_enabled(self) -> bool:
|
||||
data: bmap.bm_bool = bmap.bm_bool()
|
||||
bmap.BMMaterial_GetZWriteEnabled(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return data.value
|
||||
def set_z_write_enabled(self, data_: bool) -> None:
|
||||
data: bmap.bm_bool = bmap.bm_bool(data_)
|
||||
bmap.BMMaterial_SetZWriteEnabled(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_two_sided_enabled(self) -> bool:
|
||||
data: bmap.bm_bool = bmap.bm_bool()
|
||||
bmap.BMMaterial_GetTwoSidedEnabled(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return data.value
|
||||
def set_two_sided_enabled(self, data_: bool) -> None:
|
||||
data: bmap.bm_bool = bmap.bm_bool(data_)
|
||||
bmap.BMMaterial_SetTwoSidedEnabled(self._get_pointer(), self._get_ckid(), data)
|
||||
|
||||
def get_alpha_ref(self) -> int:
|
||||
data: bmap.bm_CKBYTE = bmap.bm_CKBYTE()
|
||||
bmap.BMMaterial_GetAlphaRef(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return data.value
|
||||
def set_alpha_ref(self, data_: int):
|
||||
data: bmap.bm_CKBYTE = bmap.bm_CKBYTE(data_)
|
||||
bmap.BMMaterial_SetAlphaRef(self._get_pointer(), self._get_ckid(), data)
|
||||
|
||||
def get_alpha_func(self) -> virtools_types.VXCMPFUNC:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetAlphaFunc(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXCMPFUNC(data.value)
|
||||
def set_alpha_func(self, data_: virtools_types.VXCMPFUNC) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetAlphaFunc(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_z_func(self) -> virtools_types.VXCMPFUNC:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetZFunc(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXCMPFUNC(data.value)
|
||||
def set_z_func(self, data_: virtools_types.VXCMPFUNC) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetZFunc(self._get_pointer(), self._get_ckid(), data)
|
||||
|
||||
class BMMesh(BMObject):
|
||||
|
||||
def get_lit_mode(self) -> virtools_types.VXMESH_LITMODE:
|
||||
mode: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMesh_GetLitMode(self._get_pointer(), self._get_ckid(), ctypes.byref(mode))
|
||||
return virtools_types.VXMESH_LITMODE(mode.value)
|
||||
|
||||
def set_lit_mode(self, mode_: virtools_types.VXMESH_LITMODE) -> None:
|
||||
mode: bmap.bm_enum = bmap.bm_enum(mode_.value)
|
||||
bmap.BMMesh_SetLitMode(self._get_pointer(), self._get_ckid(), mode)
|
||||
|
||||
def get_vertex_count(self) -> int:
|
||||
count: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
bmap.BMMesh_GetVertexCount(self._get_pointer(), self._get_ckid(), ctypes.byref(count))
|
||||
return count.value
|
||||
|
||||
def set_vertex_count(self, count_: int) -> None:
|
||||
count: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count_)
|
||||
bmap.BMMesh_SetVertexCount(self._get_pointer(), self._get_ckid(), count)
|
||||
|
||||
def get_vertex_positions(self) -> typing.Iterator[virtools_types.VxVector3]:
|
||||
# get raw pointer and return
|
||||
raw_vector: bmap.bm_VxVector3_p = bmap.bm_VxVector3_p()
|
||||
bmap.BMMesh_GetVertexPositions(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_vector))
|
||||
return _vxvector3_iterator(raw_vector, self.get_vertex_count())
|
||||
|
||||
def set_vertex_positions(self, itor: typing.Iterator[virtools_types.VxVector3]) -> None:
|
||||
# get raw float pointer and assign
|
||||
raw_vector: bmap.bm_VxVector3_p = bmap.bm_VxVector3_p()
|
||||
bmap.BMMesh_GetVertexPositions(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_vector))
|
||||
_vxvector3_assigner(raw_vector, self.get_vertex_count(), itor)
|
||||
|
||||
def get_vertex_normals(self) -> typing.Iterator[virtools_types.VxVector3]:
|
||||
raw_vector: bmap.bm_VxVector3_p = bmap.bm_VxVector3_p()
|
||||
bmap.BMMesh_GetVertexNormals(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_vector))
|
||||
return _vxvector3_iterator(raw_vector, self.get_vertex_count())
|
||||
|
||||
def set_vertex_normals(self, itor: typing.Iterator[virtools_types.VxVector3]) -> None:
|
||||
raw_vector: bmap.bm_VxVector3_p = bmap.bm_VxVector3_p()
|
||||
bmap.BMMesh_GetVertexNormals(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_vector))
|
||||
_vxvector3_assigner(raw_vector, self.get_vertex_count(), itor)
|
||||
|
||||
def get_vertex_uvs(self) -> typing.Iterator[virtools_types.VxVector2]:
|
||||
raw_vector: bmap.bm_VxVector2_p = bmap.bm_VxVector2_p()
|
||||
bmap.BMMesh_GetVertexUVs(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_vector))
|
||||
return _vxvector2_iterator(raw_vector, self.get_vertex_count())
|
||||
|
||||
def set_vertex_uvs(self, itor: typing.Iterator[virtools_types.VxVector2]) -> None:
|
||||
raw_vector: bmap.bm_VxVector2_p = bmap.bm_VxVector2_p()
|
||||
bmap.BMMesh_GetVertexUVs(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_vector))
|
||||
_vxvector2_assigner(raw_vector, self.get_vertex_count(), itor)
|
||||
|
||||
def get_face_count(self) -> int:
|
||||
count: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
bmap.BMMesh_GetFaceCount(self._get_pointer(), self._get_ckid(), ctypes.byref(count))
|
||||
return count.value
|
||||
|
||||
def set_face_count(self, count_: int) -> None:
|
||||
count: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count_)
|
||||
bmap.BMMesh_SetFaceCount(self._get_pointer(), self._get_ckid(), count)
|
||||
|
||||
def get_face_indices(self) -> typing.Iterator[virtools_types.CKFaceIndices]:
|
||||
raw_idx: bmap.bm_CKWORD_p = bmap.bm_CKWORD_p()
|
||||
bmap.BMMesh_GetFaceIndices(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_idx))
|
||||
return _ckfaceindices_iterator(raw_idx, self.get_face_count())
|
||||
|
||||
def set_face_indices(self, itor: typing.Iterator[virtools_types.CKFaceIndices]) -> None:
|
||||
raw_idx: bmap.bm_CKWORD_p = bmap.bm_CKWORD_p()
|
||||
bmap.BMMesh_GetFaceIndices(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_idx))
|
||||
_ckfaceindices_assigner(raw_idx, self.get_face_count(), itor)
|
||||
|
||||
def get_face_material_slot_indexs(self) -> typing.Iterator[int]:
|
||||
raw_idx: bmap.bm_CKWORD_p = bmap.bm_CKWORD_p()
|
||||
bmap.BMMesh_GetFaceMaterialSlotIndexs(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_idx))
|
||||
for i in range(self.get_face_count()):
|
||||
yield raw_idx[i]
|
||||
|
||||
def set_face_material_slot_indexs(self, itor: typing.Iterator[int]) -> None:
|
||||
raw_idx: bmap.bm_CKWORD_p = bmap.bm_CKWORD_p()
|
||||
bmap.BMMesh_GetFaceMaterialSlotIndexs(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_idx))
|
||||
for i in range(self.get_face_count()):
|
||||
raw_idx[i] = next(itor)
|
||||
|
||||
def get_material_slot_count(self) -> int:
|
||||
count: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
bmap.BMMesh_GetMaterialSlotCount(self._get_pointer(), self._get_ckid(), ctypes.byref(count))
|
||||
return count.value
|
||||
|
||||
def set_material_slot_count(self, count_: int) -> None:
|
||||
count: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count_)
|
||||
bmap.BMMesh_SetMaterialSlotCount(self._get_pointer(), self._get_ckid(), count)
|
||||
|
||||
def get_material_slots(self) -> typing.Iterator[BMMaterial | None]:
|
||||
idx: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
mtlid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
for i in range(self.get_material_slot_count()):
|
||||
idx.value = i
|
||||
bmap.BMMesh_GetMaterialSlot(self._get_pointer(), self._get_ckid(), idx, ctypes.byref(mtlid))
|
||||
if mtlid.value == g_InvalidCKID:
|
||||
yield None
|
||||
else:
|
||||
yield BMMaterial(self._get_pointer(), mtlid)
|
||||
|
||||
def set_material_slots(self, itor: typing.Iterator[BMMaterial | None]) -> None:
|
||||
idx: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
mtlid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
for i in range(self.get_material_slot_count()):
|
||||
idx.value = i
|
||||
# analyze mtl item
|
||||
mtlobj: BMMaterial | None = next(itor)
|
||||
if mtlobj is None:
|
||||
mtlid.value = g_InvalidCKID
|
||||
else:
|
||||
mtlid = mtlobj._get_ckid()
|
||||
# set
|
||||
bmap.BMMesh_SetMaterialSlot(self._get_pointer(), self._get_ckid(), idx, mtlid)
|
||||
|
||||
class BM3dObject(BMObject):
|
||||
def get_world_matrix(self) -> virtools_types.VxMatrix:
|
||||
mat: bmap.bm_VxMatrix = bmap.bm_VxMatrix()
|
||||
bmap.BM3dObject_GetWorldMatrix(self._get_pointer(), self._get_ckid(), ctypes.byref(mat))
|
||||
# use cast & pointer to get matrix data conveniently
|
||||
flat: bmap.bm_CKFLOAT_p = ctypes.cast(ctypes.byref(mat), bmap.bm_CKFLOAT_p)
|
||||
ret: virtools_types.VxMatrix = virtools_types.VxMatrix()
|
||||
ret.from_const(tuple(flat[i] for i in range(16)))
|
||||
return ret
|
||||
|
||||
def set_world_matrix(self, mat_: virtools_types.VxMatrix) -> None:
|
||||
# star syntax expand the tuple as the argument.
|
||||
mat: bmap.bm_VxMatrix = bmap.bm_VxMatrix(*(mat_.to_const()))
|
||||
bmap.BM3dObject_SetWorldMatrix(self._get_pointer(), self._get_ckid(), mat)
|
||||
|
||||
def get_current_mesh(self) -> BMMesh | None:
|
||||
ckid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
bmap.BM3dObject_GetCurrentMesh(self._get_pointer(), self._get_ckid(), ctypes.byref(ckid))
|
||||
if ckid.value == g_InvalidCKID:
|
||||
return None
|
||||
else:
|
||||
return BMMesh(self._get_pointer(), ckid)
|
||||
|
||||
def set_current_mesh(self, mesh: BMMesh | None) -> None:
|
||||
ckid: bmap.bm_CKID = bmap.bm_CKID(g_InvalidCKID)
|
||||
if mesh is not None:
|
||||
ckid = mesh._get_ckid()
|
||||
bmap.BM3dObject_SetCurrentMesh(self._get_pointer(), self._get_ckid(), ckid)
|
||||
|
||||
def get_visibility(self) -> bool:
|
||||
visb: bmap.bm_bool = bmap.bm_bool()
|
||||
bmap.BM3dObject_GetVisibility(self._get_pointer(), self._get_ckid(), ctypes.byref(visb))
|
||||
return visb.value
|
||||
|
||||
def set_visibility(self, visb_: bool) -> None:
|
||||
visb: bmap.bm_bool = bmap.bm_bool(visb_)
|
||||
bmap.BM3dObject_SetVisibility(self._get_pointer(), self._get_ckid(), visb)
|
||||
|
||||
class BMGroup(BMObject):
|
||||
def add_object(self, member: BM3dObject) -> None:
|
||||
bmap.BMGroup_AddObject(self._get_pointer(), self._get_ckid(), member._get_ckid())
|
||||
|
||||
def get_object_count(self) -> int:
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
bmap.BMGroup_GetObjectCount(self._get_pointer(), self._get_ckid(), ctypes.byref(csize))
|
||||
return csize.value
|
||||
|
||||
def get_objects(self) -> typing.Iterator[BM3dObject]:
|
||||
csize: int = self.get_object_count()
|
||||
|
||||
# iterate list
|
||||
cidx: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
retid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
for i in range(csize):
|
||||
cidx.value = i
|
||||
bmap.BMGroup_GetObject(self._get_pointer(), self._get_ckid(), cidx, ctypes.byref(retid))
|
||||
# return visitor
|
||||
yield BM3dObject(self._get_pointer(), retid)
|
||||
|
||||
class BMFileReader(_AbstractPointer):
|
||||
def __init__(self, file_name_: str, temp_folder_: str, texture_folder_: str, encodings_: tuple[str]):
|
||||
# create param
|
||||
file_name: bmap.bm_CKSTRING = bmap.bm_CKSTRING(file_name_.encode(g_BMapEncoding))
|
||||
temp_folder: bmap.bm_CKSTRING = bmap.bm_CKSTRING(temp_folder_.encode(g_BMapEncoding))
|
||||
texture_folder: bmap.bm_CKSTRING = bmap.bm_CKSTRING(texture_folder_.encode(g_BMapEncoding))
|
||||
encoding_count: bmap.bm_CKDWORD = bmap.bm_CKDWORD(len(encodings_))
|
||||
encodings: ctypes.Array = (bmap.bm_CKSTRING * len(encodings_))(
|
||||
*(strl.encode(g_BMapEncoding) for strl in encodings_)
|
||||
)
|
||||
out_file: bmap.bm_void_p = bmap.bm_void_p()
|
||||
# exec
|
||||
bmap.BMFile_Load(
|
||||
file_name, temp_folder, texture_folder, _g_RawCallback,
|
||||
encoding_count, encodings,
|
||||
ctypes.byref(out_file)
|
||||
)
|
||||
# init self
|
||||
_AbstractPointer.__init__(self, out_file)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.dispose()
|
||||
|
||||
def dispose(self) -> None:
|
||||
if self._is_valid():
|
||||
bmap.BMFile_Free(self._get_pointer())
|
||||
self._set_pointer(g_InvalidPtr)
|
||||
|
||||
def __get_ckobject_count(self,
|
||||
count_getter: typing.Callable[[bmap.bm_void_p, bmap.bm_CKDWORD_p], bool]) -> int:
|
||||
# get size
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
count_getter(self._get_pointer(), ctypes.byref(csize))
|
||||
return csize.value
|
||||
|
||||
def __get_ckobjects(self,
|
||||
class_type: type[TCKObj],
|
||||
count_getter: typing.Callable[[bmap.bm_void_p, bmap.bm_CKDWORD_p], bool],
|
||||
obj_getter: typing.Callable[[bmap.bm_void_p, bmap.bm_CKDWORD, bmap.bm_CKID_p], bool]) -> typing.Iterator[TCKObj]:
|
||||
# get size first
|
||||
csize: int = self.__get_ckobject_count(count_getter)
|
||||
|
||||
# iterate list
|
||||
cidx: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
retid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
for i in range(csize):
|
||||
cidx.value = i
|
||||
obj_getter(self._get_pointer(), cidx, ctypes.byref(retid))
|
||||
# yield return constructed obj visitor
|
||||
yield class_type(self._get_pointer(), retid)
|
||||
|
||||
def get_texture_count(self) -> int:
|
||||
return self.__get_ckobject_count(bmap.BMFile_GetTextureCount)
|
||||
def get_textures(self) -> typing.Iterator[BMTexture]:
|
||||
return self.__get_ckobjects(
|
||||
BMTexture,
|
||||
bmap.BMFile_GetTextureCount,
|
||||
bmap.BMFile_GetTexture
|
||||
)
|
||||
|
||||
def get_material_count(self) -> int:
|
||||
return self.__get_ckobject_count(bmap.BMFile_GetMaterialCount)
|
||||
def get_materials(self) -> typing.Iterator[BMMaterial]:
|
||||
return self.__get_ckobjects(
|
||||
BMMaterial,
|
||||
bmap.BMFile_GetMaterialCount,
|
||||
bmap.BMFile_GetMaterial
|
||||
)
|
||||
|
||||
def get_mesh_count(self) -> int:
|
||||
return self.__get_ckobject_count(bmap.BMFile_GetMeshCount)
|
||||
def get_meshs(self) -> typing.Iterator[BMMesh]:
|
||||
return self.__get_ckobjects(
|
||||
BMMesh,
|
||||
bmap.BMFile_GetMeshCount,
|
||||
bmap.BMFile_GetMesh
|
||||
)
|
||||
|
||||
def get_3dobject_count(self) -> int:
|
||||
return self.__get_ckobject_count(bmap.BMFile_Get3dObjectCount)
|
||||
def get_3dobjects(self) -> typing.Iterator[BM3dObject]:
|
||||
return self.__get_ckobjects(
|
||||
BM3dObject,
|
||||
bmap.BMFile_Get3dObjectCount,
|
||||
bmap.BMFile_Get3dObject
|
||||
)
|
||||
|
||||
def get_group_count(self) -> int:
|
||||
return self.__get_ckobject_count(bmap.BMFile_GetGroupCount)
|
||||
def get_groups(self) -> typing.Iterator[BMGroup]:
|
||||
return self.__get_ckobjects(
|
||||
BMGroup,
|
||||
bmap.BMFile_GetGroupCount,
|
||||
bmap.BMFile_GetGroup
|
||||
)
|
||||
|
||||
class BMFileWriter(_AbstractPointer):
|
||||
def __init__(self, temp_folder_: str, texture_folder_: str, encodings_: tuple[str]):
|
||||
# create param
|
||||
temp_folder: bmap.bm_CKSTRING = bmap.bm_CKSTRING(temp_folder_.encode(g_BMapEncoding))
|
||||
texture_folder: bmap.bm_CKSTRING = bmap.bm_CKSTRING(texture_folder_.encode(g_BMapEncoding))
|
||||
encoding_count: bmap.bm_CKDWORD = bmap.bm_CKDWORD(len(encodings_))
|
||||
encodings: ctypes.Array = (bmap.bm_CKSTRING * len(encodings_))(
|
||||
*(strl.encode(g_BMapEncoding) for strl in encodings_)
|
||||
)
|
||||
out_file: bmap.bm_void_p = bmap.bm_void_p()
|
||||
# exec
|
||||
bmap.BMFile_Create(
|
||||
temp_folder, texture_folder, _g_RawCallback,
|
||||
encoding_count, encodings,
|
||||
ctypes.byref(out_file)
|
||||
)
|
||||
# init self
|
||||
_AbstractPointer.__init__(self, out_file)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.dispose()
|
||||
|
||||
def save(self, file_name_: str, texture_save_opt_: virtools_types.CK_TEXTURE_SAVEOPTIONS, use_compress_: bool, compress_level_: int) -> None:
|
||||
# create param
|
||||
file_name: bmap.bm_CKSTRING = bmap.bm_CKSTRING(file_name_.encode(g_BMapEncoding))
|
||||
texture_save_opt: bmap.bm_enum = bmap.bm_enum(texture_save_opt_.value)
|
||||
use_compress: bmap.bm_bool = bmap.bm_bool(use_compress_)
|
||||
compress_level: bmap.bm_CKINT = bmap.bm_CKINT(compress_level_)
|
||||
# exec
|
||||
bmap.BMFile_Save(self._get_pointer(), file_name, texture_save_opt, use_compress, compress_level)
|
||||
|
||||
def dispose(self) -> None:
|
||||
if self._is_valid():
|
||||
bmap.BMFile_Free(self._get_pointer())
|
||||
self._set_pointer(g_InvalidPtr)
|
||||
|
||||
def __create_ckobject(self,
|
||||
class_type: type[TCKObj],
|
||||
creator: typing.Callable[[bmap.bm_void_p, bmap.bm_CKID_p], bool]) -> TCKObj:
|
||||
# prepare id container
|
||||
retid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
# create new one
|
||||
creator(self._get_pointer(), ctypes.byref(retid))
|
||||
# return visitor
|
||||
return class_type(self._get_pointer(), retid)
|
||||
|
||||
def create_texture(self) -> BMTexture:
|
||||
return self.__create_ckobject(
|
||||
BMTexture,
|
||||
bmap.BMFile_CreateTexture
|
||||
)
|
||||
|
||||
def create_material(self) -> BMMaterial:
|
||||
return self.__create_ckobject(
|
||||
BMMaterial,
|
||||
bmap.BMFile_CreateMaterial
|
||||
)
|
||||
|
||||
def create_mesh(self) -> BMMesh:
|
||||
return self.__create_ckobject(
|
||||
BMMesh,
|
||||
bmap.BMFile_CreateMesh
|
||||
)
|
||||
|
||||
def create_3dobject(self) -> BM3dObject:
|
||||
return self.__create_ckobject(
|
||||
BM3dObject,
|
||||
bmap.BMFile_Create3dObject
|
||||
)
|
||||
|
||||
def create_group(self) -> BMGroup:
|
||||
return self.__create_ckobject(
|
||||
BMGroup,
|
||||
bmap.BMFile_CreateGroup
|
||||
)
|
||||
|
||||
class BMMeshTrans(_AbstractPointer):
|
||||
def __init__(self):
|
||||
ptr: bmap.bm_void_p = bmap.bm_void_p()
|
||||
bmap.BMMeshTrans_New(ctypes.byref(ptr))
|
||||
_AbstractPointer.__init__(self, ptr)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.dispose()
|
||||
|
||||
def dispose(self) -> None:
|
||||
if self._is_valid():
|
||||
bmap.BMMeshTrans_Delete(self._get_pointer())
|
||||
self._set_pointer(g_InvalidPtr)
|
||||
|
||||
def parse(self, bmfile: BMFileWriter, objmesh: BMMesh) -> None:
|
||||
bmap.BMMeshTrans_Parse(self._get_pointer(), bmfile._get_pointer(), objmesh._get_ckid())
|
||||
|
||||
def prepare_vertex(self, count: int, itor: typing.Iterator[virtools_types.VxVector3]) -> None:
|
||||
# prepare count first
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count)
|
||||
bmap.BMMeshTrans_PrepareVertexCount(self._get_pointer(), csize)
|
||||
# get raw pointer and conv to float ptr for convenient visit
|
||||
raw_vector: bmap.bm_VxVector3_p = bmap.bm_VxVector3_p()
|
||||
bmap.BMMeshTrans_PrepareVertex(self._get_pointer(), ctypes.byref(raw_vector))
|
||||
# set by pointer
|
||||
_vxvector3_assigner(raw_vector, count, itor)
|
||||
|
||||
def prepare_normal(self, count: int, itor: typing.Iterator[virtools_types.VxVector3]) -> None:
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count)
|
||||
bmap.BMMeshTrans_PrepareNormalCount(self._get_pointer(), csize)
|
||||
|
||||
raw_vector: bmap.bm_VxVector3_p = bmap.bm_VxVector3_p()
|
||||
bmap.BMMeshTrans_PrepareNormal(self._get_pointer(), ctypes.byref(raw_vector))
|
||||
|
||||
_vxvector3_assigner(raw_vector, count, itor)
|
||||
|
||||
def prepare_uv(self, count: int, itor: typing.Iterator[virtools_types.VxVector2]) -> None:
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count)
|
||||
bmap.BMMeshTrans_PrepareUVCount(self._get_pointer(), csize)
|
||||
|
||||
raw_vector: bmap.bm_VxVector2_p = bmap.bm_VxVector2_p()
|
||||
bmap.BMMeshTrans_PrepareUV(self._get_pointer(), ctypes.byref(raw_vector))
|
||||
|
||||
_vxvector2_assigner(raw_vector, count, itor)
|
||||
|
||||
def prepare_mtl_slot(self, count: int, itor: typing.Iterator[BMMaterial | None]) -> None:
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count)
|
||||
bmap.BMMeshTrans_PrepareMtlSlotCount(self._get_pointer(), csize)
|
||||
|
||||
raw_ckid: bmap.bm_CKID_p = bmap.bm_CKID_p()
|
||||
bmap.BMMeshTrans_PrepareMtlSlot(self._get_pointer(), ctypes.byref(raw_ckid))
|
||||
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
usermtl: BMMaterial | None = next(itor)
|
||||
if usermtl is None:
|
||||
raw_ckid[idx] = g_InvalidCKID
|
||||
else:
|
||||
raw_ckid[idx] = usermtl._get_ckid().value
|
||||
idx += 1
|
||||
|
||||
def prepare_face(self,
|
||||
count: int,
|
||||
vec_idx: typing.Iterator[virtools_types.CKFaceIndices],
|
||||
nml_idx: typing.Iterator[virtools_types.CKFaceIndices],
|
||||
uv_idx: typing.Iterator[virtools_types.CKFaceIndices],
|
||||
mtl_idx: typing.Iterator[int]) -> None:
|
||||
# prepare face size
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count)
|
||||
bmap.BMMeshTrans_PrepareFaceCount(self._get_pointer(), csize)
|
||||
|
||||
# get 4 raw pointer for following assign
|
||||
raw_vec_idx: bmap.bm_CKDWORD_p = bmap.bm_CKDWORD_p()
|
||||
raw_nml_idx: bmap.bm_CKDWORD_p = bmap.bm_CKDWORD_p()
|
||||
raw_uv_idx: bmap.bm_CKDWORD_p = bmap.bm_CKDWORD_p()
|
||||
raw_mtl_idx: bmap.bm_CKDWORD_p = bmap.bm_CKDWORD_p()
|
||||
bmap.BMMeshTrans_PrepareFaceVertexIndices(self._get_pointer(), ctypes.byref(raw_vec_idx))
|
||||
bmap.BMMeshTrans_PrepareFaceNormalIndices(self._get_pointer(), ctypes.byref(raw_nml_idx))
|
||||
bmap.BMMeshTrans_PrepareFaceUVIndices(self._get_pointer(), ctypes.byref(raw_uv_idx))
|
||||
bmap.BMMeshTrans_PrepareFaceMtlSlot(self._get_pointer(), ctypes.byref(raw_mtl_idx))
|
||||
|
||||
# iterate and assign
|
||||
# assigne triple indices
|
||||
_ckfaceindices_assigner(raw_vec_idx, count, vec_idx)
|
||||
_ckfaceindices_assigner(raw_nml_idx, count, nml_idx)
|
||||
_ckfaceindices_assigner(raw_uv_idx, count, uv_idx)
|
||||
# assign mtl index
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
raw_mtl_idx[idx] = next(mtl_idx)
|
||||
idx += 1
|
||||
|
||||
#endregion
|
318
bbp_ng/PyBMap/virtools_types.py
Normal file
318
bbp_ng/PyBMap/virtools_types.py
Normal file
@ -0,0 +1,318 @@
|
||||
import typing, enum
|
||||
|
||||
ConstVxVector2 = tuple[float, float]
|
||||
ConstVxVector3 = tuple[float, float, float]
|
||||
ConstVxVector4 = tuple[float, float, float, float]
|
||||
|
||||
class VxVector2():
|
||||
x: float
|
||||
y: float
|
||||
|
||||
def __init__(self, _x: float = 0.0, _y: float = 0.0):
|
||||
self.x = _x
|
||||
self.y = _y
|
||||
|
||||
def from_const(self, cv: ConstVxVector2) -> None:
|
||||
(self.x, self.y, ) = cv
|
||||
|
||||
def to_const(self) -> ConstVxVector2:
|
||||
return (self.x, self.y, )
|
||||
|
||||
class VxVector3():
|
||||
x: float
|
||||
y: float
|
||||
z: float
|
||||
|
||||
def __init__(self, _x: float = 0.0, _y: float = 0.0, _z: float = 0.0):
|
||||
self.x = _x
|
||||
self.y = _y
|
||||
self.z = _z
|
||||
|
||||
def from_const(self, cv: ConstVxVector3) -> None:
|
||||
(self.x, self.y, self.z) = cv
|
||||
|
||||
def to_const(self) -> ConstVxVector3:
|
||||
return (self.x, self.y, self.z)
|
||||
|
||||
ConstCKFaceIndices = tuple[int, int, int]
|
||||
|
||||
class CKFaceIndices():
|
||||
i1: int
|
||||
i2: int
|
||||
i3: int
|
||||
|
||||
def __init__(self, i1_: int = 0, i2_: int = 0, i3_: int = 0):
|
||||
self.i1 = i1_
|
||||
self.i2 = i2_
|
||||
self.i3 = i3_
|
||||
|
||||
def from_const(self, cv: ConstCKFaceIndices) -> None:
|
||||
(self.i1, self.i2, self.i3) = cv
|
||||
|
||||
def to_const(self) -> ConstCKFaceIndices:
|
||||
return (self.i1, self.i2, self.i3)
|
||||
|
||||
ConstVxColorRGBA = tuple[float, float, float, float]
|
||||
ConstVxColorRGB = tuple[float, float, float]
|
||||
|
||||
class VxColor():
|
||||
"""
|
||||
The Color struct support RGBA.
|
||||
"""
|
||||
a: float
|
||||
r: float
|
||||
g: float
|
||||
b: float
|
||||
def __init__(self, _r: float = 0.0, _g: float = 0.0, _b: float = 0.0, _a: float = 1.0):
|
||||
self.r = _r
|
||||
self.g = _g
|
||||
self.b = _b
|
||||
self.a = _a
|
||||
self.regulate()
|
||||
|
||||
def to_const_rgba(self) -> ConstVxColorRGBA:
|
||||
return (self.r, self.g, self.b, self.a)
|
||||
|
||||
def to_const_rgb(self) -> ConstVxColorRGB:
|
||||
return (self.r, self.g, self.b)
|
||||
|
||||
def from_const_rgba(self, val: ConstVxColorRGBA) -> None:
|
||||
(self.r, self.g, self.b, self.a) = val
|
||||
self.regulate()
|
||||
|
||||
def from_const_rgb(self, val: ConstVxColorRGB) -> None:
|
||||
(self.r, self.g, self.b) = val
|
||||
self.a = 1.0
|
||||
self.regulate()
|
||||
|
||||
def from_dword(self, val: int) -> None:
|
||||
self.b = float(val & 0xFF) / 255.0
|
||||
val >>= 8
|
||||
self.g = float(val & 0xFF) / 255.0
|
||||
val >>= 8
|
||||
self.r = float(val & 0xFF) / 255.0
|
||||
val >>= 8
|
||||
self.a = float(val & 0xFF) / 255.0
|
||||
val >>= 8
|
||||
|
||||
def to_dword(self) -> int:
|
||||
# regulate self
|
||||
self.regulate()
|
||||
# construct value
|
||||
val: int = 0
|
||||
val |= int(self.a * 255)
|
||||
val <<= 8
|
||||
val |= int(self.r * 255)
|
||||
val <<= 8
|
||||
val |= int(self.g * 255)
|
||||
val <<= 8
|
||||
val |= int(self.b * 255)
|
||||
return val
|
||||
|
||||
def clone(self):
|
||||
return VxColor(self.r, self.g, self.b, self.a)
|
||||
|
||||
@staticmethod
|
||||
def _clamp_factor(val: float) -> float:
|
||||
if val > 1.0: return 1.0
|
||||
elif val < 0.0: return 0.0
|
||||
else: return val
|
||||
|
||||
def regulate(self):
|
||||
self.a = VxColor._clamp_factor(self.a)
|
||||
self.r = VxColor._clamp_factor(self.r)
|
||||
self.g = VxColor._clamp_factor(self.g)
|
||||
self.b = VxColor._clamp_factor(self.b)
|
||||
|
||||
ConstVxMatrix = tuple[
|
||||
float, float, float, float,
|
||||
float, float, float, float,
|
||||
float, float, float, float,
|
||||
float, float, float, float
|
||||
]
|
||||
|
||||
class VxMatrix():
|
||||
"""
|
||||
The Matrix representation.
|
||||
The bracket statement exactly equal with Virtools.
|
||||
"""
|
||||
data: list[list[float]]
|
||||
|
||||
def __init__(self):
|
||||
# init array
|
||||
self.data = [[0] * 4 for i in range(4)]
|
||||
# set to identy
|
||||
self.reset()
|
||||
|
||||
def _get_raw(self) -> list[list[float]]:
|
||||
return self.data
|
||||
|
||||
def reset(self) -> None:
|
||||
# reset to identy
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
self.data[i][j] = 0.0
|
||||
|
||||
self.data[0][0] = 1.0
|
||||
self.data[1][1] = 1.0
|
||||
self.data[2][2] = 1.0
|
||||
self.data[3][3] = 1.0
|
||||
|
||||
def from_const(self, cm: ConstVxMatrix) -> None:
|
||||
(
|
||||
self.data[0][0], self.data[0][1], self.data[0][2], self.data[0][3],
|
||||
self.data[1][0], self.data[1][1], self.data[1][2], self.data[1][3],
|
||||
self.data[2][0], self.data[2][1], self.data[2][2], self.data[2][3],
|
||||
self.data[3][0], self.data[3][1], self.data[3][2], self.data[3][3]
|
||||
) = cm
|
||||
|
||||
def to_const(self) -> ConstVxMatrix:
|
||||
return (
|
||||
self.data[0][0], self.data[0][1], self.data[0][2], self.data[0][3],
|
||||
self.data[1][0], self.data[1][1], self.data[1][2], self.data[1][3],
|
||||
self.data[2][0], self.data[2][1], self.data[2][2], self.data[2][3],
|
||||
self.data[3][0], self.data[3][1], self.data[3][2], self.data[3][3]
|
||||
)
|
||||
|
||||
class CK_TEXTURE_SAVEOPTIONS(enum.IntEnum):
|
||||
"""!
|
||||
Specify the way textures or sprites will be saved
|
||||
"""
|
||||
CKTEXTURE_RAWDATA = 0 ##< Save raw data inside file. The bitmap is saved in a raw 32 bit per pixel format.
|
||||
CKTEXTURE_EXTERNAL = 1 ##< Store only the file name for the texture. The bitmap file must be present in the bitmap paths when loading the composition.
|
||||
CKTEXTURE_IMAGEFORMAT = 2 ##< Save using format specified. The bitmap data will be converted to the specified format by the correspondant bitmap plugin and saved inside file.
|
||||
CKTEXTURE_USEGLOBAL = 3 ##< Use Global settings, that is the settings given with CKContext::SetGlobalImagesSaveOptions. (Not valid when using CKContext::SetImagesSaveOptions).
|
||||
CKTEXTURE_INCLUDEORIGINALFILE = 4 ##< Insert original image file inside CMO file. The bitmap file that was used originally for the texture or sprite will be append to the composition file and extracted when the file is loaded.
|
||||
|
||||
class VX_PIXELFORMAT(enum.IntEnum):
|
||||
"""!
|
||||
Pixel format types.
|
||||
"""
|
||||
#UNKNOWN_PF = 0 ##< Unknown pixel format
|
||||
_32_ARGB8888 = 1 ##< 32-bit ARGB pixel format with alpha
|
||||
_32_RGB888 = 2 ##< 32-bit RGB pixel format without alpha
|
||||
_24_RGB888 = 3 ##< 24-bit RGB pixel format
|
||||
_16_RGB565 = 4 ##< 16-bit RGB pixel format
|
||||
_16_RGB555 = 5 ##< 16-bit RGB pixel format (5 bits per color)
|
||||
_16_ARGB1555 = 6 ##< 16-bit ARGB pixel format (5 bits per color + 1 bit for alpha)
|
||||
_16_ARGB4444 = 7 ##< 16-bit ARGB pixel format (4 bits per color)
|
||||
_8_RGB332 = 8 ##< 8-bit RGB pixel format
|
||||
_8_ARGB2222 = 9 ##< 8-bit ARGB pixel format
|
||||
_32_ABGR8888 = 10 ##< 32-bit ABGR pixel format
|
||||
_32_RGBA8888 = 11 ##< 32-bit RGBA pixel format
|
||||
_32_BGRA8888 = 12 ##< 32-bit BGRA pixel format
|
||||
_32_BGR888 = 13 ##< 32-bit BGR pixel format
|
||||
_24_BGR888 = 14 ##< 24-bit BGR pixel format
|
||||
_16_BGR565 = 15 ##< 16-bit BGR pixel format
|
||||
_16_BGR555 = 16 ##< 16-bit BGR pixel format (5 bits per color)
|
||||
_16_ABGR1555 = 17 ##< 16-bit ABGR pixel format (5 bits per color + 1 bit for alpha)
|
||||
_16_ABGR4444 = 18 ##< 16-bit ABGR pixel format (4 bits per color)
|
||||
_DXT1 = 19 ##< S3/DirectX Texture Compression 1
|
||||
_DXT2 = 20 ##< S3/DirectX Texture Compression 2
|
||||
_DXT3 = 21 ##< S3/DirectX Texture Compression 3
|
||||
_DXT4 = 22 ##< S3/DirectX Texture Compression 4
|
||||
_DXT5 = 23 ##< S3/DirectX Texture Compression 5
|
||||
_16_V8U8 = 24 ##< 16-bit Bump Map format format (8 bits per color)
|
||||
_32_V16U16 = 25 ##< 32-bit Bump Map format format (16 bits per color)
|
||||
_16_L6V5U5 = 26 ##< 16-bit Bump Map format format with luminance
|
||||
_32_X8L8V8U8 = 27 ##< 32-bit Bump Map format format with luminance
|
||||
_8_ABGR8888_CLUT = 28 ##< 8 bits indexed CLUT (ABGR)
|
||||
_8_ARGB8888_CLUT = 29 ##< 8 bits indexed CLUT (ARGB)
|
||||
_4_ABGR8888_CLUT = 30 ##< 4 bits indexed CLUT (ABGR)
|
||||
_4_ARGB8888_CLUT = 31 ##< 4 bits indexed CLUT (ARGB)
|
||||
|
||||
class VXTEXTURE_BLENDMODE(enum.IntEnum):
|
||||
"""!
|
||||
Blend Mode Flags
|
||||
"""
|
||||
VXTEXTUREBLEND_DECAL = 1 ##< Texture replace any material information
|
||||
VXTEXTUREBLEND_MODULATE = 2 ##< Texture and material are combine. Alpha information of the texture replace material alpha component.
|
||||
VXTEXTUREBLEND_DECALALPHA = 3 ##< Alpha information in the texture specify how material and texture are combined. Alpha information of the texture replace material alpha component.
|
||||
VXTEXTUREBLEND_MODULATEALPHA = 4 ##< Alpha information in the texture specify how material and texture are combined
|
||||
VXTEXTUREBLEND_DECALMASK = 5
|
||||
VXTEXTUREBLEND_MODULATEMASK = 6
|
||||
VXTEXTUREBLEND_COPY = 7 ##< Equivalent to DECAL
|
||||
VXTEXTUREBLEND_ADD = 8
|
||||
VXTEXTUREBLEND_DOTPRODUCT3 = 9 ##< Perform a Dot Product 3 between texture (normal map) and a referential vector given in VXRENDERSTATE_TEXTUREFACTOR.
|
||||
VXTEXTUREBLEND_MAX = 10
|
||||
|
||||
class VXTEXTURE_FILTERMODE(enum.IntEnum):
|
||||
"""!
|
||||
Filter Mode Options
|
||||
"""
|
||||
VXTEXTUREFILTER_NEAREST = 1 ##< No Filter
|
||||
VXTEXTUREFILTER_LINEAR = 2 ##< Bilinear Interpolation
|
||||
VXTEXTUREFILTER_MIPNEAREST = 3 ##< Mip mapping
|
||||
VXTEXTUREFILTER_MIPLINEAR = 4 ##< Mip Mapping with Bilinear interpolation
|
||||
VXTEXTUREFILTER_LINEARMIPNEAREST = 5 ##< Mip Mapping with Bilinear interpolation between mipmap levels.
|
||||
VXTEXTUREFILTER_LINEARMIPLINEAR = 6 ##< Trilinear Filtering
|
||||
VXTEXTUREFILTER_ANISOTROPIC = 7 ##< Anisotropic filtering
|
||||
|
||||
class VXTEXTURE_ADDRESSMODE(enum.IntEnum):
|
||||
"""!
|
||||
Texture addressing modes.
|
||||
"""
|
||||
VXTEXTURE_ADDRESSWRAP = 1 ##< Default mesh wrap mode is used (see CKMesh::SetWrapMode)
|
||||
VXTEXTURE_ADDRESSMIRROR = 2 ##< Texture coordinates outside the range [0..1] are flipped evenly.
|
||||
VXTEXTURE_ADDRESSCLAMP = 3 ##< Texture coordinates greater than 1.0 are set to 1.0, and values less than 0.0 are set to 0.0.
|
||||
VXTEXTURE_ADDRESSBORDER = 4 ##< When texture coordinates are greater than 1.0 or less than 0.0 texture is set to a color defined in CKMaterial::SetTextureBorderColor.
|
||||
VXTEXTURE_ADDRESSMIRRORONCE = 5 ##<
|
||||
|
||||
class VXBLEND_MODE(enum.IntEnum):
|
||||
"""!
|
||||
Blending Mode options
|
||||
"""
|
||||
VXBLEND_ZERO = 1 ##< Blend factor is (0, 0, 0, 0).
|
||||
VXBLEND_ONE = 2 ##< Blend factor is (1, 1, 1, 1).
|
||||
VXBLEND_SRCCOLOR = 3 ##< Blend factor is (Rs, Gs, Bs, As).
|
||||
VXBLEND_INVSRCCOLOR = 4 ##< Blend factor is (1-Rs, 1-Gs, 1-Bs, 1-As).
|
||||
VXBLEND_SRCALPHA = 5 ##< Blend factor is (As, As, As, As).
|
||||
VXBLEND_INVSRCALPHA = 6 ##< Blend factor is (1-As, 1-As, 1-As, 1-As).
|
||||
VXBLEND_DESTALPHA = 7 ##< Blend factor is (Ad, Ad, Ad, Ad).
|
||||
VXBLEND_INVDESTALPHA = 8 ##< Blend factor is (1-Ad, 1-Ad, 1-Ad, 1-Ad).
|
||||
VXBLEND_DESTCOLOR = 9 ##< Blend factor is (Rd, Gd, Bd, Ad).
|
||||
VXBLEND_INVDESTCOLOR = 10 ##< Blend factor is (1-Rd, 1-Gd, 1-Bd, 1-Ad).
|
||||
VXBLEND_SRCALPHASAT = 11 ##< Blend factor is (f, f, f, 1); f = min(As, 1-Ad).
|
||||
#VXBLEND_BOTHSRCALPHA = 12 ##< Source blend factor is (As, As, As, As) and destination blend factor is (1-As, 1-As, 1-As, 1-As)
|
||||
#VXBLEND_BOTHINVSRCALPHA = 13 ##< Source blend factor is (1-As, 1-As, 1-As, 1-As) and destination blend factor is (As, As, As, As)
|
||||
|
||||
class VXFILL_MODE(enum.IntEnum):
|
||||
"""!
|
||||
Fill Mode Options
|
||||
"""
|
||||
VXFILL_POINT = 1 ##< Vertices rendering
|
||||
VXFILL_WIREFRAME = 2 ##< Edges rendering
|
||||
VXFILL_SOLID = 3 ##< Face rendering
|
||||
|
||||
class VXSHADE_MODE(enum.IntEnum):
|
||||
"""!
|
||||
Shade Mode Options
|
||||
"""
|
||||
VXSHADE_FLAT = 1 ##< Flat Shading
|
||||
VXSHADE_GOURAUD = 2 ##< Gouraud Shading
|
||||
VXSHADE_PHONG = 3 ##< Phong Shading (Not yet supported by most implementation)
|
||||
|
||||
class VXCMPFUNC(enum.IntEnum):
|
||||
"""!
|
||||
Comparison Function
|
||||
"""
|
||||
VXCMP_NEVER = 1 ##< Always fail the test.
|
||||
VXCMP_LESS = 2 ##< Accept if value if less than current value.
|
||||
VXCMP_EQUAL = 3 ##< Accept if value if equal than current value.
|
||||
VXCMP_LESSEQUAL = 4 ##< Accept if value if less or equal than current value.
|
||||
VXCMP_GREATER = 5 ##< Accept if value if greater than current value.
|
||||
VXCMP_NOTEQUAL = 6 ##< Accept if value if different than current value.
|
||||
VXCMP_GREATEREQUAL = 7 ##< Accept if value if greater or equal current value.
|
||||
VXCMP_ALWAYS = 8 ##< Always accept the test.
|
||||
|
||||
class VXMESH_LITMODE(enum.IntEnum):
|
||||
"""!
|
||||
{filename:VXMESH_LITMODE}
|
||||
Summary: Mesh lighting options
|
||||
|
||||
Remarks:
|
||||
+ The VXMESH_LITMODE is used by CKMesh::SetLitMode to specify how lighting is done.
|
||||
See Also: CKMaterial,CKMesh
|
||||
"""
|
||||
VX_PRELITMESH = 0 ##< Lighting use color information store with vertices
|
||||
VX_LITMESH = 1 ##< Lighting is done by renderer using normals and face material information.
|
335
bbp_ng/UTIL_ballance_texture.py
Normal file
335
bbp_ng/UTIL_ballance_texture.py
Normal file
@ -0,0 +1,335 @@
|
||||
import bpy, bpy_extras
|
||||
import typing, os
|
||||
from . import PROP_preferences
|
||||
from . import UTIL_functions
|
||||
|
||||
## Ballance Texture Usage
|
||||
# The aim of this module is to make sure every Ballance texture only have 1 instance in Blender as much as we can
|
||||
# (it mean that if user force to add multiple textures, we can not stop them)
|
||||
#
|
||||
# All image loading and saving operation should be operated via this module, no matter what your are loading is or is not Ballance textures.
|
||||
# This module provide a universal way to check whether texture is a part of Ballance textures and use different strategy to load them.
|
||||
#
|
||||
# The loading and saving of textures frequently happend when importing or exporting, there is 2 example about them.
|
||||
# ```
|
||||
# # bmx loading example
|
||||
# bmx_texture = blabla()
|
||||
# if bmx_texture.is_external():
|
||||
# tex = UTIL_ballance_texture.load_ballance_texture(bmx_texture.filename)
|
||||
# else:
|
||||
# tex = UTIL_ballance_texture.load_other_texture(os.path.join(tempfolder, 'Textures', bmx_texture.filename))
|
||||
# texture_process(tex) # process loaded texture
|
||||
#
|
||||
# # nmo loading example
|
||||
# vt_texture = blabla()
|
||||
# place_to_load = ""
|
||||
# if vt_texture.is_raw_data():
|
||||
# place_to_load = allocate_place()
|
||||
# save_vt_raw_data_texture(vt_texture, place_to_load)
|
||||
# if vt_texture.is_original_file() or vt_texture.is_external():
|
||||
# place_to_load = vt_texture.filename
|
||||
#
|
||||
# try_filename = UTIL_ballance_texture.get_ballance_texture_filename(place_to_load)
|
||||
# if try_filename:
|
||||
# # load as ballance texture
|
||||
# tex = UTIL_ballance_texture.load_ballance_texture(try_filename)
|
||||
# else:
|
||||
# # load as other texture
|
||||
# tex = UTIL_ballance_texture.load_other_texture(place_to_load)
|
||||
# texture_process(tex) # process loaded texture
|
||||
#
|
||||
# ```
|
||||
#
|
||||
# ```
|
||||
# # bmx saving example
|
||||
# tex: bpy.types.Image = texture_getter()
|
||||
# try_filename = UTIL_ballance_texture.get_ballance_texture_filename(
|
||||
# UTIL_ballance_texture.get_texture_filepath(tex))
|
||||
# if try_filename:
|
||||
# write_external_filename(try_filename)
|
||||
# else:
|
||||
# realpath = UTIL_ballance_texture.generate_other_texture_save_path(tex, tempfolder)
|
||||
# UTIL_ballance_texture.save_other_texture(tex, realpath)
|
||||
# write_filename(realpath)
|
||||
#
|
||||
# ```
|
||||
|
||||
#region Ballance Texture Assist Functions
|
||||
|
||||
_g_BallanceTextureFileNames: set[str] = set((
|
||||
# "atari.avi",
|
||||
"atari.bmp",
|
||||
"Ball_LightningSphere1.bmp",
|
||||
"Ball_LightningSphere2.bmp",
|
||||
"Ball_LightningSphere3.bmp",
|
||||
"Ball_Paper.bmp",
|
||||
"Ball_Stone.bmp",
|
||||
"Ball_Wood.bmp",
|
||||
"Brick.bmp",
|
||||
"Button01_deselect.tga",
|
||||
"Button01_select.tga",
|
||||
"Button01_special.tga",
|
||||
"Column_beige.bmp",
|
||||
"Column_beige_fade.tga",
|
||||
"Column_blue.bmp",
|
||||
"Cursor.tga",
|
||||
"Dome.bmp",
|
||||
"DomeEnvironment.bmp",
|
||||
"DomeShadow.tga",
|
||||
"ExtraBall.bmp",
|
||||
"ExtraParticle.bmp",
|
||||
"E_Holzbeschlag.bmp",
|
||||
"FloorGlow.bmp",
|
||||
"Floor_Side.bmp",
|
||||
"Floor_Top_Border.bmp",
|
||||
"Floor_Top_Borderless.bmp",
|
||||
"Floor_Top_Checkpoint.bmp",
|
||||
"Floor_Top_Flat.bmp",
|
||||
"Floor_Top_Profil.bmp",
|
||||
"Floor_Top_ProfilFlat.bmp",
|
||||
"Font_1.tga",
|
||||
"Gravitylogo_intro.bmp",
|
||||
"HardShadow.bmp",
|
||||
"Laterne_Glas.bmp",
|
||||
"Laterne_Schatten.tga",
|
||||
"Laterne_Verlauf.tga",
|
||||
"Logo.bmp",
|
||||
"Metal_stained.bmp",
|
||||
"Misc_Ufo.bmp",
|
||||
"Misc_UFO_Flash.bmp",
|
||||
"Modul03_Floor.bmp",
|
||||
"Modul03_Wall.bmp",
|
||||
"Modul11_13_Wood.bmp",
|
||||
"Modul11_Wood.bmp",
|
||||
"Modul15.bmp",
|
||||
"Modul16.bmp",
|
||||
"Modul18.bmp",
|
||||
"Modul18_Gitter.tga",
|
||||
"Modul30_d_Seiten.bmp",
|
||||
"Particle_Flames.bmp",
|
||||
"Particle_Smoke.bmp",
|
||||
"PE_Bal_balloons.bmp",
|
||||
"PE_Bal_platform.bmp",
|
||||
"PE_Ufo_env.bmp",
|
||||
"Pfeil.tga",
|
||||
"P_Extra_Life_Oil.bmp",
|
||||
"P_Extra_Life_Particle.bmp",
|
||||
"P_Extra_Life_Shadow.bmp",
|
||||
"Rail_Environment.bmp",
|
||||
"sandsack.bmp",
|
||||
"SkyLayer.bmp",
|
||||
"Sky_Vortex.bmp",
|
||||
"Stick_Bottom.tga",
|
||||
"Stick_Stripes.bmp",
|
||||
"Target.bmp",
|
||||
"Tower_Roof.bmp",
|
||||
"Trafo_Environment.bmp",
|
||||
"Trafo_FlashField.bmp",
|
||||
"Trafo_Shadow_Big.tga",
|
||||
"Tut_Pfeil01.tga",
|
||||
"Tut_Pfeil_Hoch.tga",
|
||||
"Wolken_intro.tga",
|
||||
"Wood_Metal.bmp",
|
||||
"Wood_MetalStripes.bmp",
|
||||
"Wood_Misc.bmp",
|
||||
"Wood_Nailed.bmp",
|
||||
"Wood_Old.bmp",
|
||||
"Wood_Panel.bmp",
|
||||
"Wood_Plain.bmp",
|
||||
"Wood_Plain2.bmp",
|
||||
"Wood_Raft.bmp",
|
||||
))
|
||||
|
||||
def _get_ballance_texture_folder() -> str:
|
||||
"""!
|
||||
Get Ballance texture folder from preferences.
|
||||
|
||||
@exception BBPException Ballance texture folder is not set in preferences
|
||||
|
||||
@return The path to Ballance texture folder.
|
||||
"""
|
||||
|
||||
pref: PROP_preferences.RawPreferences = PROP_preferences.get_raw_preferences()
|
||||
if not pref.has_valid_blc_tex_folder():
|
||||
raise UTIL_functions.BBPException("No valid Ballance texture folder in preferences.")
|
||||
|
||||
return pref.mBallanceTextureFolder
|
||||
|
||||
def _is_path_equal(path1: str, path2: str) -> bool:
|
||||
"""!
|
||||
Check whether 2 path are equal.
|
||||
|
||||
The checker will call os.path.normcase and os.path.normpath in series to regulate the give path.
|
||||
|
||||
@param path1[in] The given absolute path 1
|
||||
@param path2[in] The given absolute path 2
|
||||
@return True if equal.
|
||||
"""
|
||||
|
||||
return os.path.normpath(os.path.normcase(path1)) == os.path.normpath(os.path.normcase(path2))
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ballance Texture Detect Functions
|
||||
|
||||
def get_ballance_texture_filename(texpath: str) -> str | None:
|
||||
"""!
|
||||
Return the filename part for valid Ballance texture path.
|
||||
|
||||
If the file name part of given path is not a entry of Ballance texture file name list, function will return None immediately.
|
||||
Otherwise, function will check whether the given file path is really point to the Ballance texture folder.
|
||||
|
||||
@exception BBPException Ballance texture folder is not set in preferences
|
||||
|
||||
@param imgpath[in] Absolute path to texture.
|
||||
@return File name part of given texture path if given path is a valid Ballance texture path, or None if the path not point to a valid Ballance texture.
|
||||
"""
|
||||
|
||||
# check file name first
|
||||
filename: str = os.path.basename(texpath)
|
||||
if filename not in _g_BallanceTextureFileNames: return None
|
||||
|
||||
# if file name matched, check whether it located in ballance texture folder
|
||||
probe: str = os.path.join(_get_ballance_texture_folder(), filename)
|
||||
if not _is_path_equal(probe, texpath): return None
|
||||
|
||||
return filename
|
||||
|
||||
def is_ballance_texture_filepath(texpath: str) -> bool:
|
||||
"""!
|
||||
Check whether the given path is a valid Ballance texture.
|
||||
|
||||
Simply call get_ballance_texture_filename() and check whether it return string or None.
|
||||
|
||||
@exception BBPException Ballance texture folder is not set in preferences
|
||||
|
||||
@param imgpath[in] Absolute path to texture.
|
||||
@return True if it is Ballance texture.
|
||||
@see get_ballance_texture_filename
|
||||
"""
|
||||
|
||||
return get_ballance_texture_filename(texpath) is not None
|
||||
|
||||
def get_texture_filepath(tex: bpy.types.Image) -> str:
|
||||
"""!
|
||||
Get the file path referenced by the given texture.
|
||||
|
||||
This function will try getting the referenced file path of given texture, including packed or not packed texture.
|
||||
|
||||
This function will try resolving the file path when given texture is packed according to the path of
|
||||
current opend blender file and Ballance texture folder speficied in preferences.
|
||||
|
||||
If resolving failed, it may return blender packed data url, for example `\\./xxx.bmp`
|
||||
|
||||
@exception BBPException Ballance texture folder is not set in preferences
|
||||
|
||||
@param tex[in] The image where the file name need to be got.
|
||||
@return The resolved absolute file path.
|
||||
"""
|
||||
|
||||
# resolve image path
|
||||
absfilepath: str = bpy_extras.io_utils.path_reference(
|
||||
tex.filepath, bpy.data.filepath, _get_ballance_texture_folder(),
|
||||
'ABSOLUTE', "", None, None
|
||||
)
|
||||
|
||||
# return resolved path
|
||||
return absfilepath
|
||||
|
||||
def is_ballance_texture(tex: bpy.types.Image) -> bool:
|
||||
"""!
|
||||
Check whether the provided image is Ballance texture according to its referenced file path.
|
||||
|
||||
A simply calling combination of get_texture_filepath and is_ballance_texture_filepath
|
||||
|
||||
@exception BBPException Ballance texture folder is not set in preferences
|
||||
|
||||
@param tex[in] The texture to check.
|
||||
@return True if it is Ballance texture.
|
||||
@see get_texture_filepath, is_ballance_texture_filepath
|
||||
"""
|
||||
|
||||
return is_ballance_texture_filepath(get_texture_filepath(tex))
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ballance Texture Load & Save
|
||||
|
||||
def load_ballance_texture(texname: str) -> bpy.types.Image:
|
||||
"""!
|
||||
Load Ballance texture.
|
||||
|
||||
+ The returned image may be redirected to a existing image according to its file path, because all Ballance textures are shared.
|
||||
+ The loaded image is saved as external. No pack will be operated because plugin assume all user have Ballance texture folder.
|
||||
|
||||
@exception BBPException Ballance texture folder is not set in preferences, or provided file name is invalid.
|
||||
|
||||
@param texname[in] the file name (not the path) of loading Ballance texture. Invalid file name will raise exception.
|
||||
@return The loaded image.
|
||||
"""
|
||||
|
||||
# check texture name
|
||||
if texname not in _g_BallanceTextureFileNames:
|
||||
raise UTIL_functions.BBPException("Invalid Ballance texture file name.")
|
||||
|
||||
# load image
|
||||
# check existing image in any case. because we need make sure ballance texture is unique.
|
||||
filepath: str = os.path.join(_get_ballance_texture_folder(), texname)
|
||||
ret: bpy.types.Image = bpy.data.images.load(filepath, check_existing = True)
|
||||
|
||||
return ret
|
||||
|
||||
def load_other_texture(texname: str) -> bpy.types.Image:
|
||||
"""!
|
||||
Load the Texture which is not a part of Ballance texture.
|
||||
|
||||
This function is different with load_ballance_texture(). It can be seen as the opposition of load_ballance_texture().
|
||||
This function is used when loading the temp images created by BMX file resolving or Virtools engine.
|
||||
Because these temp file will be deleted after importing, this function need pack the loaded file into blender file immediately after loading.
|
||||
|
||||
@remark
|
||||
+ The loaded texture will be immediately packed into blender file.
|
||||
+ Loading will NOT check any loaded image according to file path.
|
||||
|
||||
@param texname[in] the absolute path to the loading image.
|
||||
@return The loaded image.
|
||||
"""
|
||||
|
||||
# load image first
|
||||
# always do not check the same image.
|
||||
ret: bpy.types.Image = bpy.data.images.load(texname, check_existing = False)
|
||||
|
||||
# then immediately pack it into file.
|
||||
ret.pack()
|
||||
|
||||
return ret
|
||||
|
||||
def generate_other_texture_save_path(tex: bpy.types.Image, file_folder: str) -> str:
|
||||
"""!
|
||||
Generate the path to saved file.
|
||||
|
||||
This function first get file name from texture, then combine it with given dest file folder,
|
||||
and return it.
|
||||
Frequently used with save_other_texture to create its parameter.
|
||||
|
||||
@param tex[in] The saving texture
|
||||
@param filepath[in] The absolute path to the folder where the texture will be saved.
|
||||
@return The path to saved file.
|
||||
"""
|
||||
return os.path.join(file_folder, os.path.basename(get_texture_filepath(tex)))
|
||||
|
||||
def save_other_texture(tex: bpy.types.Image, filepath: str) -> None:
|
||||
"""!
|
||||
Save the texture which is not a part of Ballance texture.
|
||||
|
||||
This function is frequently used when exporting something.
|
||||
Usually used to save the texture loaded by load_other_texture, because the texture loaded by load_ballance_texture do not need save.
|
||||
This function accept textures which is packed or not packed in blender file.
|
||||
|
||||
@param tex[in] The saving texture
|
||||
@param filepath[in] The absolute path to saving file.
|
||||
"""
|
||||
# MARK: must use keyword to assign param otherwise blender will throw error.
|
||||
tex.save(filepath = filepath)
|
||||
|
||||
#endregion
|
526
bbp_ng/UTIL_blender_mesh.py
Normal file
526
bbp_ng/UTIL_blender_mesh.py
Normal file
@ -0,0 +1,526 @@
|
||||
import bpy, bmesh, mathutils
|
||||
import typing, array, collections
|
||||
from . import UTIL_functions, UTIL_virtools_types
|
||||
|
||||
## Blender Mesh Usage
|
||||
# This module create a universal mesh visitor, including MeshReader, MeshWriter and MeshUVModifier
|
||||
# for every other possible module using.
|
||||
# Obviously, MeshReader is served for 2 exporter, MeshWriter is served for 2 importer.
|
||||
# MeshWriter also served for BMERevenge module and Ballance element loading.
|
||||
# MeshUVModifier is used by Flatten UV and Rail UV.
|
||||
#
|
||||
|
||||
#region Assist Functions
|
||||
|
||||
class FaceVertexData():
|
||||
mPosIdx: int
|
||||
mNmlIdx: int
|
||||
mUvIdx: int
|
||||
|
||||
def __init__(self, pos: int = 0, nml: int = 0, uv: int = 0):
|
||||
self.mPosIdx = pos
|
||||
self.mNmlIdx = nml
|
||||
self.mUvIdx = uv
|
||||
|
||||
class FaceData():
|
||||
## @remark List or tuple. List is convenient for adding and removing
|
||||
mIndices: tuple[FaceVertexData, ...] | list[FaceVertexData]
|
||||
## Face used material slot index
|
||||
# @remark If material slot is empty, or this face do not use material, set this value to 0.
|
||||
mMtlIdx: int
|
||||
|
||||
def __init__(self, indices: tuple[FaceVertexData, ...] | list[FaceVertexData] = tuple(), mtlidx: int = 0):
|
||||
self.mIndices = indices
|
||||
self.mMtlIdx = mtlidx
|
||||
|
||||
def conv_co(self) -> None:
|
||||
"""
|
||||
Change indice order between Virtools and Blender
|
||||
"""
|
||||
if isinstance(self.mIndices, list):
|
||||
self.mIndices.reverse()
|
||||
elif isinstance(self.mIndices, tuple):
|
||||
self.mIndices = self.mIndices[::-1]
|
||||
else:
|
||||
raise UTIL_functions.BBPException('invalid indices container.')
|
||||
|
||||
def is_indices_legal(self) -> bool:
|
||||
return len(self.mIndices) >= 3
|
||||
|
||||
class MeshWriterIngredient():
|
||||
mVertexPosition: typing.Iterator[UTIL_virtools_types.VxVector3] | None
|
||||
mVertexNormal: typing.Iterator[UTIL_virtools_types.VxVector3] | None
|
||||
mVertexUV: typing.Iterator[UTIL_virtools_types.VxVector2] | None
|
||||
mFace: typing.Iterator[FaceData] | None
|
||||
mMaterial: typing.Iterator[bpy.types.Material | None] | None
|
||||
|
||||
def __init__(self):
|
||||
self.mVertexPosition = None
|
||||
self.mVertexNormal = None
|
||||
self.mVertexUV = None
|
||||
self.mFace = None
|
||||
self.mMaterial = None
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
if self.mVertexPosition is None: return False
|
||||
if self.mVertexNormal is None: return False
|
||||
if self.mVertexUV is None: return False
|
||||
if self.mFace is None: return False
|
||||
if self.mMaterial is None: return False
|
||||
return True
|
||||
|
||||
def _flat_vxvector3(it: typing.Iterator[UTIL_virtools_types.VxVector3]) -> typing.Iterator[float]:
|
||||
for entry in it:
|
||||
yield entry.x
|
||||
yield entry.y
|
||||
yield entry.z
|
||||
|
||||
def _flat_vxvector2(it: typing.Iterator[UTIL_virtools_types.VxVector2]) -> typing.Iterator[float]:
|
||||
for entry in it:
|
||||
yield entry.x
|
||||
yield entry.y
|
||||
|
||||
def _flat_face_nml_index(nml_idx: array.array, nml_array: array.array) -> typing.Iterator[float]:
|
||||
for idx in nml_idx:
|
||||
pos: int = idx * 3
|
||||
yield nml_array[pos]
|
||||
yield nml_array[pos + 1]
|
||||
yield nml_array[pos + 2]
|
||||
|
||||
def _flat_face_uv_index(uv_idx: array.array, uv_array: array.array) -> typing.Iterator[float]:
|
||||
for idx in uv_idx:
|
||||
pos: int = idx * 2
|
||||
yield uv_array[pos]
|
||||
yield uv_array[pos + 1]
|
||||
|
||||
def _nest_custom_split_normal(nml_array: array.array) -> typing.Iterator[UTIL_virtools_types.ConstVxVector3]:
|
||||
# following statement create a iterator for normals array by `iter(nml_array)`
|
||||
# then triple it, because iterator is a reference type, so 3 items of this tuple is pointed to the same iterator and share the same iteration progress.
|
||||
# then use star macro to pass it to zip, it will cause zip receive 3 params pointing to the same iterator.
|
||||
# now zip() will call 3 param‘s __next__() function from left to right.
|
||||
# zip will get following iteration result because all iterator are the same one: (0, 1, 2), (3, 4, 5) and etc (number is index to corresponding value).
|
||||
# finally, use tuple to expand it to a tuple, not a generator.
|
||||
return tuple(zip(*(iter(nml_array), ) * 3))
|
||||
|
||||
class TemporaryMesh():
|
||||
"""
|
||||
|
||||
"""
|
||||
|
||||
__mBindingObject: bpy.types.Object
|
||||
__mTempMesh: bpy.types.Mesh
|
||||
|
||||
def __init__(self, binding_obj: bpy.types.Object):
|
||||
self.__mBindingObject = binding_obj
|
||||
self.__mTempMesh = None
|
||||
|
||||
if self.__mBindingObject.data is None:
|
||||
raise UTIL_functions.BBPException('try getting mesh from an object without mesh.')
|
||||
self.__mTempMesh = self.__mBindingObject.to_mesh()
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.dispose()
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
if self.__mBindingObject is None: return False
|
||||
if self.__mTempMesh is None: return False
|
||||
return True
|
||||
|
||||
def dispose(self) -> None:
|
||||
if self.is_valid():
|
||||
self.__mTempMesh = None
|
||||
self.__mBindingObject.to_mesh_clear()
|
||||
self.__mBindingObject = None
|
||||
|
||||
def get_temp_mesh(self) -> bpy.types.Mesh:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try calling invalid TemporaryMesh.')
|
||||
return self.__mTempMesh
|
||||
|
||||
#endregion
|
||||
|
||||
class MeshReader():
|
||||
"""
|
||||
The passed mesh must be created by bpy.types.Object.to_mesh() and destroyed by bpy.types.Object.to_mesh_clear().
|
||||
Because this class must trianglate mesh. To prevent change original mesh, this operations is essential.
|
||||
A helper class TemporaryMesh can help you do this.
|
||||
"""
|
||||
|
||||
__mAssocMesh: bpy.types.Mesh ##< The binding mesh for this reader. None if this reader is invalid.
|
||||
|
||||
def __init__(self, assoc_mesh: bpy.types.Mesh):
|
||||
self.__mAssocMesh = assoc_mesh
|
||||
|
||||
# triangulate temp mesh
|
||||
if self.is_valid():
|
||||
self.__triangulate_mesh()
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self.__mAssocMesh is not None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.dispose()
|
||||
|
||||
def dispose(self) -> None:
|
||||
if self.is_valid():
|
||||
# reset mesh
|
||||
self.__mAssocMesh = None
|
||||
|
||||
def get_vertex_position_count(self) -> int:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||
|
||||
return len(self.__mAssocMesh.vertices)
|
||||
|
||||
def get_vertex_position(self) -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||
|
||||
cache: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
||||
for vec in self.__mAssocMesh.vertices:
|
||||
cache.x = vec.co.x
|
||||
cache.y = vec.co.y
|
||||
cache.z = vec.co.z
|
||||
yield cache
|
||||
|
||||
def get_vertex_normal_count(self) -> int:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||
|
||||
# return loops count, equaling with face count * 3 in theory.
|
||||
return len(self.__mAssocMesh.loops)
|
||||
|
||||
def get_vertex_normal(self) -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||
|
||||
cache: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
||||
for nml in self.__mAssocMesh.corner_normals:
|
||||
cache.x = nml.vector.x
|
||||
cache.y = nml.vector.y
|
||||
cache.z = nml.vector.z
|
||||
yield cache
|
||||
|
||||
def get_vertex_uv_count(self) -> int:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||
|
||||
if self.__mAssocMesh.uv_layers.active is None:
|
||||
# if no uv layer, we need make a fake one
|
||||
# return the same value with normals.
|
||||
# it also mean create uv for each face vertex
|
||||
return len(self.__mAssocMesh.loops)
|
||||
else:
|
||||
# otherwise return its size, also equaling with face count * 3 in theory
|
||||
return len(self.__mAssocMesh.uv_layers.active.uv)
|
||||
|
||||
def get_vertex_uv(self) -> typing.Iterator[UTIL_virtools_types.VxVector2]:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||
|
||||
cache: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2()
|
||||
if self.__mAssocMesh.uv_layers.active is None:
|
||||
# create a fake one
|
||||
cache.x = 0.0
|
||||
cache.y = 0.0
|
||||
for _ in range(self.get_vertex_uv_count()):
|
||||
yield cache
|
||||
else:
|
||||
for uv in self.__mAssocMesh.uv_layers.active.uv:
|
||||
cache.x = uv.vector.x
|
||||
cache.y = uv.vector.y
|
||||
yield cache
|
||||
|
||||
def get_material_slot_count(self) -> int:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||
|
||||
return len(self.__mAssocMesh.materials)
|
||||
|
||||
def get_material_slot(self) -> typing.Iterator[bpy.types.Material | None]:
|
||||
"""
|
||||
@remark This generator may return None if this slot do not link to may material.
|
||||
"""
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||
|
||||
for mtl in self.__mAssocMesh.materials:
|
||||
yield mtl
|
||||
|
||||
def get_face_count(self) -> int:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||
|
||||
return len(self.__mAssocMesh.polygons)
|
||||
|
||||
def get_face(self) -> typing.Iterator[FaceData]:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||
|
||||
# detect whether we have material
|
||||
no_mtl: bool = self.get_material_slot_count() == 0
|
||||
|
||||
# use list as indices container for convenient adding and deleting.
|
||||
cache: FaceData = FaceData([], 0)
|
||||
for face in self.__mAssocMesh.polygons:
|
||||
# confirm material use
|
||||
# a face without mtl have 2 situations. first is the whole object do not have mtl
|
||||
# another is this face use an empty mtl slot.
|
||||
if no_mtl:
|
||||
cache.mMtlIdx = 0
|
||||
else:
|
||||
cache.mMtlIdx = face.material_index
|
||||
|
||||
# resize indices
|
||||
self.__resize_face_data_indices(cache.mIndices, face.loop_total)
|
||||
# set indices
|
||||
for i in range(face.loop_total):
|
||||
cache.mIndices[i].mPosIdx = self.__mAssocMesh.loops[face.loop_start + i].vertex_index
|
||||
cache.mIndices[i].mNmlIdx = face.loop_start + i
|
||||
cache.mIndices[i].mUvIdx = face.loop_start + i
|
||||
|
||||
# return value
|
||||
yield cache
|
||||
|
||||
def __resize_face_data_indices(self, ls: list[FaceVertexData], expected_size: int) -> None:
|
||||
diff: int = expected_size - len(ls)
|
||||
if diff > 0:
|
||||
# add entry
|
||||
for _ in range(diff):
|
||||
ls.append(FaceVertexData())
|
||||
elif diff < 0:
|
||||
# remove entry
|
||||
for _ in range(diff):
|
||||
ls.pop()
|
||||
else:
|
||||
# no count diff, pass
|
||||
pass
|
||||
|
||||
def __triangulate_mesh(self) -> None:
|
||||
bm: bmesh.types.BMesh = bmesh.new()
|
||||
bm.from_mesh(self.__mAssocMesh)
|
||||
bmesh.ops.triangulate(bm, faces = bm.faces)
|
||||
bm.to_mesh(self.__mAssocMesh)
|
||||
bm.free()
|
||||
|
||||
class MeshWriter():
|
||||
"""
|
||||
If face do not use material, pass 0 as its material index.
|
||||
If face do not have UV becuase it doesn't have material, you at least create 1 UV vector, eg. (0, 0),
|
||||
then refer it to all face uv.
|
||||
"""
|
||||
|
||||
__mAssocMesh: bpy.types.Mesh ##< The binding mesh for this writer. None if this writer is invalid.
|
||||
|
||||
__mVertexPos: array.array ##< Array item is float(f). Length must be an integer multiple of 3.
|
||||
__mVertexNormal: array.array ##< Array item is float(f). Length must be an integer multiple of 3.
|
||||
__mVertexUV: array.array ##< Array item is float(f). Length must be an integer multiple of 2.
|
||||
|
||||
## Array item is int32(L).
|
||||
# Length must be the sum of each items in __mFaceVertexCount.
|
||||
# Item is face vertex position index, based on 0, pointing to __mVertexPos (visiting need multiple it with 3 because __mVertexPos is flat struct).
|
||||
__mFacePosIndices: array.array
|
||||
## Same as __mFacePosIndices, but store face vertex normal index.
|
||||
# Array item is int32(L). Length is equal to __mFacePosIndices
|
||||
__mFaceNmlIndices: array.array
|
||||
## Same as __mFacePosIndices, but store face vertex uv index.
|
||||
# Array item is int32(L). Length is equal to __mFacePosIndices
|
||||
__mFaceUvIndices: array.array
|
||||
## Array item is int32(L).
|
||||
# Length is the face count.
|
||||
# It indicate how much vertex need to be consumed in __mFacePosIndices, __mFaceNmlIndices and __mFaceUvIndices for one face.
|
||||
__mFaceVertexCount: array.array
|
||||
__mFaceMtlIdx: array.array ##< Array item is int32(L). Length is equal to __mFaceVertexCount.
|
||||
|
||||
## Material Slot.
|
||||
# Each item is unique make sure by __mMtlSlotMap
|
||||
__mMtlSlot: list[bpy.types.Material | None]
|
||||
## The map to make sure every item in __mMtlSlot is unique.
|
||||
# Key is bpy.types.Material
|
||||
# Value is key's index in __mMtlSlot.
|
||||
__mMtlSlotMap: dict[bpy.types.Material | None, int]
|
||||
|
||||
## The attribute name storing temporary normals data inside mesh.
|
||||
__cTempNormalAttrName: typing.ClassVar[str] = 'temp_custom_normals'
|
||||
|
||||
def __init__(self, assoc_mesh: bpy.types.Mesh):
|
||||
self.__mAssocMesh = assoc_mesh
|
||||
|
||||
self.__mVertexPos = array.array('f')
|
||||
self.__mVertexNormal = array.array('f')
|
||||
self.__mVertexUV = array.array('f')
|
||||
|
||||
self.__mFacePosIndices = array.array('L')
|
||||
self.__mFaceNmlIndices = array.array('L')
|
||||
self.__mFaceUvIndices = array.array('L')
|
||||
self.__mFaceVertexCount = array.array('L')
|
||||
self.__mFaceMtlIdx = array.array('L')
|
||||
|
||||
self.__mMtlSlot = []
|
||||
self.__mMtlSlotMap = {}
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self.__mAssocMesh is not None
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.dispose()
|
||||
|
||||
def dispose(self):
|
||||
if self.is_valid():
|
||||
# write mesh
|
||||
self.__write_mesh()
|
||||
# reset mesh
|
||||
self.__mAssocMesh = None
|
||||
|
||||
def add_ingredient(self, data: MeshWriterIngredient):
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshWriter.')
|
||||
if not data.is_valid():
|
||||
raise UTIL_functions.BBPException('invalid mesh part data.')
|
||||
|
||||
# add vertex data
|
||||
prev_vertex_pos_count: int = len(self.__mVertexPos) // 3
|
||||
self.__mVertexPos.extend(_flat_vxvector3(data.mVertexPosition))
|
||||
prev_vertex_nml_count: int = len(self.__mVertexNormal) // 3
|
||||
self.__mVertexNormal.extend(_flat_vxvector3(data.mVertexNormal))
|
||||
prev_vertex_uv_count: int = len(self.__mVertexUV) // 2
|
||||
self.__mVertexUV.extend(_flat_vxvector2(data.mVertexUV))
|
||||
|
||||
# add material slot data and create mtl remap
|
||||
mtl_remap: list[int] = []
|
||||
for mtl in data.mMaterial:
|
||||
idx: int | None = self.__mMtlSlotMap.get(mtl, None)
|
||||
if idx is not None:
|
||||
mtl_remap.append(idx)
|
||||
else:
|
||||
self.__mMtlSlotMap[mtl] = len(self.__mMtlSlot)
|
||||
mtl_remap.append(len(self.__mMtlSlot))
|
||||
self.__mMtlSlot.append(mtl)
|
||||
|
||||
# add face data
|
||||
for face in data.mFace:
|
||||
# check indices count
|
||||
if not face.is_indices_legal():
|
||||
raise UTIL_functions.BBPException('face must have at least 3 vertex.')
|
||||
|
||||
# add indices
|
||||
for vec_index in face.mIndices:
|
||||
self.__mFacePosIndices.append(vec_index.mPosIdx + prev_vertex_pos_count)
|
||||
self.__mFaceNmlIndices.append(vec_index.mNmlIdx + prev_vertex_nml_count)
|
||||
self.__mFaceUvIndices.append(vec_index.mUvIdx + prev_vertex_uv_count)
|
||||
self.__mFaceVertexCount.append(len(face.mIndices))
|
||||
|
||||
# add face mtl with remap
|
||||
mtl_idx: int = face.mMtlIdx
|
||||
if mtl_idx < 0 or mtl_idx >= len(mtl_remap):
|
||||
# fall back. add 0
|
||||
self.__mFaceMtlIdx.append(0)
|
||||
else:
|
||||
self.__mFaceMtlIdx.append(mtl_remap[mtl_idx])
|
||||
|
||||
def __write_mesh(self):
|
||||
# detect status
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshWriter.')
|
||||
# and clear mesh
|
||||
self.__clear_mesh()
|
||||
|
||||
# push material data
|
||||
for mtl in self.__mMtlSlot:
|
||||
self.__mAssocMesh.materials.append(mtl)
|
||||
|
||||
# add corresponding count for vertex position
|
||||
self.__mAssocMesh.vertices.add(len(self.__mVertexPos) // 3)
|
||||
# add loops data, it is the sum count of indices
|
||||
# we use face pos indices size to get it
|
||||
self.__mAssocMesh.loops.add(len(self.__mFacePosIndices))
|
||||
# set face count
|
||||
self.__mAssocMesh.polygons.add(len(self.__mFaceVertexCount))
|
||||
# create uv layer
|
||||
self.__mAssocMesh.uv_layers.new(do_init = False)
|
||||
|
||||
# add vertex position data
|
||||
self.__mAssocMesh.vertices.foreach_set('co', self.__mVertexPos)
|
||||
# add face vertex pos index data
|
||||
self.__mAssocMesh.loops.foreach_set('vertex_index', self.__mFacePosIndices)
|
||||
# add face vertex nml by function via mesh custom attribute
|
||||
# NOTE: Blender 4.0 / 4.1 changed. I copy these code from FBX Importer.
|
||||
temp_normal_attribute: bpy.types.FloatVectorAttribute
|
||||
temp_normal_attribute = typing.cast(
|
||||
bpy.types.FloatVectorAttribute,
|
||||
self.__mAssocMesh.attributes.new(MeshWriter.__cTempNormalAttrName, 'FLOAT_VECTOR', 'CORNER')
|
||||
)
|
||||
temp_normal_attribute.data.foreach_set('vector',
|
||||
tuple(_flat_face_nml_index(self.__mFaceNmlIndices, self.__mVertexNormal))
|
||||
)
|
||||
# add face vertex uv by function
|
||||
self.__mAssocMesh.uv_layers.active.uv.foreach_set('vector',
|
||||
tuple(_flat_face_uv_index(self.__mFaceUvIndices, self.__mVertexUV))
|
||||
) # NOTE: blender 3.5 changed. UV must be visited by .uv, not the .data
|
||||
|
||||
# iterate face to set face data
|
||||
f_vertex_idx: int = 0
|
||||
for fi in range(len(self.__mFaceVertexCount)):
|
||||
# set start loop
|
||||
# NOTE: blender 3.6 changed. Loop setting in polygon do not need set loop_total any more.
|
||||
# the loop_total will be auto calculated by the next loop_start.
|
||||
# loop_total become read-only
|
||||
self.__mAssocMesh.polygons[fi].loop_start = f_vertex_idx
|
||||
|
||||
# set material index
|
||||
self.__mAssocMesh.polygons[fi].material_index = self.__mFaceMtlIdx[fi]
|
||||
|
||||
# set auto smooth. it is IMPORTANT
|
||||
# because it related to whether custom split normal can work
|
||||
self.__mAssocMesh.polygons[fi].use_smooth = True
|
||||
|
||||
# inc vertex idx
|
||||
f_vertex_idx += self.__mFaceVertexCount[fi]
|
||||
|
||||
# validate mesh.
|
||||
# it is IMPORTANT that do NOT delete custom data
|
||||
# because we need use these data to set custom split normal later
|
||||
self.__mAssocMesh.validate(clean_customdata = False)
|
||||
# update mesh without mesh calc
|
||||
self.__mAssocMesh.update(calc_edges = False, calc_edges_loose = False)
|
||||
|
||||
# set custom split normal data
|
||||
# this operation must copy preserved normal data from loops, not the array data in this class,
|
||||
# because the validate() may change the mesh and if change happended, an error will occur when applying normals (not matched loops count).
|
||||
# this should not happend in normal case, for testing, please load "Level_1.NMO" (Ballance Level 1).
|
||||
|
||||
# copy data from loops preserved in validate().
|
||||
# NOTE: Blender 4.0 / 4.1 changed. I copy these code from FBX Importer.
|
||||
loops_normals = array.array('f', [0.0] * (len(self.__mAssocMesh.loops) * 3))
|
||||
temp_normal_attribute = typing.cast(
|
||||
bpy.types.FloatVectorAttribute,
|
||||
self.__mAssocMesh.attributes[MeshWriter.__cTempNormalAttrName]
|
||||
)
|
||||
temp_normal_attribute.data.foreach_get("vector", loops_normals)
|
||||
# apply data
|
||||
self.__mAssocMesh.normals_split_custom_set(
|
||||
tuple(_nest_custom_split_normal(loops_normals))
|
||||
)
|
||||
self.__mAssocMesh.attributes.remove(
|
||||
# MARK: idk why I need fucking get this attribute again.
|
||||
# But if I were not, this function must raise bullshit exception!
|
||||
self.__mAssocMesh.attributes[MeshWriter.__cTempNormalAttrName]
|
||||
)
|
||||
|
||||
def __clear_mesh(self):
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshWriter.')
|
||||
|
||||
# clear geometry
|
||||
self.__mAssocMesh.clear_geometry()
|
||||
# clear mtl slot because clear_geometry will not do this.
|
||||
self.__mAssocMesh.materials.clear()
|
534
bbp_ng/UTIL_bme.py
Normal file
534
bbp_ng/UTIL_bme.py
Normal file
@ -0,0 +1,534 @@
|
||||
import bpy, mathutils
|
||||
import os, json, enum, typing, math
|
||||
from . import PROP_virtools_group, PROP_bme_material
|
||||
from . import UTIL_functions, UTIL_icons_manager, UTIL_blender_mesh, UTIL_virtools_types, UTIL_naming_convension
|
||||
|
||||
## NOTE: Outside caller should use BME struct's unique indetifier to visit each prototype
|
||||
# and drive this class' functions to work.
|
||||
|
||||
#region Prototype Visitor
|
||||
|
||||
class PrototypeShowcaseCfgsTypes(enum.Enum):
|
||||
Integer = 'int'
|
||||
Float = 'float'
|
||||
Boolean = 'bool'
|
||||
Face = 'face'
|
||||
|
||||
class PrototypeShowcaseTypes(enum.Enum):
|
||||
No = 'none'
|
||||
Floor = 'floor'
|
||||
Rail = 'rail'
|
||||
Wood = 'wood'
|
||||
|
||||
TOKEN_IDENTIFIER: str = 'identifier'
|
||||
|
||||
TOKEN_SHOWCASE: str = 'showcase'
|
||||
TOKEN_SHOWCASE_TITLE: str = 'title'
|
||||
TOKEN_SHOWCASE_ICON: str = 'icon'
|
||||
TOKEN_SHOWCASE_TYPE: str = 'type'
|
||||
TOKEN_SHOWCASE_CFGS: str = 'cfgs'
|
||||
TOKEN_SHOWCASE_CFGS_FIELD: str = 'field'
|
||||
TOKEN_SHOWCASE_CFGS_TYPE: str = 'type'
|
||||
TOKEN_SHOWCASE_CFGS_TITLE: str = 'title'
|
||||
TOKEN_SHOWCASE_CFGS_DESC: str = 'desc'
|
||||
TOKEN_SHOWCASE_CFGS_DEFAULT: str = 'default'
|
||||
|
||||
TOKEN_SKIP: str = 'skip'
|
||||
|
||||
TOKEN_PARAMS: str = 'params'
|
||||
TOKEN_PARAMS_FIELD: str = 'field'
|
||||
TOKEN_PARAMS_DATA: str = 'data'
|
||||
|
||||
TOKEN_VARS: str = 'vars'
|
||||
TOKEN_VARS_FIELD: str = 'field'
|
||||
TOKEN_VARS_DATA: str = 'data'
|
||||
|
||||
TOKEN_VERTICES: str = 'vertices'
|
||||
TOKEN_VERTICES_SKIP: str = 'skip'
|
||||
TOKEN_VERTICES_DATA: str = 'data'
|
||||
|
||||
TOKEN_FACES: str = 'faces'
|
||||
TOKEN_FACES_SKIP: str = 'skip'
|
||||
TOKEN_FACES_TEXTURE: str = 'texture'
|
||||
TOKEN_FACES_INDICES: str = 'indices'
|
||||
TOKEN_FACES_UVS: str = 'uvs'
|
||||
TOKEN_FACES_NORMALS: str = 'normals'
|
||||
|
||||
TOKEN_INSTANCES: str = 'instances'
|
||||
TOKEN_INSTANCES_IDENTIFIER: str = 'identifier'
|
||||
TOKEN_INSTANCES_SKIP: str = 'skip'
|
||||
TOKEN_INSTANCES_PARAMS: str = 'params'
|
||||
TOKEN_INSTANCES_TRANSFORM: str = 'transform'
|
||||
|
||||
#endregion
|
||||
|
||||
#region Prototype Loader
|
||||
|
||||
## The list storing BME prototype.
|
||||
_g_BMEPrototypes: list[dict[str, typing.Any]] = []
|
||||
## The dict. Key is prototype identifier. value is the index of prototype in prototype list.
|
||||
_g_BMEPrototypeIndexMap: dict[str, int] = {}
|
||||
|
||||
# the core loader
|
||||
for walk_root, walk_dirs, walk_files in os.walk(os.path.join(os.path.dirname(__file__), 'jsons')):
|
||||
for relfile in walk_files:
|
||||
if not relfile.endswith('.json'): continue
|
||||
with open(os.path.join(walk_root, relfile), 'r', encoding = 'utf-8') as fp:
|
||||
proto: dict[str, typing.Any]
|
||||
for proto in json.load(fp):
|
||||
# insert index to map
|
||||
_g_BMEPrototypeIndexMap[proto[TOKEN_IDENTIFIER]] = len(_g_BMEPrototypes)
|
||||
# add into list
|
||||
_g_BMEPrototypes.append(proto)
|
||||
|
||||
def _get_prototype_by_identifier(ident: str) -> dict[str, typing.Any]:
|
||||
return _g_BMEPrototypes[_g_BMEPrototypeIndexMap[ident]]
|
||||
|
||||
#endregion
|
||||
|
||||
#region Programmable Field Calc
|
||||
|
||||
_g_ProgFieldGlobals: dict[str, typing.Any] = {
|
||||
# constant
|
||||
'pi': math.pi,
|
||||
'tau': math.tau,
|
||||
|
||||
# math functions
|
||||
'sin': math.sin,
|
||||
'cos': math.cos,
|
||||
'tan': math.tan,
|
||||
'asin': math.asin,
|
||||
'acos': math.acos,
|
||||
'atan': math.atan,
|
||||
|
||||
'pow': math.pow,
|
||||
'sqrt': math.sqrt,
|
||||
|
||||
'fabs': math.fabs,
|
||||
|
||||
'degrees': math.degrees,
|
||||
'radians': math.radians,
|
||||
|
||||
# builtin functions
|
||||
'abs': abs,
|
||||
|
||||
'int': int,
|
||||
'float': float,
|
||||
'str': str,
|
||||
'bool': bool,
|
||||
|
||||
# my custom matrix functions
|
||||
'move': lambda x, y, z: mathutils.Matrix.Translation((x, y, z)),
|
||||
'rot': lambda x, y, z: mathutils.Matrix.LocRotScale(None, mathutils.Euler((math.radians(x), math.radians(y), math.radians(z)), 'XYZ'), None),
|
||||
'scale': lambda x, y, z: mathutils.Matrix.LocRotScale(None, None, (x, y, z)),
|
||||
'ident': lambda: mathutils.Matrix.Identity(4),
|
||||
}
|
||||
|
||||
def _eval_showcase_cfgs_default(strl: str) -> typing.Any:
|
||||
return eval(strl, _g_ProgFieldGlobals, None)
|
||||
|
||||
def _eval_params(strl: str, cfgs_data: dict[str, typing.Any]) -> typing.Any:
|
||||
return eval(strl, _g_ProgFieldGlobals, cfgs_data)
|
||||
|
||||
def _eval_skip(strl: str, params_data: dict[str, typing.Any]) -> typing.Any:
|
||||
return eval(strl, _g_ProgFieldGlobals, params_data)
|
||||
|
||||
def _eval_vars(strl: str, params_data: dict[str, typing.Any]) -> typing.Any:
|
||||
return eval(strl, _g_ProgFieldGlobals, params_data)
|
||||
|
||||
def _eval_others(strl: str, params_vars_data: dict[str, typing.Any]) -> typing.Any:
|
||||
return eval(strl, _g_ProgFieldGlobals, params_vars_data)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Prototype Helper
|
||||
|
||||
class PrototypeShowcaseCfgDescriptor():
|
||||
__mRawCfg: dict[str, str]
|
||||
|
||||
def __init__(self, raw_cfg: dict[str, str]):
|
||||
self.__mRawCfg = raw_cfg
|
||||
|
||||
def get_field(self) -> str:
|
||||
return self.__mRawCfg[TOKEN_SHOWCASE_CFGS_FIELD]
|
||||
|
||||
def get_type(self) -> PrototypeShowcaseCfgsTypes:
|
||||
return PrototypeShowcaseCfgsTypes(self.__mRawCfg[TOKEN_SHOWCASE_CFGS_TYPE])
|
||||
|
||||
def get_title(self) -> str:
|
||||
return self.__mRawCfg[TOKEN_SHOWCASE_CFGS_TITLE]
|
||||
|
||||
def get_desc(self) -> str:
|
||||
return self.__mRawCfg[TOKEN_SHOWCASE_CFGS_DESC]
|
||||
|
||||
def get_default(self) -> typing.Any:
|
||||
return _eval_showcase_cfgs_default(self.__mRawCfg[TOKEN_SHOWCASE_CFGS_DEFAULT])
|
||||
|
||||
class EnumPropHelper(UTIL_functions.EnumPropHelper):
|
||||
"""
|
||||
The BME specialized Blender EnumProperty helper.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
# init parent class
|
||||
UTIL_functions.EnumPropHelper.__init__(
|
||||
self,
|
||||
self.get_bme_identifiers(),
|
||||
lambda x: x,
|
||||
lambda x: x,
|
||||
lambda x: self.get_bme_showcase_title(x),
|
||||
lambda _: '',
|
||||
lambda x: self.get_bme_showcase_icon(x)
|
||||
)
|
||||
|
||||
def get_bme_identifiers(self) -> tuple[str, ...]:
|
||||
"""
|
||||
Get the identifier of prototype which need to be exposed to user.
|
||||
Template prototype is not included.
|
||||
"""
|
||||
return tuple(
|
||||
x[TOKEN_IDENTIFIER] # get identifier
|
||||
for x in filter(lambda x: x[TOKEN_SHOWCASE] is not None, _g_BMEPrototypes) # filter() to filter no showcase template.
|
||||
)
|
||||
|
||||
def get_bme_showcase_title(self, ident: str) -> str:
|
||||
"""
|
||||
Get BME display title by prototype identifier.
|
||||
"""
|
||||
# get prototype first
|
||||
proto: dict[str, typing.Any] = _get_prototype_by_identifier(ident)
|
||||
# visit title field
|
||||
return proto[TOKEN_SHOWCASE][TOKEN_SHOWCASE_TITLE]
|
||||
|
||||
def get_bme_showcase_icon(self, ident: str) -> int:
|
||||
"""
|
||||
Get BME icon by prototype's identifier
|
||||
"""
|
||||
# get prototype specified icon name
|
||||
proto: dict[str, typing.Any] = _get_prototype_by_identifier(ident)
|
||||
icon_name: str = proto[TOKEN_SHOWCASE][TOKEN_SHOWCASE_ICON]
|
||||
# get icon from icon manager
|
||||
cache: int | None = UTIL_icons_manager.get_bme_icon(icon_name)
|
||||
if cache is None: return UTIL_icons_manager.get_empty_icon()
|
||||
else: return cache
|
||||
|
||||
def get_bme_showcase_cfgs(self, ident: str) -> typing.Iterator[PrototypeShowcaseCfgDescriptor]:
|
||||
# get prototype first
|
||||
proto: dict[str, typing.Any] = _get_prototype_by_identifier(ident)
|
||||
# use map to batch create descriptor
|
||||
return map(lambda x: PrototypeShowcaseCfgDescriptor(x), proto[TOKEN_SHOWCASE][TOKEN_SHOWCASE_CFGS])
|
||||
|
||||
#endregion
|
||||
|
||||
#region Core Creator
|
||||
|
||||
def create_bme_struct_wrapper(ident: str, cfgs: dict[str, typing.Any]) -> bpy.types.Object:
|
||||
# get prototype first
|
||||
proto: dict[str, typing.Any] = _get_prototype_by_identifier(ident)
|
||||
|
||||
# analyse params by given cfgs
|
||||
params: dict[str, typing.Any] = {}
|
||||
for proto_param in proto[TOKEN_PARAMS]:
|
||||
params[proto_param[TOKEN_PARAMS_FIELD]] = _eval_params(proto_param[TOKEN_PARAMS_DATA], cfgs)
|
||||
|
||||
# create used mesh
|
||||
mesh: bpy.types.Mesh = bpy.data.meshes.new('BMEStruct')
|
||||
|
||||
# create mesh writer and bme mtl helper
|
||||
# recursively calling underlying creation function
|
||||
with UTIL_blender_mesh.MeshWriter(mesh) as writer:
|
||||
with PROP_bme_material.BMEMaterialsHelper(bpy.context.scene) as bmemtl:
|
||||
create_bme_struct(
|
||||
ident,
|
||||
writer,
|
||||
bmemtl,
|
||||
mathutils.Matrix.Identity(4),
|
||||
params
|
||||
)
|
||||
|
||||
# create object and assign prop
|
||||
# get obj info first
|
||||
obj_info: UTIL_naming_convension.BallanceObjectInfo
|
||||
match(PrototypeShowcaseTypes(proto[TOKEN_SHOWCASE][TOKEN_SHOWCASE_TYPE])):
|
||||
case PrototypeShowcaseTypes.No:
|
||||
obj_info = UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.DECORATION)
|
||||
case PrototypeShowcaseTypes.Floor:
|
||||
obj_info = UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.FLOOR)
|
||||
case PrototypeShowcaseTypes.Rail:
|
||||
obj_info = UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.RAIL)
|
||||
case PrototypeShowcaseTypes.Wood:
|
||||
obj_info = UTIL_naming_convension.BallanceObjectInfo.create_from_others(UTIL_naming_convension.BallanceObjectType.WOOD)
|
||||
# then get object name
|
||||
obj_name: str | None = UTIL_naming_convension.YYCToolchainConvention.set_to_name(obj_info, None)
|
||||
if obj_name is None: raise UTIL_functions.BBPException('impossible null name')
|
||||
# create object by name
|
||||
obj: bpy.types.Object = bpy.data.objects.new(obj_name, mesh)
|
||||
# assign virtools groups
|
||||
UTIL_naming_convension.VirtoolsGroupConvention.set_to_object(obj, obj_info, None)
|
||||
|
||||
# return object
|
||||
return obj
|
||||
|
||||
def create_bme_struct(
|
||||
ident: str,
|
||||
writer: UTIL_blender_mesh.MeshWriter,
|
||||
bmemtl: PROP_bme_material.BMEMaterialsHelper,
|
||||
transform: mathutils.Matrix,
|
||||
params: dict[str, typing.Any]) -> None:
|
||||
# get prototype first
|
||||
proto: dict[str, typing.Any] = _get_prototype_by_identifier(ident)
|
||||
|
||||
# check whether skip the whole struct before cal vars
|
||||
if _eval_skip(proto[TOKEN_SKIP], params) == True:
|
||||
return
|
||||
|
||||
# calc vars by given params
|
||||
# please note i will add entries directly into params dict
|
||||
# but the params dict will not used independently later,
|
||||
# all following use is the union of params and vars dict.
|
||||
# so it is safe.
|
||||
for proto_var in proto[TOKEN_VARS]:
|
||||
params[proto_var[TOKEN_VARS_FIELD]] = _eval_vars(proto_var[TOKEN_VARS_DATA], params)
|
||||
|
||||
# collect valid face and vertices data for following using.
|
||||
# if NOT skip, add into valid list
|
||||
valid_vec_idx: list[int] = []
|
||||
for vec_idx, proto_vec in enumerate(proto[TOKEN_VERTICES]):
|
||||
if _eval_others(proto_vec[TOKEN_VERTICES_SKIP], params) == False:
|
||||
valid_vec_idx.append(vec_idx)
|
||||
valid_face_idx: list[int] = []
|
||||
for face_idx, proto_face in enumerate(proto[TOKEN_FACES]):
|
||||
if _eval_others(proto_face[TOKEN_FACES_SKIP], params) == False:
|
||||
valid_face_idx.append(face_idx)
|
||||
|
||||
# create mtl slot remap to help following mesh adding
|
||||
# because mesh writer do not accept string format mtl slot visiting,
|
||||
# it only accept int based mtl slot index.
|
||||
#
|
||||
# Also we build face used mtl slot index at the same time.
|
||||
# So we do not analyse texture field again when providing face data.
|
||||
# The result is in `prebuild_face_mtl_idx` and please note it will store all face's mtl index.
|
||||
# For example: if face 0 is skipped and face 1 is used, the first entry in `prebuild_face_mtl_idx`
|
||||
# will be the mtl slot index used by face 0, not 1. And its length is equal to the face count.
|
||||
# However, because face 0 is skipped, so the entry is not used and default set to 0.
|
||||
#
|
||||
# NOTE: since Python 3.6, the item of builtin dict is ordered by inserting order.
|
||||
# we rely on this to implement following features.
|
||||
mtl_remap: dict[str, int] = {}
|
||||
prebuild_face_mtl_idx: list[int] = [0] * len(proto[TOKEN_FACES])
|
||||
for face_idx in valid_face_idx:
|
||||
# eval mtl name
|
||||
mtl_name: str = _eval_others(proto[TOKEN_FACES][face_idx][TOKEN_FACES_TEXTURE], params)
|
||||
# try insert into remap and record to face mtl idx
|
||||
if mtl_name not in mtl_remap:
|
||||
# record index
|
||||
prebuild_face_mtl_idx[face_idx] = len(mtl_remap)
|
||||
# add into remap if not exist
|
||||
mtl_remap[mtl_name] = len(mtl_remap)
|
||||
else:
|
||||
# if existing, no need to add into remap
|
||||
# but we need get its index from remap
|
||||
prebuild_face_mtl_idx[face_idx] = mtl_remap.get(mtl_name, 0)
|
||||
|
||||
# pre-compute vertices data because we may need used later.
|
||||
# Because if face normal data is null, it mean that we need to compute it
|
||||
# by given vertices.
|
||||
# The computed vertices is stored in `prebuild_vec_data` and is NOT like `prebuild_face_mtl_idx`,
|
||||
# we only store valid one in `prebuild_vec_data`.
|
||||
prebuild_vec_data: list[UTIL_virtools_types.ConstVxVector3 | None] = []
|
||||
cache_bv: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
||||
for vec_idx in valid_vec_idx:
|
||||
# but it need mul with transform matrix
|
||||
cache_bv.x, cache_bv.y, cache_bv.z = _eval_others(proto[TOKEN_VERTICES][vec_idx][TOKEN_VERTICES_DATA], params)
|
||||
# mul with transform matrix
|
||||
cache_bv = typing.cast(mathutils.Vector, transform @ cache_bv)
|
||||
# get result
|
||||
prebuild_vec_data.append((cache_bv.x, cache_bv.y, cache_bv.z))
|
||||
|
||||
# Check whether given transform is mirror matrix
|
||||
# because mirror matrix will reverse triangle indice order.
|
||||
# If matrix is mirror matrix, we need reverse it again in following procession,
|
||||
# including getting uv, calculating normal and providing face data.
|
||||
mirror_matrix: bool = _is_mirror_matrix(transform)
|
||||
|
||||
# prepare mesh part data
|
||||
mesh_part: UTIL_blender_mesh.MeshWriterIngredient = UTIL_blender_mesh.MeshWriterIngredient()
|
||||
def vpos_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||
# simply get data from prebuild vec data
|
||||
v: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
||||
for vec_data in prebuild_vec_data:
|
||||
# skip skipped vertices
|
||||
if vec_data is None: continue
|
||||
# yield result
|
||||
v.x, v.y, v.z = vec_data
|
||||
yield v
|
||||
mesh_part.mVertexPosition = vpos_iterator()
|
||||
def vnml_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||
# prepare normal used transform first
|
||||
# ref: https://zhuanlan.zhihu.com/p/96717729
|
||||
nml_transform: mathutils.Matrix = transform.inverted_safe().transposed()
|
||||
# prepare vars
|
||||
bv: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
||||
v: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
||||
for face_idx in valid_face_idx:
|
||||
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx]
|
||||
face_nml_data: list[str] | None = face_data[TOKEN_FACES_NORMALS]
|
||||
if face_nml_data is None:
|
||||
# nml is null, we need compute by ourselves
|
||||
# get first 3 entries in indices list as the compution ref
|
||||
# please note that we may need reverse it
|
||||
face_indices_data: list[int]
|
||||
if mirror_matrix:
|
||||
face_indices_data = face_data[TOKEN_FACES_INDICES][::-1]
|
||||
else:
|
||||
face_indices_data = face_data[TOKEN_FACES_INDICES][:]
|
||||
# compute it by getting vertices info from prebuild vertices data
|
||||
# because the normals is computed from transformed vertices
|
||||
# so no need to correct its by normal transform.
|
||||
bv.x, bv.y, bv.z = _compute_normals(
|
||||
typing.cast(UTIL_virtools_types.ConstVxVector3, prebuild_vec_data[face_indices_data[0]]),
|
||||
typing.cast(UTIL_virtools_types.ConstVxVector3, prebuild_vec_data[face_indices_data[1]]),
|
||||
typing.cast(UTIL_virtools_types.ConstVxVector3, prebuild_vec_data[face_indices_data[2]])
|
||||
)
|
||||
# yield result with N times (N = indices count)
|
||||
v.x, v.y, v.z = bv.x, bv.y, bv.z
|
||||
for _ in range(len(face_indices_data)):
|
||||
yield v
|
||||
else:
|
||||
# nml is given, analyse programable fields
|
||||
for mtl_data in face_nml_data:
|
||||
# BME normals need transform by matrix first,
|
||||
bv.x, bv.y, bv.z = _eval_others(mtl_data, params)
|
||||
bv = typing.cast(mathutils.Vector, nml_transform @ bv)
|
||||
# then normalize it
|
||||
bv.normalize()
|
||||
# yield result
|
||||
v.x, v.y, v.z = bv.x, bv.y, bv.z
|
||||
yield v
|
||||
mesh_part.mVertexNormal = vnml_iterator()
|
||||
def vuv_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector2]:
|
||||
v: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2()
|
||||
for face_idx in valid_face_idx:
|
||||
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx]
|
||||
# iterate uv list considering mirror matrix
|
||||
indices_count: int = len(face_data[TOKEN_FACES_INDICES])
|
||||
for i in (range(indices_count)[::-1] if mirror_matrix else range(indices_count)):
|
||||
# BME uv do not need any extra process
|
||||
v.x, v.y = _eval_others(face_data[TOKEN_FACES_UVS][i], params)
|
||||
yield v
|
||||
mesh_part.mVertexUV = vuv_iterator()
|
||||
def mtl_iterator() -> typing.Iterator[bpy.types.Material | None]:
|
||||
for mtl_name in mtl_remap.keys():
|
||||
yield bmemtl.get_material(mtl_name)
|
||||
mesh_part.mMaterial = mtl_iterator()
|
||||
def face_iterator() -> typing.Iterator[UTIL_blender_mesh.FaceData]:
|
||||
# create face data with 3 placeholder
|
||||
f: UTIL_blender_mesh.FaceData = UTIL_blender_mesh.FaceData(
|
||||
[UTIL_blender_mesh.FaceVertexData() for i in range(3)]
|
||||
)
|
||||
|
||||
# create a internal counter to count how many indices has been processed
|
||||
# this counter will be used to calc uv and normal index
|
||||
# because these are based on face, not vertex position index.
|
||||
face_counter: int = 0
|
||||
|
||||
# iterate valid face
|
||||
for face_idx in valid_face_idx:
|
||||
# get face data
|
||||
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx]
|
||||
|
||||
# get face indices considering the mirror matrix
|
||||
face_indices: list[int]
|
||||
if mirror_matrix:
|
||||
face_indices = face_data[TOKEN_FACES_INDICES][::-1]
|
||||
else:
|
||||
face_indices = face_data[TOKEN_FACES_INDICES][:]
|
||||
# calc indices count
|
||||
indices_count: int = len(face_indices)
|
||||
# resize face data to fulfill req
|
||||
while len(f.mIndices) > indices_count:
|
||||
f.mIndices.pop()
|
||||
while len(f.mIndices) < indices_count:
|
||||
f.mIndices.append(UTIL_blender_mesh.FaceVertexData())
|
||||
|
||||
# fill the data
|
||||
for i in range(indices_count):
|
||||
# fill vertex position data by indices
|
||||
f.mIndices[i].mPosIdx = face_indices[i]
|
||||
# fill nml and uv based on face index
|
||||
f.mIndices[i].mNmlIdx = face_counter + i
|
||||
f.mIndices[i].mUvIdx = face_counter + i
|
||||
|
||||
# add current face indices count to internal counter
|
||||
face_counter += indices_count
|
||||
|
||||
# fill texture data
|
||||
f.mMtlIdx = prebuild_face_mtl_idx[face_idx]
|
||||
|
||||
# return data once
|
||||
yield f
|
||||
mesh_part.mFace = face_iterator()
|
||||
# add part to writer
|
||||
writer.add_ingredient(mesh_part)
|
||||
|
||||
# then we incursively process instance creation
|
||||
for proto_instance in proto[TOKEN_INSTANCES]:
|
||||
# check whether skip this instance
|
||||
if _eval_others(proto_instance[TOKEN_INSTANCES_SKIP], params) == True:
|
||||
continue
|
||||
|
||||
# calc instance params
|
||||
instance_params: dict[str, typing.Any] = {}
|
||||
proto_instance_params: dict[str, str] = proto_instance[TOKEN_INSTANCES_PARAMS]
|
||||
for proto_inst_param_field, proto_inst_param_data in proto_instance_params.items():
|
||||
instance_params[proto_inst_param_field] = _eval_others(proto_inst_param_data, params)
|
||||
|
||||
# call recursively
|
||||
create_bme_struct(
|
||||
proto_instance[TOKEN_INSTANCES_IDENTIFIER],
|
||||
writer,
|
||||
bmemtl,
|
||||
# left-mul transform, because self transform should be applied first, the apply parent's transform
|
||||
transform @ _eval_others(proto_instance[TOKEN_INSTANCES_TRANSFORM], params),
|
||||
instance_params
|
||||
)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Creation Assist Functions
|
||||
|
||||
def _compute_normals(
|
||||
point1: UTIL_virtools_types.ConstVxVector3,
|
||||
point2: UTIL_virtools_types.ConstVxVector3,
|
||||
point3: UTIL_virtools_types.ConstVxVector3) -> UTIL_virtools_types.ConstVxVector3:
|
||||
# build vector
|
||||
p1: mathutils.Vector = mathutils.Vector(point1)
|
||||
p2: mathutils.Vector = mathutils.Vector(point2)
|
||||
p3: mathutils.Vector = mathutils.Vector(point3)
|
||||
|
||||
vector1: mathutils.Vector = p2 - p1
|
||||
vector2: mathutils.Vector = p3 - p2
|
||||
|
||||
# do vector x mutiply
|
||||
# vector1 x vector2
|
||||
corss_mul: mathutils.Vector = vector1.cross(vector2)
|
||||
|
||||
# do a normalization
|
||||
corss_mul.normalize()
|
||||
return (corss_mul.x, corss_mul.y, corss_mul.z)
|
||||
|
||||
def _is_mirror_matrix(mat: mathutils.Matrix) -> bool:
|
||||
"""
|
||||
Reflection matrix (aka. mirror matrix) is a special scaling matrix.
|
||||
In this matrix, 1 or 3 scaling factor is minus number.
|
||||
|
||||
Mirror matrix will cause the inverse of triangle indice order.
|
||||
So we need detect it and re-reverse when creating bm struct.
|
||||
This function can detect whether given matrix is mirror matrix.
|
||||
|
||||
Reference: https://zhuanlan.zhihu.com/p/96717729
|
||||
"""
|
||||
return mat.is_negative
|
||||
#return mat.to_3x3().determinant() < 0
|
||||
|
||||
#endregion
|
101
bbp_ng/UTIL_file_browser.py
Normal file
101
bbp_ng/UTIL_file_browser.py
Normal file
@ -0,0 +1,101 @@
|
||||
import bpy, bpy_extras
|
||||
|
||||
## File Browser Usage
|
||||
# These created file browser is just a futher wrapper of `bpy_extras.io_utils.ExportHelper`
|
||||
# So user must use it like ExportHelper. It mean inhert it and no need to write invoke function.
|
||||
#
|
||||
# These wrapper also provide general visitor for getting input file name or directory:
|
||||
# * general_get_filename()
|
||||
# * general_get_directory()
|
||||
#
|
||||
# For example:
|
||||
# ```
|
||||
# class BBP_OT_custom_import(bpy.types.Operator, UTIL_file_browser.OpenBmxFile)
|
||||
#
|
||||
# def execute(self, context):
|
||||
# print(self.general_get_filename()) # get file name if support
|
||||
# print(self.general_get_directory()) # get file name if support
|
||||
#
|
||||
# ```
|
||||
|
||||
class ImportBallanceImage(bpy_extras.io_utils.ImportHelper):
|
||||
|
||||
# no need to set file ext because we support multiple file ext.
|
||||
# see ImportGLTF2 for more info.
|
||||
# filename_ext = ".bmp"
|
||||
|
||||
# set with 2 file ext with ; as spelittor
|
||||
# see ImportGLTF2 for more info.
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default = "*.bmp;*.tga",
|
||||
options = {'HIDDEN'}
|
||||
)
|
||||
|
||||
def general_set_filename(self, filename: str) -> None:
|
||||
self.filepath = filename
|
||||
|
||||
def general_get_filename(self) -> str:
|
||||
return self.filepath
|
||||
|
||||
class ImportBmxFile(bpy_extras.io_utils.ImportHelper):
|
||||
|
||||
# set file ext filter
|
||||
filename_ext = ".bmx"
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default = "*.bmx",
|
||||
options = {'HIDDEN'}
|
||||
)
|
||||
|
||||
def general_get_filename(self) -> str:
|
||||
return self.filepath
|
||||
|
||||
class ExportBmxFile(bpy_extras.io_utils.ExportHelper):
|
||||
|
||||
# set file ext filter
|
||||
filename_ext = ".bmx"
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default = "*.bmx",
|
||||
options = {'HIDDEN'}
|
||||
)
|
||||
|
||||
def general_get_filename(self) -> str:
|
||||
return self.filepath
|
||||
|
||||
class ImportVirtoolsFile(bpy_extras.io_utils.ImportHelper):
|
||||
|
||||
# we support multiple file ext, set like ImportBallanceImage
|
||||
# filename_ext = ".nmo"
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default = "*.nmo;*.cmo;*.vmo",
|
||||
options = {'HIDDEN'}
|
||||
)
|
||||
|
||||
def general_get_filename(self) -> str:
|
||||
return self.filepath
|
||||
|
||||
class ExportVirtoolsFile(bpy_extras.io_utils.ExportHelper):
|
||||
|
||||
# only support export nmo file
|
||||
filename_ext = ".nmo"
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default = "*.nmo",
|
||||
options = {'HIDDEN'}
|
||||
)
|
||||
|
||||
def general_get_filename(self) -> str:
|
||||
return self.filepath
|
||||
|
||||
class ImportDirectory(bpy_extras.io_utils.ImportHelper):
|
||||
|
||||
# add directory prop to receive directory
|
||||
directory: bpy.props.StringProperty()
|
||||
|
||||
# blank filter
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default = "",
|
||||
options = {'HIDDEN'}
|
||||
)
|
||||
|
||||
def general_get_directory(self) -> str:
|
||||
return self.directory
|
||||
|
87
bbp_ng/UTIL_file_io.py
Normal file
87
bbp_ng/UTIL_file_io.py
Normal file
@ -0,0 +1,87 @@
|
||||
import bpy, mathutils
|
||||
import struct, os, io, typing
|
||||
from . import UTIL_virtools_types
|
||||
|
||||
_FileWriter_t = io.BufferedWriter
|
||||
_FileReader_t = io.BufferedReader
|
||||
|
||||
#region Writer Functions
|
||||
|
||||
def write_string(fs: _FileWriter_t, strl: str) -> None:
|
||||
count = len(strl)
|
||||
write_uint32(fs, count)
|
||||
fs.write(strl.encode("utf_32_le"))
|
||||
|
||||
def write_uint8(fs: _FileWriter_t, num: int) -> None:
|
||||
fs.write(struct.pack("<B", num))
|
||||
|
||||
def write_uint32(fs: _FileWriter_t, num: int) -> None:
|
||||
fs.write(struct.pack("<I", num))
|
||||
|
||||
def write_uint64(fs: _FileWriter_t, num: int) -> None:
|
||||
fs.write(struct.pack("<Q", num))
|
||||
|
||||
def write_bool(fs: _FileWriter_t, boolean: bool) -> None:
|
||||
if boolean:
|
||||
write_uint8(fs, 1)
|
||||
else:
|
||||
write_uint8(fs, 0)
|
||||
|
||||
def write_float(fs: _FileWriter_t, fl: float) -> None:
|
||||
fs.write(struct.pack("<f", fl))
|
||||
|
||||
def write_world_matrix(fs: _FileWriter_t, mat: UTIL_virtools_types.VxMatrix) -> None:
|
||||
fs.write(struct.pack("<16f", *mat.to_tuple()))
|
||||
|
||||
def write_color(fs: _FileWriter_t, colors: UTIL_virtools_types.VxColor) -> None:
|
||||
fs.write(struct.pack("<fff", *colors.to_tuple_rgb()))
|
||||
|
||||
def write_uint32_array(fs: _FileWriter_t, vals: typing.Iterable[int], count: int) -> None:
|
||||
fs.write(struct.pack('<' + str(count) + 'I', *vals))
|
||||
|
||||
def write_float_array(fs: _FileWriter_t, vals: typing.Iterable[float], count: int) -> None:
|
||||
fs.write(struct.pack('<' + str(count) + 'f', *vals))
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reader Functions
|
||||
|
||||
def peek_stream(fs: _FileReader_t) -> bytes:
|
||||
res = fs.read(1)
|
||||
fs.seek(-1, os.SEEK_CUR)
|
||||
return res
|
||||
|
||||
def read_float(fs: _FileReader_t) -> float:
|
||||
return struct.unpack("f", fs.read(4))[0]
|
||||
|
||||
def read_uint8(fs: _FileReader_t) -> int:
|
||||
return struct.unpack("B", fs.read(1))[0]
|
||||
|
||||
def read_uint32(fs: _FileReader_t) -> int:
|
||||
return struct.unpack("I", fs.read(4))[0]
|
||||
|
||||
def read_uint64(fs: _FileReader_t) -> int:
|
||||
return struct.unpack("Q", fs.read(8))[0]
|
||||
|
||||
def read_string(fs: _FileReader_t) -> str:
|
||||
count = read_uint32(fs)
|
||||
return fs.read(count * 4).decode("utf_32_le")
|
||||
|
||||
def read_bool(fs: _FileReader_t) -> None:
|
||||
return read_uint8(fs) != 0
|
||||
|
||||
def read_world_materix(fs: _FileReader_t, mat: UTIL_virtools_types.VxMatrix) -> None:
|
||||
mat.from_tuple(struct.unpack("<16f", fs.read(16 * 4)))
|
||||
|
||||
def read_color(fs: _FileReader_t, target: UTIL_virtools_types.VxColor) -> None:
|
||||
target.from_const_rgb(struct.unpack("fff", fs.read(3 * 4)))
|
||||
|
||||
def read_uint32_array(fs: _FileReader_t, count: int) -> tuple[int, ...]:
|
||||
fmt: struct.Struct = struct.Struct('<' + str(count) + 'I')
|
||||
return fmt.unpack(fs.read(fmt.size))
|
||||
|
||||
def read_float_array(fs: _FileReader_t, count: int) -> tuple[float, ...]:
|
||||
fmt: struct.Struct = struct.Struct('<' + str(count) + 'f')
|
||||
return fmt.unpack(fs.read(fmt.size))
|
||||
|
||||
#endregion
|
173
bbp_ng/UTIL_functions.py
Normal file
173
bbp_ng/UTIL_functions.py
Normal file
@ -0,0 +1,173 @@
|
||||
import bpy, mathutils
|
||||
import math, typing, enum, sys
|
||||
|
||||
class BBPException(Exception):
|
||||
"""
|
||||
The exception thrown by Ballance Blender Plugin
|
||||
"""
|
||||
pass
|
||||
|
||||
def clamp_float(v: float, min_val: float, max_val: float) -> float:
|
||||
"""!
|
||||
@brief Clamp a float value
|
||||
|
||||
@param v[in] The value need to be clamp.
|
||||
@param min_val[in] The allowed minium value, including self.
|
||||
@param max_val[in] The allowed maxium value, including self.
|
||||
@return Clamped value.
|
||||
"""
|
||||
if (max_val < min_val): raise BBPException("Invalid range of clamp_float().")
|
||||
|
||||
if (v < min_val): return min_val
|
||||
elif (v > max_val): return max_val
|
||||
else: return v
|
||||
|
||||
def clamp_int(v: int, min_val: int, max_val: int) -> int:
|
||||
"""!
|
||||
@brief Clamp a int value
|
||||
|
||||
@param v[in] The value need to be clamp.
|
||||
@param min_val[in] The allowed minium value, including self.
|
||||
@param max_val[in] The allowed maxium value, including self.
|
||||
@return Clamped value.
|
||||
"""
|
||||
if (max_val < min_val): raise BBPException("Invalid range of clamp_int().")
|
||||
|
||||
if (v < min_val): return min_val
|
||||
elif (v > max_val): return max_val
|
||||
else: return v
|
||||
|
||||
def message_box(message: tuple[str, ...], title: str, icon: str):
|
||||
"""
|
||||
Show a message box in Blender. Non-block mode.
|
||||
|
||||
@param message[in] The text this message box displayed. Each item in this param will show as a single line.
|
||||
@param title[in] Message box title text.
|
||||
@param icon[in] The icon this message box displayed.
|
||||
"""
|
||||
def draw(self, context: bpy.types.Context):
|
||||
layout = self.layout
|
||||
for item in message:
|
||||
layout.label(text=item, translate=False)
|
||||
|
||||
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
|
||||
|
||||
def move_to_cursor(obj: bpy.types.Object):
|
||||
# use obj.matrix_world to move, not obj.location because this bug:
|
||||
# https://blender.stackexchange.com/questions/27667/incorrect-matrix-world-after-transformation
|
||||
# the update of matrix_world after setting location is not immediately.
|
||||
# and calling update() function for view_layer for the translation of each object is not suit for too much objects.
|
||||
|
||||
# obj.location = bpy.context.scene.cursor.location
|
||||
obj.matrix_world = obj.matrix_world @ mathutils.Matrix.Translation(bpy.context.scene.cursor.location - obj.location)
|
||||
|
||||
def add_into_scene_and_move_to_cursor(obj: bpy.types.Object):
|
||||
view_layer = bpy.context.view_layer
|
||||
collection = view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
|
||||
move_to_cursor(obj)
|
||||
|
||||
def select_certain_objects(objs: tuple[bpy.types.Object, ...]) -> None:
|
||||
# deselect all objects first
|
||||
bpy.ops.object.select_all(action = 'DESELECT')
|
||||
# if no objects, return
|
||||
if len(objs) == 0: return
|
||||
|
||||
# set selection for each object
|
||||
for obj in objs:
|
||||
obj.select_set(True)
|
||||
# select first object as active object
|
||||
bpy.context.view_layer.objects.active = objs[0]
|
||||
|
||||
def is_in_object_mode() -> bool:
|
||||
# get active object from context
|
||||
obj = bpy.context.active_object
|
||||
|
||||
# if there is no active object, we think it is in object mode
|
||||
if obj is None: return True
|
||||
|
||||
# simply check active object mode
|
||||
return obj.mode == 'OBJECT'
|
||||
|
||||
class EnumPropHelper():
|
||||
"""
|
||||
These class contain all functions related to EnumProperty, including generating `items`,
|
||||
parsing data from EnumProperty string value and getting EnumProperty acceptable string format from data.
|
||||
"""
|
||||
|
||||
# define some type hint
|
||||
_TFctToStr = typing.Callable[[typing.Any], str]
|
||||
_TFctFromStr = typing.Callable[[str], typing.Any]
|
||||
_TFctName = typing.Callable[[typing.Any], str]
|
||||
_TFctDesc = typing.Callable[[typing.Any], str]
|
||||
_TFctIcon = typing.Callable[[typing.Any], str | int]
|
||||
|
||||
# define class member
|
||||
|
||||
__mCollections: typing.Iterable[typing.Any]
|
||||
__mFctToStr: _TFctToStr
|
||||
__mFctFromStr: _TFctFromStr
|
||||
__mFctName: _TFctName
|
||||
__mFctDesc: _TFctDesc
|
||||
__mFctIcon: _TFctIcon
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
collections_: typing.Iterable[typing.Any],
|
||||
fct_to_str: _TFctToStr,
|
||||
fct_from_str: _TFctFromStr,
|
||||
fct_name: _TFctName,
|
||||
fct_desc: _TFctDesc,
|
||||
fct_icon: _TFctIcon):
|
||||
"""
|
||||
Initialize a EnumProperty helper.
|
||||
|
||||
@param collections_ [in] The collection all available enum property entries contained.
|
||||
It can be enum.Enum or a simple list/tuple/dict.
|
||||
@param fct_to_str [in] A function pointer converting data collection member to its string format.
|
||||
For enum.IntEnum, it can be simply `lambda x: str(x.value)`
|
||||
@param fct_from_str [in] A function pointer getting data collection member from its string format.
|
||||
For enum.IntEnum, it can be simply `lambda x: TEnum(int(x))`
|
||||
@param fct_name [in] A function pointer converting data collection member to its display name.
|
||||
@param fct_desc [in] Same as `fct_name` but return description instead. Return empty string, not None if no description.
|
||||
@param fct_icon [in] Same as `fct_name` but return the used icon instead. Return empty string if no icon.
|
||||
"""
|
||||
# assign member
|
||||
self.__mCollections = collections_
|
||||
self.__mFctToStr = fct_to_str
|
||||
self.__mFctFromStr = fct_from_str
|
||||
self.__mFctName = fct_name
|
||||
self.__mFctDesc = fct_desc
|
||||
self.__mFctIcon = fct_icon
|
||||
|
||||
def generate_items(self) -> tuple[tuple[str, str, str, int | str, int], ...]:
|
||||
"""
|
||||
Generate a tuple which can be applied to Blender EnumProperty's "items".
|
||||
"""
|
||||
# blender enum prop item format:
|
||||
# (token, display name, descriptions, icon, index)
|
||||
return tuple(
|
||||
(
|
||||
self.__mFctToStr(member), # call to_str as its token.
|
||||
self.__mFctName(member),
|
||||
self.__mFctDesc(member),
|
||||
self.__mFctIcon(member),
|
||||
idx # use hardcode index, not the collection member self.
|
||||
) for idx, member in enumerate(self.__mCollections)
|
||||
)
|
||||
|
||||
def get_selection(self, prop: str) -> typing.Any:
|
||||
"""
|
||||
Return collection member from given Blender EnumProp string data.
|
||||
"""
|
||||
# call from_str fct ptr
|
||||
return self.__mFctFromStr(prop)
|
||||
|
||||
def to_selection(self, val: typing.Any) -> str:
|
||||
"""
|
||||
Parse collection member to Blender EnumProp acceptable string format.
|
||||
"""
|
||||
# call to_str fct ptr
|
||||
return self.__mFctToStr(val)
|
||||
|
113
bbp_ng/UTIL_icons_manager.py
Normal file
113
bbp_ng/UTIL_icons_manager.py
Normal file
@ -0,0 +1,113 @@
|
||||
import bpy, bpy.utils.previews
|
||||
import os, enum, typing
|
||||
|
||||
class BlenderPresetIcons(enum.Enum):
|
||||
Info = 'INFO'
|
||||
Warning = 'ERROR'
|
||||
Error = 'CANCEL'
|
||||
|
||||
#region Custom Icons Helper
|
||||
|
||||
_g_SupportedImageExts: set[str] = set((
|
||||
'.png',
|
||||
))
|
||||
|
||||
_g_IconsManager: bpy.utils.previews.ImagePreviewCollection | None = None
|
||||
|
||||
_g_EmptyIcon: int = 0
|
||||
_g_IconPrefix: str = "BlcBldPlg_"
|
||||
|
||||
_g_BmeIconsMap: dict[str, int] = {}
|
||||
_g_BmeIconPrefix: str = _g_IconPrefix + 'Bme_'
|
||||
_g_ComponentIconsMap: dict[str, int] = {}
|
||||
_g_ComponentIconPrefix: str = _g_IconPrefix + 'Component_'
|
||||
_g_GroupIconsMap: dict[str, int] = {}
|
||||
_g_GroupIconPrefix: str = _g_IconPrefix + 'Group_'
|
||||
|
||||
def _iterate_folder_images(folder: str) -> typing.Iterator[tuple[str, str]]:
|
||||
for name in os.listdir(folder):
|
||||
# check whether it is file
|
||||
filepath: str = os.path.join(folder, name)
|
||||
if os.path.isfile(filepath):
|
||||
# check file exts
|
||||
(root, ext) = os.path.splitext(name)
|
||||
if ext.lower() in _g_SupportedImageExts:
|
||||
yield (filepath, root)
|
||||
|
||||
def _load_image_folder(
|
||||
folder: str,
|
||||
loader: bpy.utils.previews.ImagePreviewCollection,
|
||||
container: dict[str, int],
|
||||
name_prefix: str) -> None:
|
||||
# iterate folder
|
||||
for (filepath, filename_no_ext) in _iterate_folder_images(folder):
|
||||
# generate name for unique
|
||||
icon_name: str = name_prefix + filename_no_ext
|
||||
# load it
|
||||
loader.load(icon_name, filepath, 'IMAGE')
|
||||
# add into list. use plain name (not the unique name)
|
||||
container[filename_no_ext] = loader[icon_name].icon_id
|
||||
|
||||
#endregion
|
||||
|
||||
#region Custom Icons Visitors
|
||||
|
||||
def get_empty_icon() -> int:
|
||||
return _g_EmptyIcon
|
||||
|
||||
def get_bme_icon(name: str) -> int | None:
|
||||
return _g_BmeIconsMap.get(name, None)
|
||||
|
||||
def get_component_icon(name: str) -> int | None:
|
||||
return _g_ComponentIconsMap.get(name, None)
|
||||
|
||||
def get_group_icon(name: str) -> int | None:
|
||||
return _g_GroupIconsMap.get(name, None)
|
||||
|
||||
#endregion
|
||||
|
||||
def register() -> None:
|
||||
global _g_IconsManager
|
||||
global _g_EmptyIcon
|
||||
global _g_BmeIconsMap, _g_ComponentIconsMap, _g_GroupIconsMap
|
||||
|
||||
# create preview collection and get icon folder
|
||||
icons_folder: str = os.path.join(os.path.dirname(__file__), "icons")
|
||||
_g_IconsManager = bpy.utils.previews.new()
|
||||
|
||||
# load empty icon as default fallback
|
||||
empty_icon_name: str = _g_IconPrefix + 'EmptyIcon'
|
||||
_g_IconsManager.load(empty_icon_name, os.path.join(icons_folder, "Empty.png"), 'IMAGE')
|
||||
_g_EmptyIcon = _g_IconsManager[empty_icon_name].icon_id
|
||||
|
||||
# load bme, component, group icon
|
||||
_load_image_folder(
|
||||
os.path.join(icons_folder, 'bme'),
|
||||
_g_IconsManager,
|
||||
_g_BmeIconsMap,
|
||||
_g_BmeIconPrefix
|
||||
)
|
||||
_load_image_folder(
|
||||
os.path.join(icons_folder, 'component'),
|
||||
_g_IconsManager,
|
||||
_g_ComponentIconsMap,
|
||||
_g_ComponentIconPrefix
|
||||
)
|
||||
_load_image_folder(
|
||||
os.path.join(icons_folder, 'group'),
|
||||
_g_IconsManager,
|
||||
_g_GroupIconsMap,
|
||||
_g_GroupIconPrefix
|
||||
)
|
||||
|
||||
def unregister() -> None:
|
||||
global _g_IconsManager
|
||||
global _g_EmptyIcon
|
||||
global _g_BmeIconsMap, _g_ComponentIconsMap, _g_GroupIconsMap
|
||||
|
||||
bpy.utils.previews.remove(_g_IconsManager)
|
||||
_g_IconsManager = None
|
||||
|
||||
_g_BmeIconsMap.clear()
|
||||
_g_ComponentIconsMap.clear()
|
||||
_g_GroupIconsMap.clear()
|
233
bbp_ng/UTIL_ioport_shared.py
Normal file
233
bbp_ng/UTIL_ioport_shared.py
Normal file
@ -0,0 +1,233 @@
|
||||
import bpy
|
||||
import enum, typing
|
||||
from . import UTIL_virtools_types, UTIL_functions
|
||||
from . import PROP_ptrprop_resolver
|
||||
|
||||
## Intent
|
||||
# Some importer or exporter may share same properties.
|
||||
# So we create some shared class and user just need inherit them
|
||||
# and call general getter to get user selected data.
|
||||
# Also provide draw function thus caller do not need draw the params themselves.
|
||||
|
||||
class ConflictStrategy(enum.IntEnum):
|
||||
Rename = enum.auto()
|
||||
Current = enum.auto()
|
||||
_g_ConflictStrategyDesc: dict[ConflictStrategy, tuple[str, str]] = {
|
||||
ConflictStrategy.Rename: ('Rename', 'Rename the new one'),
|
||||
ConflictStrategy.Current: ('Use Current', 'Use current one'),
|
||||
}
|
||||
_g_EnumHelper_ConflictStrategy: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
|
||||
ConflictStrategy,
|
||||
lambda x: str(x.value),
|
||||
lambda x: ConflictStrategy(int(x)),
|
||||
lambda x: _g_ConflictStrategyDesc[x][0],
|
||||
lambda x: _g_ConflictStrategyDesc[x][1],
|
||||
lambda _: ''
|
||||
)
|
||||
|
||||
#region Assist Classes
|
||||
|
||||
class ExportEditModeBackup():
|
||||
"""
|
||||
The class which save Edit Mode when exporting and restore it after exporting.
|
||||
Because edit mode is not allowed when exporting.
|
||||
Support `with` statement.
|
||||
|
||||
```
|
||||
with ExportEditModeBackup():
|
||||
# do some exporting work
|
||||
blabla()
|
||||
# restore automatically when exiting "with"
|
||||
```
|
||||
"""
|
||||
mInEditMode: bool
|
||||
|
||||
def __init__(self):
|
||||
if bpy.context.object and bpy.context.object.mode == "EDIT":
|
||||
# set and toggle it. otherwise exporting will failed.
|
||||
self.mInEditMode = True
|
||||
bpy.ops.object.editmode_toggle()
|
||||
else:
|
||||
self.mInEditMode = False
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
if self.mInEditMode:
|
||||
bpy.ops.object.editmode_toggle()
|
||||
self.mInEditMode = False
|
||||
|
||||
class ConflictResolver():
|
||||
"""
|
||||
This class frequently used when importing objects.
|
||||
This class accept 4 conflict strategies for object, mesh, material and texture,
|
||||
and provide 4 general creation functions to handle these strategies.
|
||||
Each general creation functions will return an instance and a bool indicating whether this instance need be initialized.
|
||||
"""
|
||||
|
||||
__mObjectStrategy: ConflictStrategy
|
||||
__mMeshStrategy: ConflictStrategy
|
||||
__mMaterialStrategy: ConflictStrategy
|
||||
__mTextureStrategy: ConflictStrategy
|
||||
|
||||
def __init__(self, obj_strategy: ConflictStrategy, mesh_strategy: ConflictStrategy, mtl_strategy: ConflictStrategy, tex_strategy: ConflictStrategy):
|
||||
self.__mObjectStrategy = obj_strategy
|
||||
self.__mMeshStrategy = mesh_strategy
|
||||
self.__mMaterialStrategy = mtl_strategy
|
||||
self.__mTextureStrategy = tex_strategy
|
||||
|
||||
def create_object(self, name: str, data: bpy.types.Mesh) -> tuple[bpy.types.Object, bool]:
|
||||
"""
|
||||
Create object according to conflict strategy.
|
||||
`data` will only be applied when creating new object (no existing instance or strategy order rename)
|
||||
"""
|
||||
if self.__mObjectStrategy == ConflictStrategy.Current:
|
||||
old: bpy.types.Object | None = bpy.data.objects.get(name, None)
|
||||
if old is not None:
|
||||
return (old, False)
|
||||
return (bpy.data.objects.new(name, data), True)
|
||||
|
||||
def create_mesh(self, name: str) -> tuple[bpy.types.Mesh, bool]:
|
||||
if self.__mMeshStrategy == ConflictStrategy.Current:
|
||||
old: bpy.types.Mesh | None = bpy.data.meshes.get(name, None)
|
||||
if old is not None:
|
||||
return (old, False)
|
||||
return (bpy.data.meshes.new(name), True)
|
||||
|
||||
def create_material(self, name: str) -> tuple[bpy.types.Material, bool]:
|
||||
if self.__mMaterialStrategy == ConflictStrategy.Current:
|
||||
old: bpy.types.Material | None = bpy.data.materials.get(name, None)
|
||||
if old is not None:
|
||||
return (old, False)
|
||||
return (bpy.data.materials.new(name), True)
|
||||
|
||||
def create_texture(self, name: str, fct_cret: typing.Callable[[], bpy.types.Image]) -> tuple[bpy.types.Image, bool]:
|
||||
"""
|
||||
Create texture according to conflict strategy.
|
||||
If the strategy order current, it will return current existing instance.
|
||||
If no existing instance or strategy order rename, it will call `fct_cret` to create new texture.
|
||||
|
||||
Because texture do not have a general creation function, we frequently create it by other modules provided texture functions.
|
||||
So `fct_cret` is the real creation function. And it will not be executed if no creation happended.
|
||||
"""
|
||||
if self.__mTextureStrategy == ConflictStrategy.Current:
|
||||
old: bpy.types.Image | None = bpy.data.images.get(name, None)
|
||||
if old is not None:
|
||||
return (old, False)
|
||||
# create texture, set name, and return
|
||||
tex: bpy.types.Image = fct_cret()
|
||||
tex.name = name
|
||||
return (tex, True)
|
||||
|
||||
#endregion
|
||||
|
||||
class ImportParams():
|
||||
texture_conflict_strategy: bpy.props.EnumProperty(
|
||||
name = "Texture Name Conflict",
|
||||
items = _g_EnumHelper_ConflictStrategy.generate_items(),
|
||||
description = "Define how to process texture name conflict",
|
||||
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Current),
|
||||
)
|
||||
|
||||
material_conflict_strategy: bpy.props.EnumProperty(
|
||||
name = "Material Name Conflict",
|
||||
items = _g_EnumHelper_ConflictStrategy.generate_items(),
|
||||
description = "Define how to process material name conflict",
|
||||
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
|
||||
)
|
||||
|
||||
mesh_conflict_strategy: bpy.props.EnumProperty(
|
||||
name = "Mesh Name Conflict",
|
||||
items = _g_EnumHelper_ConflictStrategy.generate_items(),
|
||||
description = "Define how to process mesh name conflict",
|
||||
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
|
||||
)
|
||||
|
||||
object_conflict_strategy: bpy.props.EnumProperty(
|
||||
name = "Object Name Conflict",
|
||||
items = _g_EnumHelper_ConflictStrategy.generate_items(),
|
||||
description = "Define how to process object name conflict",
|
||||
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
|
||||
)
|
||||
|
||||
def draw_import_params(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.label(text = 'Object Name Conflict')
|
||||
layout.prop(self, 'object_conflict_strategy', text = '')
|
||||
layout.label(text = 'Mesh Name Conflict')
|
||||
layout.prop(self, 'mesh_conflict_strategy', text = '')
|
||||
layout.label(text = 'Material Name Conflict')
|
||||
layout.prop(self, 'material_conflict_strategy', text = '')
|
||||
layout.label(text = 'Texture Name Conflict')
|
||||
layout.prop(self, 'texture_conflict_strategy', text = '')
|
||||
|
||||
def general_get_texture_conflict_strategy(self) -> ConflictStrategy:
|
||||
return _g_EnumHelper_ConflictStrategy.get_selection(self.texture_conflict_strategy)
|
||||
|
||||
def general_get_material_conflict_strategy(self) -> ConflictStrategy:
|
||||
return _g_EnumHelper_ConflictStrategy.get_selection(self.material_conflict_strategy)
|
||||
|
||||
def general_get_mesh_conflict_strategy(self) -> ConflictStrategy:
|
||||
return _g_EnumHelper_ConflictStrategy.get_selection(self.mesh_conflict_strategy)
|
||||
|
||||
def general_get_object_conflict_strategy(self) -> ConflictStrategy:
|
||||
return _g_EnumHelper_ConflictStrategy.get_selection(self.object_conflict_strategy)
|
||||
|
||||
def general_get_conflict_resolver(self) -> ConflictResolver:
|
||||
return ConflictResolver(
|
||||
self.general_get_object_conflict_strategy(),
|
||||
self.general_get_mesh_conflict_strategy(),
|
||||
self.general_get_material_conflict_strategy(),
|
||||
self.general_get_texture_conflict_strategy()
|
||||
)
|
||||
|
||||
class ExportParams():
|
||||
export_mode: bpy.props.EnumProperty(
|
||||
name = "Export Mode",
|
||||
items = (
|
||||
('COLLECTION', "Collection", "Export a collection", 'OUTLINER_COLLECTION', 0),
|
||||
('OBJECT', "Object", "Export an object", 'OBJECT_DATA', 1),
|
||||
),
|
||||
)
|
||||
|
||||
def draw_export_params(self, layout: bpy.types.UILayout) -> None:
|
||||
# make prop expand horizontaly, not vertical.
|
||||
sublayout = layout.row()
|
||||
# draw switch
|
||||
sublayout.prop(self, "export_mode", expand = True)
|
||||
|
||||
# draw picker
|
||||
if self.export_mode == 'COLLECTION':
|
||||
PROP_ptrprop_resolver.draw_export_collection(layout)
|
||||
elif self.export_mode == 'OBJECT':
|
||||
PROP_ptrprop_resolver.draw_export_object(layout)
|
||||
|
||||
def general_get_export_objects(self) -> tuple[bpy.types.Object] | None:
|
||||
"""
|
||||
Return resolved exported objects or None if no selection.
|
||||
"""
|
||||
if self.export_mode == 'COLLECTION':
|
||||
col: bpy.types.Collection = PROP_ptrprop_resolver.get_export_collection()
|
||||
if col is None: return None
|
||||
else:
|
||||
return tuple(col.all_objects)
|
||||
else:
|
||||
obj: bpy.types.Object = PROP_ptrprop_resolver.get_export_object()
|
||||
if obj is None: return None
|
||||
else: return (obj, )
|
||||
|
||||
class VirtoolsParams():
|
||||
vt_encodings: bpy.props.StringProperty(
|
||||
name = "Encodings",
|
||||
description = "The encoding list used by Virtools engine to resolve object name. Use `;` to split multiple encodings",
|
||||
default = UTIL_virtools_types.g_PyBMapDefaultEncoding
|
||||
)
|
||||
|
||||
def draw_virtools_params(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.label(text = 'Encodings')
|
||||
layout.prop(self, 'vt_encodings', text = '')
|
||||
|
||||
def general_get_vt_encodings(self) -> tuple[str]:
|
||||
# get encoding, split it by `;` and strip blank chars.
|
||||
encodings: str = self.vt_encodings
|
||||
return tuple(map(lambda x: x.strip(), encodings.split(';')))
|
636
bbp_ng/UTIL_naming_convension.py
Normal file
636
bbp_ng/UTIL_naming_convension.py
Normal file
@ -0,0 +1,636 @@
|
||||
import bpy
|
||||
import typing, enum, re
|
||||
from . import UTIL_functions
|
||||
from . import PROP_virtools_group
|
||||
|
||||
#region Rename Error Reporter
|
||||
|
||||
class _RenameErrorType(enum.IntEnum):
|
||||
ERROR = enum.auto()
|
||||
WARNING = enum.auto()
|
||||
INFO = enum.auto()
|
||||
|
||||
class _RenameErrorItem():
|
||||
mErrType: _RenameErrorType
|
||||
mDescription: str
|
||||
|
||||
def __init__(self, err_t: _RenameErrorType, description: str):
|
||||
self.mErrType = err_t
|
||||
self.mDescription = description
|
||||
|
||||
class RenameErrorReporter():
|
||||
"""
|
||||
A basic 'rename error report' using simple prints in console.
|
||||
|
||||
This object can be used as a context manager.
|
||||
|
||||
It supports multiple levels of 'substeps' - you shall always enter at least one substep (because level 0
|
||||
has only one single step, representing the whole 'area' of the progress stuff).
|
||||
|
||||
You should give the object renaming of substeps each time you enter a new one.
|
||||
|
||||
Leaving a substep automatically steps by one the parent level.
|
||||
|
||||
```
|
||||
with RenameErrorReporter() as reporter:
|
||||
progress.enter_object(obj)
|
||||
|
||||
# process for object with reporter
|
||||
reporter.add_error('fork!')
|
||||
|
||||
progress.leave_object()
|
||||
```
|
||||
"""
|
||||
mAllObjCounter: int
|
||||
mFailedObjCounter: int
|
||||
|
||||
mErrList: list[_RenameErrorItem]
|
||||
mOldName: str
|
||||
mHasError: bool
|
||||
|
||||
def __init__(self):
|
||||
self.mAllObjCounter = 0
|
||||
self.mFailedObjCounter = 0
|
||||
|
||||
self.mErrList = []
|
||||
self.mOldName = ""
|
||||
self.mHasError = False
|
||||
|
||||
def add_error(self, description: str):
|
||||
self.mHasError = True
|
||||
self.mErrList.append(_RenameErrorItem(_RenameErrorType.ERROR, description))
|
||||
def add_warning(self, description: str):
|
||||
self.mErrList.append(_RenameErrorItem(_RenameErrorType.WARNING, description))
|
||||
def add_info(self, description: str):
|
||||
self.mErrList.append(_RenameErrorItem(_RenameErrorType.INFO, description))
|
||||
|
||||
def get_all_objs_count(self) -> int: return self.mAllObjCounter
|
||||
def get_failed_objs_count(self) -> int: return self.mFailedObjCounter
|
||||
|
||||
def __enter__(self):
|
||||
# print console report header
|
||||
print('============')
|
||||
print('Rename Report')
|
||||
print('------------')
|
||||
# return self as context
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
# print console report tail
|
||||
print('------------')
|
||||
print(f'All / Failed - {self.mAllObjCounter} / {self.mFailedObjCounter}')
|
||||
print('============')
|
||||
# reset variables
|
||||
self.mAllObjCounter = 0
|
||||
self.mFailedObjCounter = 0
|
||||
|
||||
def enter_object(self, obj: bpy.types.Object) -> None:
|
||||
# inc all counter
|
||||
self.mAllObjCounter += 1
|
||||
# assign old name
|
||||
self.mOldName = obj.name
|
||||
def leave_object(self, obj:bpy.types.Object) -> None:
|
||||
# if error list is empty, no need to report
|
||||
if len(self.mErrList) == 0: return
|
||||
|
||||
# inc failed if necessary
|
||||
if self.mHasError:
|
||||
self.mFailedObjCounter += 1
|
||||
|
||||
# output header
|
||||
# if new name is different with old name, output both of them
|
||||
new_name: str = obj.name
|
||||
if self.mOldName == new_name:
|
||||
print(f'For object "{new_name}"')
|
||||
else:
|
||||
print(f'For object "{new_name}" (Old name: "{self.mOldName}")')
|
||||
|
||||
# output error list with indent
|
||||
for item in self.mErrList:
|
||||
print('\t' + RenameErrorReporter.__erritem_to_string(item))
|
||||
|
||||
# clear error list for next object
|
||||
self.mErrList.clear()
|
||||
self.mHasError = False
|
||||
|
||||
@staticmethod
|
||||
def __errtype_to_string(err_v: _RenameErrorType) -> str:
|
||||
match(err_v):
|
||||
case _RenameErrorType.ERROR: return 'ERROR'
|
||||
case _RenameErrorType.WARNING: return 'WARN'
|
||||
case _RenameErrorType.INFO: return 'INFO'
|
||||
case _: raise UTIL_functions.BBPException("Unknown error type.")
|
||||
@staticmethod
|
||||
def __erritem_to_string(item: _RenameErrorItem) -> str:
|
||||
return f'[{RenameErrorReporter.__errtype_to_string(item.mErrType)}]\t{item.mDescription}'
|
||||
|
||||
#endregion
|
||||
|
||||
#region Naming Convention Used Types
|
||||
|
||||
class BallanceObjectType(enum.IntEnum):
|
||||
COMPONENT = enum.auto()
|
||||
|
||||
FLOOR = enum.auto()
|
||||
RAIL = enum.auto()
|
||||
WOOD = enum.auto()
|
||||
STOPPER = enum.auto()
|
||||
|
||||
LEVEL_START = enum.auto()
|
||||
LEVEL_END = enum.auto()
|
||||
CHECKPOINT = enum.auto()
|
||||
RESETPOINT = enum.auto()
|
||||
|
||||
DEPTH_CUBE = enum.auto()
|
||||
SKYLAYER = enum.auto()
|
||||
|
||||
DECORATION = enum.auto()
|
||||
|
||||
class BallanceObjectInfo():
|
||||
mBasicType: BallanceObjectType
|
||||
|
||||
## Only available for COMPONENT basic type
|
||||
mComponentType: str | None
|
||||
## Only available for COMPONENT, CHECKPOINT, RESETPOINT basic type
|
||||
# For COMPONENT, it indicate which sector this component belong to.
|
||||
# For CHECKPOINT, RESETPOINT, it indicate the index of this object.
|
||||
# In CHECKPOINT, RESETPOINT mode, the sector actually is the suffix number of these objects' name. So checkpoint starts with 1, not 0.
|
||||
mSector: int | None
|
||||
|
||||
def __init__(self, basic_type: BallanceObjectType):
|
||||
self.mBasicType = basic_type
|
||||
|
||||
@classmethod
|
||||
def create_from_component(cls, comp_type: str, sector: int):
|
||||
inst = cls(BallanceObjectType.COMPONENT)
|
||||
inst.mComponentType = comp_type
|
||||
inst.mSector = sector
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def create_from_checkpoint(cls, sector: int):
|
||||
inst = cls(BallanceObjectType.CHECKPOINT)
|
||||
inst.mSector = sector
|
||||
return inst
|
||||
@classmethod
|
||||
def create_from_resetpoint(cls, sector: int):
|
||||
inst = cls(BallanceObjectType.RESETPOINT)
|
||||
inst.mSector = sector
|
||||
return inst
|
||||
|
||||
@classmethod
|
||||
def create_from_others(cls, basic_type: BallanceObjectType):
|
||||
return cls(basic_type)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sector Extractor
|
||||
|
||||
_g_RegexBlcSectorGroup: re.Pattern = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
|
||||
|
||||
def extract_sector_from_name(group_name: str) -> int | None:
|
||||
"""
|
||||
A convenient function to extract sector index from given group name.
|
||||
This function also supports 999 sector plugin.
|
||||
|
||||
Not only in this module, but also in outside modules, this function is vary used to extract sector index info.
|
||||
|
||||
Function return the index extracted, or None if given group name is not a valid sector group.
|
||||
The valid sector index is range from 1 to 999 (inclusive)
|
||||
"""
|
||||
regex_result = _g_RegexBlcSectorGroup.match(group_name)
|
||||
if regex_result is not None:
|
||||
return int(regex_result.group(1))
|
||||
else:
|
||||
return None
|
||||
|
||||
def build_name_from_sector_index(sector_index: int) -> str:
|
||||
"""
|
||||
A convenient function to build Ballance recognizable sector group name.
|
||||
This function also supports 999 sector plugin.
|
||||
|
||||
This function also is used in this module or other modules outside.
|
||||
|
||||
Function return a sector name string. It basically the reverse operation of `extract_sector_from_name`.
|
||||
"""
|
||||
if sector_index == 9:
|
||||
return 'Sector_9'
|
||||
else:
|
||||
return f'Sector_{sector_index:0>2d}'
|
||||
|
||||
#endregion
|
||||
|
||||
#region Naming Convention Declaration
|
||||
|
||||
_g_BlcNormalComponents: set[str] = set((
|
||||
"P_Extra_Life",
|
||||
"P_Extra_Point",
|
||||
"P_Trafo_Paper",
|
||||
"P_Trafo_Stone",
|
||||
"P_Trafo_Wood",
|
||||
"P_Ball_Paper",
|
||||
"P_Ball_Stone",
|
||||
"P_Ball_Wood",
|
||||
"P_Box",
|
||||
"P_Dome",
|
||||
"P_Modul_01",
|
||||
"P_Modul_03",
|
||||
"P_Modul_08",
|
||||
"P_Modul_17",
|
||||
"P_Modul_18",
|
||||
"P_Modul_19",
|
||||
"P_Modul_25",
|
||||
"P_Modul_26",
|
||||
"P_Modul_29",
|
||||
"P_Modul_30",
|
||||
"P_Modul_34",
|
||||
"P_Modul_37",
|
||||
"P_Modul_41"
|
||||
))
|
||||
_g_BlcUniqueComponents: set[str] = set((
|
||||
"PS_Levelstart",
|
||||
"PE_Levelende",
|
||||
"PC_Checkpoints",
|
||||
"PR_Resetpoints"
|
||||
))
|
||||
_g_BlcFloor: set[str] = set((
|
||||
"Sound_HitID_01",
|
||||
"Sound_RollID_01"
|
||||
))
|
||||
_g_BlcWood: set[str] = set((
|
||||
"Sound_HitID_02",
|
||||
"Sound_RollID_02"
|
||||
))
|
||||
|
||||
class VirtoolsGroupConvention():
|
||||
cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + ')_(0[1-9]|[1-9][0-9])_.*$')
|
||||
cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_TwoFlames_(0[1-7])$')
|
||||
cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_Resetpoint_(0[1-8])$')
|
||||
|
||||
@staticmethod
|
||||
def __get_pcpr_from_name(name: str, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
|
||||
regex_result = VirtoolsGroupConvention.cRegexPC.match(name)
|
||||
if regex_result is not None:
|
||||
return BallanceObjectInfo.create_from_checkpoint(
|
||||
int(regex_result.group(1))
|
||||
)
|
||||
regex_result = VirtoolsGroupConvention.cRegexPR.match(name)
|
||||
if regex_result is not None:
|
||||
return BallanceObjectInfo.create_from_resetpoint(
|
||||
int(regex_result.group(1))
|
||||
)
|
||||
|
||||
if reporter: reporter.add_error("PC_Checkpoints or PR_Resetpoints detected. But couldn't get sector from name.")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def __get_sector_from_groups(gps: typing.Iterator[str]) -> int | None:
|
||||
# this counter is served for stupid
|
||||
# multi-sector-grouping accident.
|
||||
counter: int = 0
|
||||
last_matched_sector: int = 0
|
||||
for i in gps:
|
||||
regex_result: int | None = extract_sector_from_name(i)
|
||||
if regex_result is not None:
|
||||
last_matched_sector = regex_result
|
||||
counter += 1
|
||||
|
||||
if counter != 1: return None
|
||||
else: return last_matched_sector
|
||||
|
||||
@staticmethod
|
||||
def parse_from_object(obj: bpy.types.Object, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
|
||||
# create visitor
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
# if no group, we should consider it is decoration or skylayer
|
||||
if gp.get_count() == 0:
|
||||
if obj.name == 'SkyLayer': return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER)
|
||||
else: return BallanceObjectInfo.create_from_others(BallanceObjectType.DECORATION)
|
||||
|
||||
# try to filter unique elements first
|
||||
inter_gps: set[str] = gp.intersect_groups(_g_BlcUniqueComponents)
|
||||
if len(inter_gps) == 1:
|
||||
# get it
|
||||
match((tuple(inter_gps))[0]):
|
||||
case 'PS_Levelstart':
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_START)
|
||||
case 'PE_Levelende':
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_END)
|
||||
case 'PC_Checkpoints' | 'PR_Resetpoints':
|
||||
# these type's data should be gotten from its name
|
||||
return VirtoolsGroupConvention.__get_pcpr_from_name(obj.name, reporter)
|
||||
case _:
|
||||
if reporter: reporter.add_error("The match of Unique Component lost.")
|
||||
return None
|
||||
elif len(inter_gps) != 0:
|
||||
if reporter: reporter.add_error("A Multi-grouping Unique Component.")
|
||||
return None
|
||||
|
||||
# distinguish normal elements
|
||||
inter_gps = gp.intersect_groups(_g_BlcNormalComponents)
|
||||
if len(inter_gps) == 1:
|
||||
# get it
|
||||
# now try get its sector
|
||||
gotten_elements: str = (tuple(inter_gps))[0]
|
||||
gotten_sector: int | None = VirtoolsGroupConvention.__get_sector_from_groups(gp.iterate_groups())
|
||||
if gotten_sector is None:
|
||||
# fail to get sector
|
||||
if reporter: reporter.add_error("Component detected. But couldn't get sector from CKGroup data.")
|
||||
return None
|
||||
return BallanceObjectInfo.create_from_component(
|
||||
gotten_elements,
|
||||
gotten_sector
|
||||
)
|
||||
elif len(inter_gps) != 0:
|
||||
# must be a weird grouping, report it
|
||||
if reporter: reporter.add_error("A Multi-grouping Component.")
|
||||
return None
|
||||
|
||||
# distinguish road
|
||||
if gp.contain_group('Phys_FloorRails'):
|
||||
# rail
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.RAIL)
|
||||
elif gp.contain_group('Phys_Floors'):
|
||||
# distinguish it between Floor and Wood
|
||||
floor_result = gp.intersect_groups(_g_BlcFloor)
|
||||
rail_result = gp.intersect_groups(_g_BlcWood)
|
||||
if len(floor_result) > 0 and len(rail_result) == 0:
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.FLOOR)
|
||||
elif len(floor_result) == 0 and len(rail_result) > 0:
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.WOOD)
|
||||
else:
|
||||
if reporter: reporter.add_warning("Can't distinguish object between Floors and Rails. Suppose it is Floors.")
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.FLOOR)
|
||||
elif gp.contain_group('Phys_FloorStopper'):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.STOPPER)
|
||||
elif gp.contain_group('DepthTestCubes'):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.DEPTH_CUBE)
|
||||
|
||||
# no matched
|
||||
if reporter: reporter.add_error("Group match lost.")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def set_to_object(obj: bpy.types.Object, info: BallanceObjectInfo, reporter: RenameErrorReporter | None) -> bool:
|
||||
# create visitor
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
# match by basic type
|
||||
match(info.mBasicType):
|
||||
case BallanceObjectType.DECORATION: pass # decoration do not need group
|
||||
case BallanceObjectType.SKYLAYER: pass # sky layer do not need group
|
||||
|
||||
case BallanceObjectType.LEVEL_START:
|
||||
gp.add_group('PS_Levelstart')
|
||||
case BallanceObjectType.LEVEL_END:
|
||||
gp.add_group('PE_Levelende')
|
||||
case BallanceObjectType.CHECKPOINT:
|
||||
gp.add_group('PC_Checkpoints')
|
||||
case BallanceObjectType.RESETPOINT:
|
||||
gp.add_group('PR_Resetpoints')
|
||||
|
||||
case BallanceObjectType.DEPTH_CUBE:
|
||||
gp.add_group('PE_Levelende')
|
||||
|
||||
case BallanceObjectType.FLOOR:
|
||||
gp.add_group('Phys_Floors')
|
||||
gp.add_group('Sound_HitID_01')
|
||||
gp.add_group('Sound_RollID_01')
|
||||
# floor type also need group into shadow group.
|
||||
gp.add_group('Shadow')
|
||||
case BallanceObjectType.RAIL:
|
||||
gp.add_group('Phys_FloorRails')
|
||||
gp.add_group('Sound_HitID_03')
|
||||
gp.add_group('Sound_RollID_03')
|
||||
case BallanceObjectType.WOOD:
|
||||
gp.add_group('Phys_Floors')
|
||||
gp.add_group('Sound_HitID_02')
|
||||
gp.add_group('Sound_RollID_02')
|
||||
case BallanceObjectType.STOPPER:
|
||||
gp.add_group('Phys_FloorStopper')
|
||||
|
||||
case BallanceObjectType.COMPONENT:
|
||||
# group into component type
|
||||
# use typing.cast() to force linter accept it because None is impossible
|
||||
gp.add_group(typing.cast(str, info.mComponentType))
|
||||
# group to sector
|
||||
gp.add_group(build_name_from_sector_index(typing.cast(int, info.mSector)))
|
||||
|
||||
case _:
|
||||
if reporter is not None:
|
||||
reporter.add_error('No matched info.')
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
class YYCToolchainConvention():
|
||||
@staticmethod
|
||||
def parse_from_name(name: str, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
|
||||
# check component first
|
||||
regex_result = VirtoolsGroupConvention.cRegexComponent.match(name) # use vt one because they are same
|
||||
if regex_result is not None:
|
||||
return BallanceObjectInfo.create_from_component(
|
||||
regex_result.group(1),
|
||||
int(regex_result.group(2))
|
||||
)
|
||||
|
||||
# check PC PR elements
|
||||
regex_result = VirtoolsGroupConvention.cRegexPC.match(name) # use vt one because they are same
|
||||
if regex_result is not None:
|
||||
return BallanceObjectInfo.create_from_checkpoint(
|
||||
int(regex_result.group(1))
|
||||
)
|
||||
regex_result = VirtoolsGroupConvention.cRegexPR.match(name) # use vt one because they are same
|
||||
if regex_result is not None:
|
||||
return BallanceObjectInfo.create_from_resetpoint(
|
||||
int(regex_result.group(1))
|
||||
)
|
||||
|
||||
# check other unique elements
|
||||
if name == "PS_FourFlames_01":
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_START)
|
||||
if name == "PE_Balloon_01":
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_END)
|
||||
|
||||
# process floors
|
||||
if name.startswith("A_Floor"):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.FLOOR)
|
||||
if name.startswith("A_Rail"):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.RAIL)
|
||||
if name.startswith("A_Wood"):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.WOOD)
|
||||
if name.startswith("A_Stopper"):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.STOPPER)
|
||||
|
||||
# process others
|
||||
if name.startswith("DepthCubes"):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.DEPTH_CUBE)
|
||||
if name.startswith("D_"):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.DECORATION)
|
||||
if name == 'SkyLayer':
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER)
|
||||
|
||||
if reporter is not None:
|
||||
reporter.add_error("Name match lost.")
|
||||
return None
|
||||
|
||||
|
||||
@staticmethod
|
||||
def parse_from_object(obj: bpy.types.Object, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
|
||||
return YYCToolchainConvention.parse_from_name(obj.name, reporter)
|
||||
|
||||
@staticmethod
|
||||
def set_to_name(info: BallanceObjectInfo, reporter: RenameErrorReporter | None) -> str | None:
|
||||
match(info.mBasicType):
|
||||
case BallanceObjectType.DECORATION:
|
||||
return 'D_'
|
||||
case BallanceObjectType.SKYLAYER:
|
||||
return 'SkyLayer'
|
||||
|
||||
case BallanceObjectType.LEVEL_START:
|
||||
return 'PS_FourFlames_01'
|
||||
case BallanceObjectType.LEVEL_END:
|
||||
return 'PE_Balloon_01'
|
||||
case BallanceObjectType.CHECKPOINT:
|
||||
return f'PC_TwoFlames_{info.mSector:0>2d}'
|
||||
case BallanceObjectType.RESETPOINT:
|
||||
return f'PR_Resetpoint_{info.mSector:0>2d}'
|
||||
|
||||
case BallanceObjectType.DEPTH_CUBE:
|
||||
return 'DepthCubes_'
|
||||
|
||||
case BallanceObjectType.FLOOR:
|
||||
return 'A_Floor_'
|
||||
case BallanceObjectType.RAIL:
|
||||
return 'A_Rail_'
|
||||
case BallanceObjectType.WOOD:
|
||||
return 'A_Wood_'
|
||||
case BallanceObjectType.STOPPER:
|
||||
return 'A_Stopper_'
|
||||
|
||||
case BallanceObjectType.COMPONENT:
|
||||
return '{}_{:0>2d}_'.format(
|
||||
info.mComponentType, info.mSector)
|
||||
|
||||
case _:
|
||||
if reporter is not None:
|
||||
reporter.add_error('No matched info.')
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def set_to_object(obj: bpy.types.Object, info: BallanceObjectInfo, reporter: RenameErrorReporter | None) -> bool:
|
||||
expect_name: str | None = YYCToolchainConvention.set_to_name(info, reporter)
|
||||
if expect_name is None: return False
|
||||
|
||||
obj.name = expect_name
|
||||
return True
|
||||
|
||||
class ImengyuConvention():
|
||||
cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + '):[^:]*:([1-9]|[1-9][0-9])$')
|
||||
cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_CheckPoint:([0-9]+)$')
|
||||
cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_ResetPoint:([0-9]+)$')
|
||||
|
||||
@staticmethod
|
||||
def parse_from_name(name: str, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
|
||||
# check component first
|
||||
regex_result = ImengyuConvention.cRegexComponent.match(name)
|
||||
if regex_result is not None:
|
||||
return BallanceObjectInfo.create_from_component(
|
||||
regex_result.group(1),
|
||||
int(regex_result.group(2))
|
||||
)
|
||||
|
||||
# check PC PR elements
|
||||
regex_result = ImengyuConvention.cRegexPC.match(name)
|
||||
if regex_result is not None:
|
||||
return BallanceObjectInfo.create_from_checkpoint(
|
||||
int(regex_result.group(1))
|
||||
)
|
||||
regex_result = ImengyuConvention.cRegexPR.match(name)
|
||||
if regex_result is not None:
|
||||
return BallanceObjectInfo.create_from_resetpoint(
|
||||
int(regex_result.group(1))
|
||||
)
|
||||
|
||||
# check other unique elements
|
||||
if name == "PS_LevelStart":
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_START)
|
||||
if name == "PE_LevelEnd":
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.LEVEL_END)
|
||||
|
||||
# process floors
|
||||
if name.startswith("S_Floors"):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.FLOOR)
|
||||
if name.startswith("S_FloorRails"):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.RAIL)
|
||||
if name.startswith("S_FloorWoods"):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.WOOD)
|
||||
if name.startswith("S_FloorStopper"):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.STOPPER)
|
||||
|
||||
# process others
|
||||
if name.startswith("DepthTestCubes"):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.DEPTH_CUBE)
|
||||
if name.startswith("O_"):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.DECORATION)
|
||||
if name == 'SkyLayer':
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER)
|
||||
|
||||
if reporter is not None:
|
||||
reporter.add_error("Name match lost.")
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def parse_from_object(obj: bpy.types.Object, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
|
||||
return ImengyuConvention.parse_from_name(obj.name, reporter)
|
||||
|
||||
@staticmethod
|
||||
def set_to_name(info: BallanceObjectInfo, oldname: str | None, reporter: RenameErrorReporter | None) -> str | None:
|
||||
match(info.mBasicType):
|
||||
case BallanceObjectType.DECORATION:
|
||||
return 'O_'
|
||||
case BallanceObjectType.SKYLAYER:
|
||||
return 'SkyLayer'
|
||||
|
||||
case BallanceObjectType.LEVEL_START:
|
||||
return 'PS_LevelStart'
|
||||
case BallanceObjectType.LEVEL_END:
|
||||
return 'PE_LevelEnd'
|
||||
case BallanceObjectType.CHECKPOINT:
|
||||
return f'PR_ResetPoint:{info.mSector:d}'
|
||||
case BallanceObjectType.RESETPOINT:
|
||||
return f'PC_CheckPoint:{info.mSector:d}'
|
||||
|
||||
case BallanceObjectType.DEPTH_CUBE:
|
||||
return 'DepthTestCubes'
|
||||
|
||||
case BallanceObjectType.FLOOR:
|
||||
return 'S_Floors'
|
||||
case BallanceObjectType.RAIL:
|
||||
return 'S_FloorWoods'
|
||||
case BallanceObjectType.WOOD:
|
||||
return 'S_FloorRails'
|
||||
case BallanceObjectType.STOPPER:
|
||||
return 'S_FloorStopper'
|
||||
|
||||
case BallanceObjectType.COMPONENT:
|
||||
return '{}:{}:{:d}'.format(
|
||||
info.mComponentType,
|
||||
oldname.replace(':', '_') if oldname is not None else '',
|
||||
info.mSector
|
||||
)
|
||||
|
||||
case _:
|
||||
if reporter is not None:
|
||||
reporter.add_error('No matched info.')
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def set_to_object(obj: bpy.types.Object, info: BallanceObjectInfo, reporter: RenameErrorReporter | None) -> bool:
|
||||
expect_name: str | None = ImengyuConvention.set_to_name(info, obj.name, reporter)
|
||||
if expect_name is None: return False
|
||||
|
||||
obj.name = expect_name
|
||||
return True
|
||||
|
||||
|
||||
#endregion
|
238
bbp_ng/UTIL_virtools_types.py
Normal file
238
bbp_ng/UTIL_virtools_types.py
Normal file
@ -0,0 +1,238 @@
|
||||
import mathutils
|
||||
import typing, sys
|
||||
from . import UTIL_functions
|
||||
|
||||
# extract all declarations in PyBMap
|
||||
from .PyBMap.virtools_types import *
|
||||
# and add some patches for them
|
||||
# mainly patch them with functions exchanging data with blender
|
||||
# and the convertion between differnet coordinate system.
|
||||
# hint: `co` mean coordinate system in blender.
|
||||
|
||||
#region VxVector2 Patch
|
||||
|
||||
def vxvector2_conv_co(self: VxVector2) -> None:
|
||||
"""
|
||||
Convert UV coordinate system between Virtools and Blender.
|
||||
"""
|
||||
self.y = -self.y
|
||||
|
||||
#endregion
|
||||
|
||||
#region VxVector3 Patch
|
||||
|
||||
def vxvector3_conv_co(self: VxVector3) -> None:
|
||||
"""
|
||||
Convert Position or Normal coordinate system between Virtools and Blender.
|
||||
"""
|
||||
self.y, self.z = self.z, self.y
|
||||
|
||||
#endregion
|
||||
|
||||
#region VxMatrix Patch
|
||||
|
||||
def vxmatrix_conv_co(self: VxMatrix) -> None:
|
||||
"""
|
||||
Convert World Matrix coordinate system between Virtools and Blender.
|
||||
"""
|
||||
# swap column 1 and 2
|
||||
for i in range(4):
|
||||
self.data[i][1], self.data[i][2] = self.data[i][2], self.data[i][1]
|
||||
# swap row 1 and 2
|
||||
for i in range(4):
|
||||
self.data[1][i], self.data[2][i] = self.data[2][i], self.data[1][i]
|
||||
|
||||
def vxmatrix_from_blender(self: VxMatrix, data_: mathutils.Matrix) -> None:
|
||||
"""
|
||||
Set matrix by blender matrix.
|
||||
"""
|
||||
# transposed first
|
||||
data: mathutils.Matrix = data_.transposed()
|
||||
(
|
||||
self.data[0][0], self.data[0][1], self.data[0][2], self.data[0][3],
|
||||
self.data[1][0], self.data[1][1], self.data[1][2], self.data[1][3],
|
||||
self.data[2][0], self.data[2][1], self.data[2][2], self.data[2][3],
|
||||
self.data[3][0], self.data[3][1], self.data[3][2], self.data[3][3]
|
||||
) = (
|
||||
data[0][0], data[0][1], data[0][2], data[0][3],
|
||||
data[1][0], data[1][1], data[1][2], data[1][3],
|
||||
data[2][0], data[2][1], data[2][2], data[2][3],
|
||||
data[3][0], data[3][1], data[3][2], data[3][3]
|
||||
)
|
||||
|
||||
def vxmatrix_to_blender(self: VxMatrix) -> mathutils.Matrix:
|
||||
"""
|
||||
Get blender matrix from this matrix
|
||||
"""
|
||||
data: mathutils.Matrix = mathutils.Matrix((
|
||||
(self.data[0][0], self.data[0][1], self.data[0][2], self.data[0][3]),
|
||||
(self.data[1][0], self.data[1][1], self.data[1][2], self.data[1][3]),
|
||||
(self.data[2][0], self.data[2][1], self.data[2][2], self.data[2][3]),
|
||||
(self.data[3][0], self.data[3][1], self.data[3][2], self.data[3][3]),
|
||||
))
|
||||
# transpose self
|
||||
data.transpose()
|
||||
return data
|
||||
|
||||
#endregion
|
||||
|
||||
#region Blender EnumProperty Creation
|
||||
|
||||
class EnumAnnotation():
|
||||
mDisplayName: str
|
||||
mDescription: str
|
||||
def __init__(self, display_name: str, description: str):
|
||||
self.mDisplayName = display_name
|
||||
self.mDescription = description
|
||||
|
||||
_g_Annotation: dict[type, dict[int, EnumAnnotation]] = {
|
||||
VXTEXTURE_BLENDMODE: {
|
||||
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_DECAL.value: EnumAnnotation("Decal", "Texture replace any material information "),
|
||||
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MODULATE.value: EnumAnnotation("Modulate", "Texture and material are combine. Alpha information of the texture replace material alpha component. "),
|
||||
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_DECALALPHA.value: EnumAnnotation("Decal Alpha", "Alpha information in the texture specify how material and texture are combined. Alpha information of the texture replace material alpha component. "),
|
||||
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MODULATEALPHA.value: EnumAnnotation("Modulate Alpha", "Alpha information in the texture specify how material and texture are combined "),
|
||||
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_DECALMASK.value: EnumAnnotation("Decal Mask", ""),
|
||||
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MODULATEMASK.value: EnumAnnotation("Modulate Mask", ""),
|
||||
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_COPY.value: EnumAnnotation("Copy", "Equivalent to DECAL "),
|
||||
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_ADD.value: EnumAnnotation("Add", ""),
|
||||
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_DOTPRODUCT3.value: EnumAnnotation("Dot Product 3", "Perform a Dot Product 3 between texture (normal map) and a referential vector given in VXRENDERSTATE_TEXTUREFACTOR. "),
|
||||
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MAX.value: EnumAnnotation("Max", ""),
|
||||
},
|
||||
VXTEXTURE_FILTERMODE: {
|
||||
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_NEAREST.value: EnumAnnotation("Nearest", "No Filter "),
|
||||
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEAR.value: EnumAnnotation("Linear", "Bilinear Interpolation "),
|
||||
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_MIPNEAREST.value: EnumAnnotation("Mip Nearest", "Mip mapping "),
|
||||
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_MIPLINEAR.value: EnumAnnotation("Mip Linear", "Mip Mapping with Bilinear interpolation "),
|
||||
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEARMIPNEAREST.value: EnumAnnotation("Linear Mip Nearest", "Mip Mapping with Bilinear interpolation between mipmap levels. "),
|
||||
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEARMIPLINEAR.value: EnumAnnotation("Linear Mip Linear", "Trilinear Filtering "),
|
||||
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_ANISOTROPIC.value: EnumAnnotation("Anisotropic", "Anisotropic filtering "),
|
||||
},
|
||||
VXBLEND_MODE: {
|
||||
VXBLEND_MODE.VXBLEND_ZERO.value: EnumAnnotation("Zero", "Blend factor is (0, 0, 0, 0). "),
|
||||
VXBLEND_MODE.VXBLEND_ONE.value: EnumAnnotation("One", "Blend factor is (1, 1, 1, 1). "),
|
||||
VXBLEND_MODE.VXBLEND_SRCCOLOR.value: EnumAnnotation("Src Color", "Blend factor is (Rs, Gs, Bs, As). "),
|
||||
VXBLEND_MODE.VXBLEND_INVSRCCOLOR.value: EnumAnnotation("Inv Src Color", "Blend factor is (1-Rs, 1-Gs, 1-Bs, 1-As). "),
|
||||
VXBLEND_MODE.VXBLEND_SRCALPHA.value: EnumAnnotation("Src Alpha", "Blend factor is (As, As, As, As). "),
|
||||
VXBLEND_MODE.VXBLEND_INVSRCALPHA.value: EnumAnnotation("Inv Src Alpha", "Blend factor is (1-As, 1-As, 1-As, 1-As). "),
|
||||
VXBLEND_MODE.VXBLEND_DESTALPHA.value: EnumAnnotation("Dest Alpha", "Blend factor is (Ad, Ad, Ad, Ad). "),
|
||||
VXBLEND_MODE.VXBLEND_INVDESTALPHA.value: EnumAnnotation("Inv Dest Alpha", "Blend factor is (1-Ad, 1-Ad, 1-Ad, 1-Ad). "),
|
||||
VXBLEND_MODE.VXBLEND_DESTCOLOR.value: EnumAnnotation("Dest Color", "Blend factor is (Rd, Gd, Bd, Ad). "),
|
||||
VXBLEND_MODE.VXBLEND_INVDESTCOLOR.value: EnumAnnotation("Inv Dest Color", "Blend factor is (1-Rd, 1-Gd, 1-Bd, 1-Ad). "),
|
||||
VXBLEND_MODE.VXBLEND_SRCALPHASAT.value: EnumAnnotation("Src Alpha Sat", "Blend factor is (f, f, f, 1); f = min(As, 1-Ad). "),
|
||||
#VXBLEND_MODE.VXBLEND_BOTHSRCALPHA.value: EnumAnnotation("Both Src Alpha", "Source blend factor is (As, As, As, As) and destination blend factor is (1-As, 1-As, 1-As, 1-As) "),
|
||||
#VXBLEND_MODE.VXBLEND_BOTHINVSRCALPHA.value: EnumAnnotation("Both Inv Src Alpha", "Source blend factor is (1-As, 1-As, 1-As, 1-As) and destination blend factor is (As, As, As, As) "),
|
||||
},
|
||||
VXTEXTURE_ADDRESSMODE: {
|
||||
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSWRAP.value: EnumAnnotation("Wrap", "Default mesh wrap mode is used (see CKMesh::SetWrapMode) "),
|
||||
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSMIRROR.value: EnumAnnotation("Mirror", "Texture coordinates outside the range [0..1] are flipped evenly. "),
|
||||
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSCLAMP.value: EnumAnnotation("Clamp", "Texture coordinates greater than 1.0 are set to 1.0, and values less than 0.0 are set to 0.0. "),
|
||||
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSBORDER.value: EnumAnnotation("Border", "When texture coordinates are greater than 1.0 or less than 0.0 texture is set to a color defined in CKMaterial::SetTextureBorderColor. "),
|
||||
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSMIRRORONCE.value: EnumAnnotation("Mirror Once", " "),
|
||||
},
|
||||
VXFILL_MODE: {
|
||||
VXFILL_MODE.VXFILL_POINT.value: EnumAnnotation("Point", "Vertices rendering "),
|
||||
VXFILL_MODE.VXFILL_WIREFRAME.value: EnumAnnotation("Wireframe", "Edges rendering "),
|
||||
VXFILL_MODE.VXFILL_SOLID.value: EnumAnnotation("Solid", "Face rendering "),
|
||||
},
|
||||
VXSHADE_MODE: {
|
||||
VXSHADE_MODE.VXSHADE_FLAT.value: EnumAnnotation("Flat", "Flat Shading "),
|
||||
VXSHADE_MODE.VXSHADE_GOURAUD.value: EnumAnnotation("Gouraud", "Gouraud Shading "),
|
||||
VXSHADE_MODE.VXSHADE_PHONG.value: EnumAnnotation("Phong", "Phong Shading (Not yet supported by most implementation) "),
|
||||
},
|
||||
VXCMPFUNC: {
|
||||
VXCMPFUNC.VXCMP_NEVER.value: EnumAnnotation("Never", "Always fail the test. "),
|
||||
VXCMPFUNC.VXCMP_LESS.value: EnumAnnotation("Less", "Accept if value if less than current value. "),
|
||||
VXCMPFUNC.VXCMP_EQUAL.value: EnumAnnotation("Equal", "Accept if value if equal than current value. "),
|
||||
VXCMPFUNC.VXCMP_LESSEQUAL.value: EnumAnnotation("Less Equal", "Accept if value if less or equal than current value. "),
|
||||
VXCMPFUNC.VXCMP_GREATER.value: EnumAnnotation("Greater", "Accept if value if greater than current value. "),
|
||||
VXCMPFUNC.VXCMP_NOTEQUAL.value: EnumAnnotation("Not Equal", "Accept if value if different than current value. "),
|
||||
VXCMPFUNC.VXCMP_GREATEREQUAL.value: EnumAnnotation("Greater Equal", "Accept if value if greater or equal current value. "),
|
||||
VXCMPFUNC.VXCMP_ALWAYS.value: EnumAnnotation("Always", "Always accept the test. "),
|
||||
},
|
||||
CK_TEXTURE_SAVEOPTIONS: {
|
||||
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_RAWDATA.value: EnumAnnotation("Raw Data", "Save raw data inside file. The bitmap is saved in a raw 32 bit per pixel format. "),
|
||||
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL.value: EnumAnnotation("External", "Store only the file name for the texture. The bitmap file must be present in the bitmap paths when loading the composition. "),
|
||||
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_IMAGEFORMAT.value: EnumAnnotation("Image Format", "Save using format specified. The bitmap data will be converted to the specified format by the correspondant bitmap plugin and saved inside file. "),
|
||||
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_USEGLOBAL.value: EnumAnnotation("Use Global", "Use Global settings, that is the settings given with CKContext::SetGlobalImagesSaveOptions. (Not valid when using CKContext::SetImagesSaveOptions). "),
|
||||
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_INCLUDEORIGINALFILE.value: EnumAnnotation("Include Original File", "Insert original image file inside CMO file. The bitmap file that was used originally for the texture or sprite will be append to the composition file and extracted when the file is loaded. "),
|
||||
},
|
||||
VX_PIXELFORMAT: {
|
||||
VX_PIXELFORMAT._32_ARGB8888.value: EnumAnnotation("32 Bits ARGB8888", "32-bit ARGB pixel format with alpha "),
|
||||
VX_PIXELFORMAT._32_RGB888.value: EnumAnnotation("32 Bits RGB888", "32-bit RGB pixel format without alpha "),
|
||||
VX_PIXELFORMAT._24_RGB888.value: EnumAnnotation("24 Bits RGB888", "24-bit RGB pixel format "),
|
||||
VX_PIXELFORMAT._16_RGB565.value: EnumAnnotation("16 Bits RGB565", "16-bit RGB pixel format "),
|
||||
VX_PIXELFORMAT._16_RGB555.value: EnumAnnotation("16 Bits RGB555", "16-bit RGB pixel format (5 bits per color) "),
|
||||
VX_PIXELFORMAT._16_ARGB1555.value: EnumAnnotation("16 Bits ARGB1555", "16-bit ARGB pixel format (5 bits per color + 1 bit for alpha) "),
|
||||
VX_PIXELFORMAT._16_ARGB4444.value: EnumAnnotation("16 Bits ARGB4444", "16-bit ARGB pixel format (4 bits per color) "),
|
||||
VX_PIXELFORMAT._8_RGB332.value: EnumAnnotation("8 Bits RGB332", "8-bit RGB pixel format "),
|
||||
VX_PIXELFORMAT._8_ARGB2222.value: EnumAnnotation("8 Bits ARGB2222", "8-bit ARGB pixel format "),
|
||||
VX_PIXELFORMAT._32_ABGR8888.value: EnumAnnotation("32 Bits ABGR8888", "32-bit ABGR pixel format "),
|
||||
VX_PIXELFORMAT._32_RGBA8888.value: EnumAnnotation("32 Bits RGBA8888", "32-bit RGBA pixel format "),
|
||||
VX_PIXELFORMAT._32_BGRA8888.value: EnumAnnotation("32 Bits BGRA8888", "32-bit BGRA pixel format "),
|
||||
VX_PIXELFORMAT._32_BGR888.value: EnumAnnotation("32 Bits BGR888", "32-bit BGR pixel format "),
|
||||
VX_PIXELFORMAT._24_BGR888.value: EnumAnnotation("24 Bits BGR888", "24-bit BGR pixel format "),
|
||||
VX_PIXELFORMAT._16_BGR565.value: EnumAnnotation("16 Bits BGR565", "16-bit BGR pixel format "),
|
||||
VX_PIXELFORMAT._16_BGR555.value: EnumAnnotation("16 Bits BGR555", "16-bit BGR pixel format (5 bits per color) "),
|
||||
VX_PIXELFORMAT._16_ABGR1555.value: EnumAnnotation("16 Bits ABGR1555", "16-bit ABGR pixel format (5 bits per color + 1 bit for alpha) "),
|
||||
VX_PIXELFORMAT._16_ABGR4444.value: EnumAnnotation("16 Bits ABGR4444", "16-bit ABGR pixel format (4 bits per color) "),
|
||||
VX_PIXELFORMAT._DXT1.value: EnumAnnotation("DXT1", "S3/DirectX Texture Compression 1 "),
|
||||
VX_PIXELFORMAT._DXT2.value: EnumAnnotation("DXT2", "S3/DirectX Texture Compression 2 "),
|
||||
VX_PIXELFORMAT._DXT3.value: EnumAnnotation("DXT3", "S3/DirectX Texture Compression 3 "),
|
||||
VX_PIXELFORMAT._DXT4.value: EnumAnnotation("DXT4", "S3/DirectX Texture Compression 4 "),
|
||||
VX_PIXELFORMAT._DXT5.value: EnumAnnotation("DXT5", "S3/DirectX Texture Compression 5 "),
|
||||
VX_PIXELFORMAT._16_V8U8.value: EnumAnnotation("16 Bits V8U8", "16-bit Bump Map format format (8 bits per color) "),
|
||||
VX_PIXELFORMAT._32_V16U16.value: EnumAnnotation("32 Bits V16U16", "32-bit Bump Map format format (16 bits per color) "),
|
||||
VX_PIXELFORMAT._16_L6V5U5.value: EnumAnnotation("16 Bits L6V5U5", "16-bit Bump Map format format with luminance "),
|
||||
VX_PIXELFORMAT._32_X8L8V8U8.value: EnumAnnotation("32 Bits X8L8V8U8", "32-bit Bump Map format format with luminance "),
|
||||
VX_PIXELFORMAT._8_ABGR8888_CLUT.value: EnumAnnotation("8 Bits ABGR8888 CLUT", "8 bits indexed CLUT (ABGR) "),
|
||||
VX_PIXELFORMAT._8_ARGB8888_CLUT.value: EnumAnnotation("8 Bits ARGB8888 CLUT", "8 bits indexed CLUT (ARGB) "),
|
||||
VX_PIXELFORMAT._4_ABGR8888_CLUT.value: EnumAnnotation("4 Bits ABGR8888 CLUT", "4 bits indexed CLUT (ABGR) "),
|
||||
VX_PIXELFORMAT._4_ARGB8888_CLUT.value: EnumAnnotation("4 Bits ARGB8888 CLUT", "4 bits indexed CLUT (ARGB) "),
|
||||
},
|
||||
VXMESH_LITMODE: {
|
||||
VXMESH_LITMODE.VX_PRELITMESH.value: EnumAnnotation("Prelit", "Lighting use color information store with vertices "),
|
||||
VXMESH_LITMODE.VX_LITMESH.value: EnumAnnotation("Lit", "Lighting is done by renderer using normals and face material information. "),
|
||||
}
|
||||
}
|
||||
|
||||
class EnumPropHelper(UTIL_functions.EnumPropHelper):
|
||||
"""
|
||||
Virtools type specified Blender EnumProp helper.
|
||||
"""
|
||||
__mAnnotationDict: dict[int, EnumAnnotation]
|
||||
__mEnumTy: type[enum.Enum]
|
||||
|
||||
def __init__(self, ty: type[enum.Enum]):
|
||||
# set enum type and annotation ref first
|
||||
self.__mEnumTy = ty
|
||||
self.__mAnnotationDict = _g_Annotation[ty]
|
||||
# init parent data
|
||||
UTIL_functions.EnumPropHelper.__init__(
|
||||
self,
|
||||
self.__mEnumTy, # enum.Enum it self is iterable
|
||||
lambda x: str(x.value), # convert enum.Enum's value to string
|
||||
lambda x: self.__mEnumTy(int(x)), # use stored enum type and int() to get enum member
|
||||
lambda x: self.__mAnnotationDict[x.value].mDisplayName,
|
||||
lambda x: self.__mAnnotationDict[x.value].mDescription,
|
||||
lambda _: ''
|
||||
)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Virtools Blender Bridge Funcs & Vars
|
||||
|
||||
def virtools_name_regulator(name: str | None) -> str:
|
||||
if name: return name
|
||||
else: return 'annoymous'
|
||||
|
||||
## Default Encoding for PyBMap
|
||||
# Use semicolon split each encodings. Support Western European and Simplified Chinese in default.
|
||||
g_PyBMapDefaultEncoding: str
|
||||
if sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
||||
# See: https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
|
||||
g_PyBMapDefaultEncoding = "1252;936"
|
||||
else:
|
||||
# See: https://www.gnu.org/software/libiconv/
|
||||
g_PyBMapDefaultEncoding = "CP1252;CP936"
|
||||
|
||||
#endregion
|
281
bbp_ng/__init__.py
Normal file
281
bbp_ng/__init__.py
Normal file
@ -0,0 +1,281 @@
|
||||
#region Reload and Import
|
||||
|
||||
# import core lib
|
||||
import bpy
|
||||
import typing, collections
|
||||
|
||||
# reload if needed
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
|
||||
#endregion
|
||||
|
||||
# we must load icons manager first
|
||||
# and register it
|
||||
from . import UTIL_icons_manager
|
||||
UTIL_icons_manager.register()
|
||||
|
||||
# then load other modules
|
||||
from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_group
|
||||
from . import PROP_ballance_element, PROP_bme_material, PROP_ballance_map_info
|
||||
from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools
|
||||
from . import OP_UV_flatten_uv, OP_UV_rail_uv
|
||||
from . import OP_MTL_fix_material
|
||||
from . import OP_ADDS_component, OP_ADDS_bme, OP_ADDS_rail
|
||||
from . import OP_OBJECT_legacy_align, OP_OBJECT_virtools_group, OP_OBJECT_naming_convention
|
||||
|
||||
#region Menu
|
||||
|
||||
# ===== Menu Defines =====
|
||||
|
||||
class BBP_MT_View3DMenu(bpy.types.Menu):
|
||||
"""Ballance 3D Operators"""
|
||||
bl_idname = "BBP_MT_View3DMenu"
|
||||
bl_label = "Ballance"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'UV', icon = 'UV')
|
||||
layout.operator(OP_UV_flatten_uv.BBP_OT_flatten_uv.bl_idname)
|
||||
layout.operator(OP_UV_rail_uv.BBP_OT_rail_uv.bl_idname)
|
||||
layout.separator()
|
||||
layout.label(text = 'Align', icon = 'SNAP_ON')
|
||||
layout.operator(OP_OBJECT_legacy_align.BBP_OT_legacy_align.bl_idname)
|
||||
layout.separator()
|
||||
layout.label(text = 'Select', icon = 'SELECT_SET')
|
||||
layout.operator(OP_OBJECT_virtools_group.BBP_OT_select_object_by_virtools_group.bl_idname)
|
||||
layout.separator()
|
||||
layout.label(text = 'Material', icon = 'MATERIAL')
|
||||
layout.operator(OP_MTL_fix_material.BBP_OT_fix_all_material.bl_idname)
|
||||
|
||||
class BBP_MT_AddBmeMenu(bpy.types.Menu):
|
||||
"""Add Ballance Floor"""
|
||||
bl_idname = "BBP_MT_AddBmeMenu"
|
||||
bl_label = "Floors"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
OP_ADDS_bme.BBP_OT_add_bme_struct.draw_blc_menu(layout)
|
||||
|
||||
class BBP_MT_AddRailMenu(bpy.types.Menu):
|
||||
"""Add Ballance Rail"""
|
||||
bl_idname = "BBP_MT_AddRailMenu"
|
||||
bl_label = "Rails"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.label(text = "Sections", icon = 'MESH_CIRCLE')
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_rail_section.bl_idname)
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_transition_section.bl_idname)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Straight Rails", icon = 'IPO_CONSTANT')
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_straight_rail.bl_idname)
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_transition_rail.bl_idname)
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_side_rail.bl_idname)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Curve Rails", icon = 'MOD_SCREW')
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_arc_rail.bl_idname)
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_spiral_rail.bl_idname)
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_side_spiral_rail.bl_idname)
|
||||
|
||||
class BBP_MT_AddComponentsMenu(bpy.types.Menu):
|
||||
"""Add Ballance Components"""
|
||||
bl_idname = "BBP_MT_AddComponentsMenu"
|
||||
bl_label = "Components"
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.label(text = "Basic Components")
|
||||
OP_ADDS_component.BBP_OT_add_component.draw_blc_menu(layout)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Nong Components")
|
||||
OP_ADDS_component.BBP_OT_add_nong_extra_point.draw_blc_menu(layout)
|
||||
OP_ADDS_component.BBP_OT_add_nong_ventilator.draw_blc_menu(layout)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Series Components")
|
||||
OP_ADDS_component.BBP_OT_add_tilting_block_series.draw_blc_menu(layout)
|
||||
OP_ADDS_component.BBP_OT_add_swing_series.draw_blc_menu(layout)
|
||||
OP_ADDS_component.BBP_OT_add_ventilator_series.draw_blc_menu(layout)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Components Pair")
|
||||
OP_ADDS_component.BBP_OT_add_sector_component_pair.draw_blc_menu(layout)
|
||||
|
||||
# ===== Menu Drawer =====
|
||||
|
||||
MenuDrawer_t = typing.Callable[[typing.Any, typing.Any], None]
|
||||
|
||||
def menu_drawer_import(self, context) -> None:
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
#layout.operator(OP_IMPORT_bmfile.BBP_OT_import_bmfile.bl_idname, text = "Ballance Map (.bmx)")
|
||||
layout.operator(OP_IMPORT_virtools.BBP_OT_import_virtools.bl_idname, text = "Virtools File (.nmo/.cmo/.vmo) (experimental)")
|
||||
|
||||
def menu_drawer_export(self, context) -> None:
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
#layout.operator(OP_EXPORT_bmfile.BBP_OT_export_bmfile.bl_idname, text = "Ballance Map (.bmx)")
|
||||
layout.operator(OP_EXPORT_virtools.BBP_OT_export_virtools.bl_idname, text = "Virtools File (.nmo/.cmo/.vmo) (experimental)")
|
||||
|
||||
def menu_drawer_view3d(self, context) -> None:
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
layout.menu(BBP_MT_View3DMenu.bl_idname)
|
||||
|
||||
def menu_drawer_add(self, context) -> None:
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
layout.separator()
|
||||
layout.label(text = "Ballance")
|
||||
layout.menu(BBP_MT_AddBmeMenu.bl_idname, icon='MESH_CUBE')
|
||||
layout.menu(BBP_MT_AddRailMenu.bl_idname, icon='MESH_CIRCLE')
|
||||
layout.menu(BBP_MT_AddComponentsMenu.bl_idname, icon='MESH_ICOSPHERE')
|
||||
|
||||
def menu_drawer_grouping(self, context) -> None:
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
layout.separator()
|
||||
|
||||
# NOTE: because outline context may change operator context
|
||||
# so it will cause no popup window when click operator in outline.
|
||||
# thus we create a sub layout and set its operator context as 'INVOKE_DEFAULT'
|
||||
# thus, all operators can pop up normally.
|
||||
col = layout.column()
|
||||
col.operator_context = 'INVOKE_DEFAULT'
|
||||
|
||||
col.label(text = "Virtools Group")
|
||||
col.operator(OP_OBJECT_virtools_group.BBP_OT_add_objects_virtools_group.bl_idname, icon = 'ADD', text = "Group into...")
|
||||
col.operator(OP_OBJECT_virtools_group.BBP_OT_rm_objects_virtools_group.bl_idname, icon = 'REMOVE', text = "Ungroup from...")
|
||||
col.operator(OP_OBJECT_virtools_group.BBP_OT_clear_objects_virtools_group.bl_idname, icon = 'TRASH', text = "Clear All Groups")
|
||||
|
||||
def menu_drawer_naming_convention(self, context) -> None:
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
layout.separator()
|
||||
|
||||
# same reason in `menu_drawer_grouping()``
|
||||
col = layout.column()
|
||||
col.operator_context = 'INVOKE_DEFAULT'
|
||||
|
||||
col.label(text = "Ballance")
|
||||
col.operator(OP_OBJECT_naming_convention.BBP_OT_regulate_objects_name.bl_idname, icon = 'GREASEPENCIL')
|
||||
col.operator(OP_OBJECT_naming_convention.BBP_OT_auto_grouping.bl_idname, icon = 'GROUP')
|
||||
col.operator(OP_OBJECT_naming_convention.BBP_OT_convert_to_imengyu.bl_idname, icon = 'ARROW_LEFTRIGHT')
|
||||
|
||||
#endregion
|
||||
|
||||
#region Register and Unregister.
|
||||
|
||||
g_BldClasses: tuple[typing.Any, ...] = (
|
||||
BBP_MT_View3DMenu,
|
||||
BBP_MT_AddBmeMenu,
|
||||
BBP_MT_AddRailMenu,
|
||||
BBP_MT_AddComponentsMenu
|
||||
)
|
||||
|
||||
class MenuEntry():
|
||||
mContainerMenu: bpy.types.Menu
|
||||
mIsAppend: bool
|
||||
mMenuDrawer: MenuDrawer_t
|
||||
def __init__(self, cont: bpy.types.Menu, is_append: bool, menu_func: MenuDrawer_t):
|
||||
self.mContainerMenu = cont
|
||||
self.mIsAppend = is_append
|
||||
self.mMenuDrawer = menu_func
|
||||
|
||||
g_BldMenus: tuple[MenuEntry, ...] = (
|
||||
MenuEntry(bpy.types.VIEW3D_MT_editor_menus, False, menu_drawer_view3d),
|
||||
MenuEntry(bpy.types.TOPBAR_MT_file_import, True, menu_drawer_import),
|
||||
MenuEntry(bpy.types.TOPBAR_MT_file_export, True, menu_drawer_export),
|
||||
MenuEntry(bpy.types.VIEW3D_MT_add, True, menu_drawer_add),
|
||||
|
||||
# register double (for 2 menus)
|
||||
MenuEntry(bpy.types.VIEW3D_MT_object_context_menu, True, menu_drawer_grouping),
|
||||
MenuEntry(bpy.types.OUTLINER_MT_object, True, menu_drawer_grouping),
|
||||
|
||||
MenuEntry(bpy.types.OUTLINER_MT_collection, True, menu_drawer_naming_convention),
|
||||
)
|
||||
|
||||
def register() -> None:
|
||||
# register module
|
||||
PROP_preferences.register()
|
||||
PROP_ptrprop_resolver.register()
|
||||
|
||||
PROP_virtools_material.register()
|
||||
PROP_virtools_texture.register()
|
||||
PROP_virtools_mesh.register()
|
||||
PROP_virtools_group.register()
|
||||
PROP_ballance_element.register()
|
||||
PROP_bme_material.register()
|
||||
PROP_ballance_map_info.register()
|
||||
|
||||
OP_IMPORT_bmfile.register()
|
||||
OP_EXPORT_bmfile.register()
|
||||
OP_IMPORT_virtools.register()
|
||||
OP_EXPORT_virtools.register()
|
||||
|
||||
OP_UV_rail_uv.register()
|
||||
OP_UV_flatten_uv.register()
|
||||
|
||||
OP_MTL_fix_material.register()
|
||||
|
||||
OP_ADDS_component.register()
|
||||
OP_ADDS_bme.register()
|
||||
OP_ADDS_rail.register()
|
||||
|
||||
OP_OBJECT_legacy_align.register()
|
||||
OP_OBJECT_virtools_group.register()
|
||||
OP_OBJECT_naming_convention.register()
|
||||
|
||||
# register other classes
|
||||
for cls in g_BldClasses:
|
||||
bpy.utils.register_class(cls)
|
||||
|
||||
# add menu drawer
|
||||
for entry in g_BldMenus:
|
||||
if entry.mIsAppend:
|
||||
entry.mContainerMenu.append(entry.mMenuDrawer)
|
||||
else:
|
||||
entry.mContainerMenu.prepend(entry.mMenuDrawer)
|
||||
|
||||
def unregister() -> None:
|
||||
# remove menu drawer
|
||||
for entry in g_BldMenus:
|
||||
entry.mContainerMenu.remove(entry.mMenuDrawer)
|
||||
|
||||
# unregister other classes
|
||||
for cls in g_BldClasses:
|
||||
bpy.utils.unregister_class(cls)
|
||||
|
||||
# unregister modules
|
||||
OP_OBJECT_naming_convention.unregister()
|
||||
OP_OBJECT_virtools_group.unregister()
|
||||
OP_OBJECT_legacy_align.unregister()
|
||||
|
||||
OP_ADDS_rail.unregister()
|
||||
OP_ADDS_bme.unregister()
|
||||
OP_ADDS_component.unregister()
|
||||
|
||||
OP_MTL_fix_material.unregister()
|
||||
|
||||
OP_UV_flatten_uv.unregister()
|
||||
OP_UV_rail_uv.unregister()
|
||||
|
||||
OP_EXPORT_virtools.unregister()
|
||||
OP_IMPORT_virtools.unregister()
|
||||
OP_EXPORT_bmfile.unregister()
|
||||
OP_IMPORT_bmfile.unregister()
|
||||
|
||||
PROP_ballance_map_info.unregister()
|
||||
PROP_bme_material.unregister()
|
||||
PROP_ballance_element.unregister()
|
||||
PROP_virtools_group.unregister()
|
||||
PROP_virtools_mesh.unregister()
|
||||
PROP_virtools_texture.unregister()
|
||||
PROP_virtools_material.unregister()
|
||||
|
||||
PROP_ptrprop_resolver.unregister()
|
||||
PROP_preferences.unregister()
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
#endregion
|
81
bbp_ng/blender_manifest.toml
Normal file
81
bbp_ng/blender_manifest.toml
Normal file
@ -0,0 +1,81 @@
|
||||
# Full context are copied from https://docs.blender.org/manual/en/dev/extensions/getting_started.html
|
||||
# Please note any update of this manifest
|
||||
|
||||
schema_version = "1.0.0"
|
||||
|
||||
# Example of manifest file for a Blender extension
|
||||
# Change the values according to your extension
|
||||
id = "bbp_ng"
|
||||
version = "4.0.0"
|
||||
name = "Ballance Blender Plugin"
|
||||
tagline = "The specialized add-on served for creating game map of Ballance"
|
||||
maintainer = "yyc12345 <yyc12321@outlook.com>"
|
||||
# Supported types: "add-on", "theme"
|
||||
type = "add-on"
|
||||
|
||||
# Optional link to documentation, support, source files, etc
|
||||
website = "https://github.com/yyc12345/BallanceBlenderHelper"
|
||||
|
||||
# Optional list defined by Blender and server, see:
|
||||
# https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html
|
||||
tags = ["Object", "Mesh", "UV", "Import-Export"]
|
||||
|
||||
blender_version_min = "4.2.0"
|
||||
# # Optional: Blender version that the extension does not support, earlier versions are supported.
|
||||
# # This can be omitted and defined later on the extensions platform if an issue is found.
|
||||
# blender_version_max = "5.1.0"
|
||||
|
||||
# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix)
|
||||
# https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html
|
||||
license = [
|
||||
"SPDX:GPL-3.0-or-later",
|
||||
]
|
||||
# Optional: required by some licenses.
|
||||
# copyright = [
|
||||
# "2002-2024 Developer Name",
|
||||
# "1998 Company Name",
|
||||
# ]
|
||||
|
||||
# Optional list of supported platforms. If omitted, the extension will be available in all operating systems.
|
||||
platforms = ["windows-x64", "linux-x64"]
|
||||
# Supported platforms: "windows-x64", "macos-arm64", "linux-x64", "windows-arm64", "macos-x64"
|
||||
|
||||
# Optional: bundle 3rd party Python modules.
|
||||
# https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html
|
||||
# wheels = [
|
||||
# "./wheels/hexdump-3.3-py3-none-any.whl",
|
||||
# "./wheels/jsmin-3.0.1-py3-none-any.whl",
|
||||
# ]
|
||||
|
||||
# Optional: add-ons can list which resources they will require:
|
||||
# * files (for access of any filesystem operations)
|
||||
# * network (for internet access)
|
||||
# * clipboard (to read and/or write the system clipboard)
|
||||
# * camera (to capture photos and videos)
|
||||
# * microphone (to capture audio)
|
||||
#
|
||||
# If using network, remember to also check `bpy.app.online_access`
|
||||
# https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access
|
||||
#
|
||||
# For each permission it is important to also specify the reason why it is required.
|
||||
# Keep this a single short sentence without a period (.) at the end.
|
||||
# For longer explanations use the documentation or detail page.
|
||||
|
||||
[permissions]
|
||||
# network = "Need to sync motion-capture data to server"
|
||||
files = "Import/export Virtools file from/to disk"
|
||||
# clipboard = "Copy and paste bone transforms"
|
||||
|
||||
# Optional: build settings.
|
||||
# https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build
|
||||
[build]
|
||||
paths_exclude_pattern = [
|
||||
"__pycache__/", # Python runtime cache
|
||||
".style.yapf", # Python code style
|
||||
"*.gitkeep", # Git directory keeper
|
||||
".gitignore", # Git Ignore File
|
||||
"*.md", # Useless document.
|
||||
"/raw_jsons", # Raw JSONs.
|
||||
"/raw_icons", # Raw Icons.
|
||||
"/tools", # Assistant tools.
|
||||
]
|
0
bbp_ng/icons/.gitkeep
Normal file
0
bbp_ng/icons/.gitkeep
Normal file
0
bbp_ng/jsons/.gitkeep
Normal file
0
bbp_ng/jsons/.gitkeep
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user