176 Commits

Author SHA1 Message Date
25091a48a7 finish specific flatten uv 2024-01-14 12:07:47 +08:00
259f99ddf8 refactor flatten for future dev 2024-01-12 23:55:28 +08:00
f123bdacc0 finish bme struct 2024-01-12 17:18:41 +08:00
8f27fec3c8 update bme struct 2024-01-10 22:37:41 +08:00
0d0d4db2db try nomaterial 2024-01-09 21:02:56 +08:00
bfbdf5c99b fix rail cret issues
- add transition rail oper
- set sharp edge only when cap is available.
- fix normal flip issue in straight rail.
2024-01-09 17:52:30 +08:00
50b2cf2cf1 add docs. add mkdocs deploy 2024-01-08 23:36:59 +08:00
013096459a finish rail adder 2024-01-08 15:08:20 +08:00
31aa5c3127 add null normal support in bme. fix rail cret draw() 2024-01-08 10:28:56 +08:00
f9502fe2d4 basically finish rail adder 2024-01-07 17:33:21 +08:00
b5565d796a add a part of rail adder 2024-01-06 23:51:11 +08:00
6b31401240 add bme struct. fix bmap type hint 2024-01-04 17:29:01 +08:00
2c006b4528 add conflict resolver 2024-01-04 10:35:52 +08:00
680f367a42 fix issues
- disable bmx import/export temporaryly
- optimize the ui display of importing/exporting. (use box to organize props)
- place button horizontally, not vertically in virtools material.
- disallow apply in legacy align if no axis selected.
- add applied step counter in legacy alignment.
- add feedback for reseting bme material/component. (add a message box to show success)
2024-01-01 13:07:10 +08:00
318d661ac1 separate gitignore and add document 2023-12-31 19:31:41 +08:00
321e1b6fe9 finish rename opers 2023-12-31 17:35:15 +08:00
20aba6c273 fix issue. add feat.
- add ignored bmesh update function in flatten uv
- finish select by group, add, rm, clear group functions.
2023-12-30 22:17:42 +08:00
c5ce6e8a2c add border in bme 2023-12-27 16:05:35 +08:00
9315ff723d fix issues
- apply transform when creating BME struct's vertices and faces.
- add 3ds max align operator. support Apply button in a nasty way.
2023-12-26 22:23:56 +08:00
50b7eb0bce fix issues
- fix Sector group no-successive issue when saving as nmo
- add nong ventilator adder.
- use copy.copy to write some code to reduce the calling of get/set_raw_vt_mtl()
- keep texture when apply virtools mtl preset.
2023-12-25 15:04:22 +08:00
03e8feac67 add support to compress bme json 2023-12-25 10:49:25 +08:00
35f2c4a389 finish bme 2023-12-18 10:30:32 +08:00
6bc3933291 update bme 2023-12-17 23:01:45 +08:00
03441c642b update bme prop display 2023-12-17 12:01:57 +08:00
77b15a8797 refactor EnumPropHelper to improve BME again. 2023-12-16 22:27:31 +08:00
94872957fd init bme creator work 2023-12-16 18:12:00 +08:00
a6de69c882 refactor BlenderEnumPropHelper 2023-12-15 21:57:50 +08:00
52f3936e42 try bme adder 2023-12-14 21:33:22 +08:00
2f123e6a3c add bme material preset 2023-12-14 18:03:03 +08:00
f6569313bf update component creation 2023-12-09 17:42:03 +08:00
ebb22c9ec1 refactor. and finish basic of adding component 2023-12-07 21:28:23 +08:00
ae9a848864 write shit 2023-12-06 17:16:31 +08:00
ca7e047c09 add something
- move all annotation to vt types
- add 2 options when saving virtools file accoridng to the change of BMap interface changes.
2023-12-03 22:49:29 +08:00
5c34bbad38 fix virtools export fatal errors 2023-12-03 18:14:20 +08:00
70dd525315 almost finish virtools exporing 2023-12-03 17:07:52 +08:00
3025fcf305 update vt file exporting 2023-12-02 20:25:36 +08:00
52452a96d5 update virtools export 2023-12-01 23:27:53 +08:00
84b3ace13d start exporing virtools file 2023-11-30 22:38:53 +08:00
7116d7198a update naming 2023-11-29 22:12:04 +08:00
6944415912 finish naming convention converter 2023-11-29 21:35:15 +08:00
88fe1519e3 add name convention 2023-11-26 20:37:19 +08:00
5bb3abed20 shhit 2023-11-26 12:57:49 +08:00
e79982c43f init naming convention plugin system 2023-11-24 17:49:18 +08:00
8e2d7a4133 update plugin
- revert merge of ballance textures
- prepare for naming convension dev
- show icons for virtools group list
2023-11-23 22:34:06 +08:00
cb80fa8b03 add icons manager. show icons in group 2023-11-22 19:57:29 +08:00
e84c1148f3 add thumbnail for blender icons 2023-11-22 17:41:43 +08:00
1432c2990a fix issues.
- fix issue according to python linter
- do not write vt group if no changes. reduce the cost of virtools group class.
2023-11-17 22:00:22 +08:00
1a2dd08092 update plugin.
- sync PyBMap work. use different library name in different OS.
- add BMap encoding default value according to different OS (Windows and non-Windows) because non-Windows OS, we use libiconv as encoding converter.
- move all pointer properties to a single module and give corresponding visitor.
- add shared importer exporter parameters module thus bmfile import/export also can ref it.
2023-11-16 22:41:03 +08:00
59a1275f68 add support of virtools mesh lit mode 2023-11-15 23:05:00 +08:00
d128ffcde5 update custom normals importing 2023-11-14 22:16:12 +08:00
fd17d3b5c9 revert some changes about prev commit 2023-11-14 21:37:29 +08:00
1f0444009f fix 2 issues
- update apply_virtools_material. use keywords, not index to visit input and output sockets.
- update blender mesh operator. send a message when mesh::validate() proceed mesh and stop custom normals assignment.
2023-11-14 21:24:09 +08:00
bd40e0fdf2 update video format display name 2023-11-12 20:55:21 +08:00
6991307b8b merge util_blc_tex to prop_vt_tex. support virtools texture props when importing virtools file. 2023-11-12 20:35:54 +08:00
b7ad5c67d4 add virtools texture support 2023-11-11 13:32:58 +08:00
5bbaa895eb fix raw data texture reading error 2023-11-10 14:59:06 +08:00
1299a14f3b finish virtools file importer 2023-11-10 12:26:04 +08:00
8fad643dd9 finish half of virtools importer 2023-11-09 21:41:26 +08:00
ec0749f3e5 adapt to PyBMap binding 2023-11-09 17:20:57 +08:00
6e4b1b37da finish virtools groups 2023-10-27 11:51:12 +08:00
1eaaedfcbd finish ballance element 2023-10-26 12:32:08 +08:00
c1f92c7244 basically finish framework of ballance_element 2023-10-25 21:35:26 +08:00
2896602e6a add basic support of Ballance elements 2023-10-25 12:07:14 +08:00
9079bf1bb3 remove uv modifier from bld_mesh.py and finish flatten uv and rail uv 2023-10-23 10:57:29 +08:00
f7dd5f32ba update MeshUVModifier 2023-10-21 10:48:46 +08:00
b4bb85cc71 update MeshUVModifier 2023-10-20 20:50:30 +08:00
bcbe9c987e add MeshUVModifier 2023-10-20 10:40:20 +08:00
16d4fc2bca finish mesh reader 2023-10-19 10:56:33 +08:00
4f10b1a9e9 add placeholder for importer and exporter 2023-10-18 21:23:04 +08:00
484a4101ad finish blender mesh writer 2023-10-18 12:09:40 +08:00
ea6cb430fe add blender mesh helper 2023-10-17 11:50:31 +08:00
354b3cd180 fix and add some functions 2023-10-16 10:12:05 +08:00
1d1de08bd7 update texture manager 2023-10-15 21:13:56 +08:00
21345b8251 add some functions 2023-10-15 13:48:50 +08:00
b94e7e2b22 add addon pref and file browser wrapper 2023-10-14 11:48:41 +08:00
5584bc66a0 basically finish virtools material 2023-10-12 22:15:21 +08:00
4f693b555d add virtools material basically 2023-10-11 22:24:22 +08:00
4733295a39 add some basic of virtools material 2023-10-11 10:55:45 +08:00
d9b9531828 try migrate first operator 2023-10-10 16:01:52 +08:00
1db6cab8ca add some code 2023-10-09 22:20:21 +08:00
8de1adda04 add some development help 2023-10-09 14:10:25 +08:00
f07cfc91d5 [doc] prepare next generation development 2023-10-09 10:39:35 +08:00
0a3b3c5862 [feat] allow nested collection when exporting bm file.
provided by propylamine
2023-09-05 14:40:10 +08:00
0d1e382b37 [fix] fix typo
- unknow -> unknown
2023-08-17 10:21:42 +08:00
12d5f8c236 [doc] bump up version. fix document.
- bump up version to 3.3
- fix documents
  * rewrite the supported version.
  * update suggested plugin link.
  * notify user some installation changes. order user change installation folder.
2023-07-21 10:02:36 +08:00
6fe856fa8e [fix] migrate to blender 3.6 LTS
- change bl_info["support"] to COMMUNITY. because Blender do not support TESTINg anymore.
- now plugin should be installed in addons folder, not addons_contrib, due to blender changes.
- remove the reference about mesh.polypons.loop_total. because it is readonly now. (blender 3.6 changed)
- change uv assign method. use new properties instead. (MeshUVLoop is deprecated in blender 3.5 and removed in blender 4.0)
2023-07-01 12:56:07 +08:00
c2a85a2d86 [fix] fix empty material slot export error.
- fix the issue that the object with empty material slot will cause a None crash when exporting BM file.
2023-05-25 20:54:50 +08:00
dafb679780 [doc] add document for flatten uv and bump up version.
- add document for flatten uv
- bump up version to 3.2
2023-03-11 16:57:17 +08:00
f3663a4280 [fix] fix fatal div zero issue
- fix div error exception when flatten uv - scale size == 0
2023-03-09 21:24:05 +08:00
9c8d365ab6 [feat] add ref. point feature for flatten uv
- add reference point & uv feature for flatten.
- due to first feature, flatten uv now can process more structure of ballance floor, such as looping floor with low edge count.
- give flatten uv 2 different modes.
- original flatten uv method still existed as a scale size mode.
2023-03-09 21:18:13 +08:00
0be036fcea [fix] fix various bugs
- update README to keep update with plugin design
    * refactor Select by Virtools Group section
    * add material preset function description
- fix issue about flatten uv may get zero base vector.
- update elements adder menu.
- bump up version to 3.1
2023-01-31 10:50:07 +08:00
e153e51abd [feat] improve element adder
- fix ambiguous default side setter for floor adder. use "update" field of EnumProperty instead. also applied for dup element with span adder.
- divide component adder into 3 different adder, to process different add strategies.
- update elements placeholder adder. this change will force all element placeholder share a single mesh in the whole map.
- add series element adder.
2023-01-30 22:49:49 +08:00
d292ce389a [fix] add icons for sound hit/roll id group
- add 3 extra icons for groups Sound_(Roll/Hit)ID_(01|02|03).
- add a empty placeholder icon to make some UI more clear
- finish add group icons.
2023-01-30 15:00:29 +08:00
807e006245 [feat] add full element icons
- add more element icons. now element icons is not problem.
- change icon load strategy. now icon is loaded outside plugin. this operations might slow down blender but now I can apply my custom map to some operators to get better using experience.
- use new element icons to decorate some group name to let user know what this group stands for.
2023-01-30 11:12:15 +08:00
8d7a982e50 [fix] fix add floor alignment and add some new icons.
- try setting center alignment for add floor popup operator. it is not center alignment now because blender's impl but it is better than previous layout.
- add some components icon for more directly visual.
2023-01-29 21:39:24 +08:00
ddf6b7befe [fix] fix misc not important issues
- correct all icon usage. use 'CANCEL' instead of 'ERROR' to indicate error correctly. and move these icon const into UTILS_icons_manager.py
- auto rename generated rail section to match the default name standard.
- give more clear signal about import and export bm file by using Operator.report() function.
2023-01-29 10:32:43 +08:00
a300ddbb49 [fix] fix fatal error when use flatten uv for sink floor top.
- default behavior of flatten uv only suit for normal floor
- add extra property called scale correction to allow use specific custom uv scale.
2023-01-28 20:44:39 +08:00
c9e51c9b6a [feat] change function "selected by Virtools groups"
- remove ignore hidden property. because it is rarely used.
- support more select mode. like blender selection.
- merge function filter by group and select by group as a united function.
2023-01-28 20:05:18 +08:00
b58f837a94 [feat] add confirm window for some dangerous opers
- add clear oper in Virtools group panel.
- add confirm window for clear Virtools groups opers. both panel and context menu.
2023-01-28 11:15:41 +08:00
5fe865c621 [feat] improve rename system
- rename system now support display renamed name and old name at the same time to solve the problem that user can not find renamed objects.
- correct some typo of rename system report.
2023-01-28 10:39:31 +08:00
9b9fc9cde8 [feat] promote experience about floor creation.
- change bl_options and rewrite invoke and draw functions to let floor creating window become more visual. credit: BLumia.
- also change 3ds max align and flatten uv presentation after changing creating floor window.
- seperate icon loader/unload module.
2023-01-27 16:28:32 +08:00
ef459a210d [feat] promote virtools material
- set the default value of virtools material like virtools creation.
- add preset in virtools panel for convenient using.
2023-01-25 14:57:30 +08:00
7680d11c0e [fix] fix various issues
- add edit mode switch before bm export to prevent potential error
- let the default value of ZBuffer in Virtools Material become True
- fix the issue that duplicated elements adder do not understand enable option.
2023-01-18 00:03:11 +08:00
e7376a3e9c [feat] add 2 new features
- add tunnel section creation and re-organise rail creation menu.
- allow duplicated elements (Nong xxx) creation for some special, such as Fan and Extra Point.
2023-01-09 11:10:13 +08:00
2a87e98904 add lost group Shadow. fix README typo 2023-01-05 10:32:46 +08:00
9211b0bcca translate engrish document and promote zh document 2023-01-03 14:43:12 +08:00
43a93d7c19 update ZH document 2023-01-02 17:20:36 +08:00
803bcaad05 [feat] promote visual representation
- update enum prop into radio button display mode in bmx export and group name selections
- move rename system into outline window right click menu.
- add grouping operator to object menu in outline window.
2022-12-30 10:06:02 +08:00
314284ed94 [feat] finish wide floor and sink platform auto gen.
- add height re-calc in derived block generation for fixing height issue when re-use Flat in sink platform.
- finish all wide floor prototype.
- finish all platform prototype.
2022-12-29 11:28:50 +08:00
0a815f04d6 [feat] change BMERevenge model read method
- divided json into small pieces for easy management.
- change json reading method for change (1)
2022-12-28 21:40:51 +08:00
2bd031784a [feat] update BMERevenge generation method
- use some laggy AST code to update BMERevenge generation method.
- after this change, BMERevenge become more programable and easy to create more complex models
- ready for wide floor generation development
2022-12-27 22:08:35 +08:00
d5cb8eb1ec make Virtools material can be optional 2022-11-20 12:02:15 +08:00
02c11ffe5a [feat] add more virtools group related operators
- support select/filter by virtools group.
- support group/ungroup/clear in object context menu.
- improve virtools group visit functions
2022-11-19 14:30:31 +08:00
240d5612df add new type rail uv: TT_ReflectionMapping 2022-08-29 10:52:34 +08:00
84dd5b76f1 add feature, optimize function argv passing.
- add support of 4 alpha fields intorduced in BM spec recently.
- optimize material creation functions argv passing strategy.
- change related func calls of (2).
- optimize material parameter pick code to reduce useless check.
2022-07-23 17:22:44 +08:00
2950857e3d do some improvement work
- split blender material creation function.
- allow user to apply virtools material to blender nodes in operator.
- add quick group picker in add virtools group operator.
2022-04-12 15:40:06 +08:00
dde95c3e4f finish vt_mtl and vt_group Panel system 2022-04-09 14:41:32 +08:00
4701164a6c add panel code basically. preparing for future dev 2022-04-08 21:50:31 +08:00
6c875d23ae add console output for rename system 2022-04-06 14:18:45 +08:00
3c36b8b9db finish rename system debug 2022-04-05 21:16:53 +08:00
c943264d05 finish rename system, without debug. 2022-04-05 20:45:00 +08:00
ca459c6185 add half code of rename system 2022-04-05 15:55:42 +08:00
1bfae63fe3 preparing rename system development 2022-04-04 22:04:11 +08:00
cb9609ac2c fix various bugs after refactor 2022-04-04 12:45:41 +08:00
c40f956771 fix loading and bm import/export error 2022-04-04 11:30:04 +08:00
e264c85a04 refactor project. preparing v3.0 development. no debug current 2022-04-03 22:48:12 +08:00
9c24569a06 fix add_floor texture problem again 2020-11-08 10:31:29 +08:00
031458bfc8 fix add_floor material error 2020-11-07 19:32:07 +08:00
3d1cf14334 update to bm spec 1.3 2020-11-06 21:34:47 +08:00
df44426195 fix suffix error when using add_element 2020-11-06 16:23:36 +08:00
670cc830fd finish version 2.0 2020-10-21 13:48:36 +08:00
582e96615a add rail_uv projection param. Update export bm UI (use PointerProperty) 2020-10-19 22:12:27 +08:00
ac9d196e3e update zh-cn document 2020-10-10 20:17:37 +08:00
f332e15791 finish flatten uv 2020-10-10 19:56:15 +08:00
0fb53f6827 add flatten uv. still have bug 2020-10-10 17:00:31 +08:00
c74141a346 fix move 2 cursor error 2020-10-10 11:11:08 +08:00
b3ee636848 finish all floor prototype 2020-10-10 10:27:15 +08:00
83ba270184 finish all basic floor protorype 2020-10-10 09:53:07 +08:00
027e267cc7 WIP adding floor prototype (2/3) 2020-10-09 21:59:06 +08:00
f5275b2abd WIP adding block prototype 2020-10-09 21:26:17 +08:00
93d077f18d add prototype 2020-10-09 17:16:36 +08:00
078dc952e7 add triangle face support 2020-10-09 16:39:20 +08:00
112b63319f WIP floor prototype 2020-10-09 12:53:00 +08:00
e2949708e6 finish add_derived_floor and debug 2020-10-08 21:19:06 +08:00
512cbce7fe WIP. write derived block creator 2020-10-08 17:12:38 +08:00
f0e3c3a597 finish add_basic_floor debug 2020-10-08 13:07:43 +08:00
75bfcbea02 add add_floor core function. WIP. no debug 2020-10-07 22:25:02 +08:00
4259d057fd fix rail_uv bbox calc bug 2020-10-07 20:38:01 +08:00
6ae4cbeddc finish add_floor ui display 2020-10-07 15:49:12 +08:00
35655a671d update message box and rail_uv 2020-10-07 15:20:51 +08:00
a8203dcb9c try use pointer property for rail_uv 2020-10-06 23:57:21 +08:00
612c641391 WIP, update some code 2020-10-06 12:57:25 +08:00
da722fe4bb adding add_floor function WIP 2020-10-05 22:30:06 +08:00
c5fc214384 increase bm version 2020-09-06 10:27:49 +08:00
ff24707186 update to bm 1.2 2020-09-06 10:11:48 +08:00
1fe77d843e add import opt, for BMERevenge import 2020-09-04 17:01:01 +08:00
d236925612 delete debug print 2020-09-04 11:10:46 +08:00
0af7f204dc finish rail section operator 2020-09-03 23:44:47 +08:00
79a04f1496 finish add elements 2020-09-03 13:57:06 +08:00
08eb6dcff9 [add] add ui and fix naming problem
- renaming all classes
- add conflict option for importing bm. no real code
- add shift + a menu, still no real code
2020-09-02 23:04:21 +08:00
751ff15b95 fix some name error and add no uv checker 2020-08-31 21:56:08 +08:00
97659f74cc remove debug print 2020-08-30 21:28:22 +08:00
41d3c5f3b7 use with syntax to save file and fix no uv bug 2020-08-30 21:22:25 +08:00
40756b0178 create document and bump version 2020-08-08 11:35:32 +08:00
af05f2ae21 fully finish bm import/export function 2020-08-07 14:17:13 +08:00
034f1e55aa update export module into bm 1.1 2020-08-07 10:41:50 +08:00
da4f07f37a add smooth feature 2020-08-06 14:43:31 +08:00
d90d05170b last edit 2020-08-06 12:17:06 +08:00
f61c3e1cef fix edit mode switch crash 2020-08-06 11:41:52 +08:00
bc13386f55 update mesh algo. but it still have bugs 2020-08-06 11:17:29 +08:00
72fe37040c add some prefs and finish bm import (no debug) 2020-08-05 16:19:38 +08:00
dcfc397421 fix fatal face export error 2020-07-23 11:21:11 +08:00
5b2314dbf8 move a settings' location 2020-07-20 16:14:39 +08:00
0dc526535c move some codes and add poll function for rail uv 2020-07-20 10:13:18 +08:00
6c00b2cb7b fix typo 2020-07-19 15:13:55 +08:00
654ce39a02 add pref and align tools 2020-07-19 15:05:43 +08:00
168 changed files with 15558 additions and 594 deletions

7
.gitattributes vendored Normal file
View File

@ -0,0 +1,7 @@
# all png are binary
*.png binary
# our generated mesh should be save as binary
*.bin 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
View File

@ -0,0 +1,27 @@
name: Publish docs via GitHub Pages
on:
push:
branches:
- ng
jobs:
build:
name: Deploy docs
runs-on: ubuntu-latest
steps:
- name: Checkout ng
uses: actions/checkout@v4
with:
ref: ng
- 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

View File

@ -1,9 +1,14 @@
# Ballance Blender Helper
# BBP NG
This is a Blender plugin which is served for Ballance mapping.
[中文版本](README_ZH.md)
WIP. Only BM export function is finished. But it is enough. Following function will be added in the future and it just like the decoration of BM export. Because once we have BM export, every map can be converted from Blender to Virtools.
BBP NG, abbr **B**allance **B**lender **P**lugin **N**ext **G**eneration.
BM file spec can be seen in [there](https://github.com/yyc12345/gist/tree/master/BMFileSpec)
## Brief Introduction
Support criteria: Only support the lastest **LTS** version.
The next generation of original Ballance Blender Helper. This plugin is fully rewritten.
This plugin still work in progress. The development will be pushed into `ng` branch in main repository. For legacy plugin user, please visit `master` branch directly.
## Develop Help
Use `pip install fake-bpy-module-latest==20230627` to install the type hint library for Blender. Because fake-bpy-module do not release official 3.6 package, we need install it by choosing the most closest version of Blender 3.6 release. That what I found.

10
README_ZH.md Normal file
View File

@ -0,0 +1,10 @@
# BBP NG
[English version](README.md)
BBP NG又名**B**allance **B**lender **P**lugin **N**ext **G**eneration下一代Ballance Blender插件
## 简介
下一代的Ballance Blender插件。此插件完全重写了上一代插件。
此插件仍然在开发过程中。开发内容会被推送到主仓库的`ng`分支中。对于旧插件的用户,请直接访问`master`分支。

View File

@ -1,134 +0,0 @@
bl_info={
"name":"Ballance Blender Plugin",
"description":"Ballance mapping tools for Blender",
"author":"yyc12345",
"version":(0,1),
"blender":(2,83,0),
"category":"Object",
"support":"TESTING"
}
# ============================================= import system
import bpy,bpy_extras
# import my code (with reload)
if "bpy" in locals():
import importlib
if "bm_import_export" in locals():
importlib.reload(bm_import_export)
if "floor_rail_uv" in locals():
importlib.reload(floor_rail_uv)
if "utils" in locals():
importlib.reload(utils)
if "config" in locals():
importlib.reload(config)
from . import config, utils, bm_import_export, floor_rail_uv
# ============================================= func block
class ImportBM(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
"""Load a Ballance Map File"""
bl_idname = "import_scene.bm"
bl_label = "Import BM"
bl_options = {'PRESET', 'UNDO'}
filename_ext = ".bm"
def execute(self, context):
bm_import_export.import_bm(context, self.filepath)
return {'FINISHED'}
class ExportBM(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
"""Save a Ballance Map File"""
bl_idname = "export_scene.bm"
bl_label = 'Export BM'
bl_options = {'PRESET'}
filename_ext = ".bm"
export_mode: bpy.props.EnumProperty(
name="Export mode",
items=(('COLLECTION', "Selected collection", "Export the selected collection"),
('OBJECT', "Selected objects", "Export the selected objects"),
),
)
export_target: bpy.props.StringProperty(
name="Export target",
description="Which one will be exported",
)
no_component_suffix: bpy.props.StringProperty(
name="No component suffix",
description="The object which have this suffix will not be saved as component.",
)
def execute(self, context):
bm_import_export.export_bm(context, self.filepath, self.export_mode, self.export_target, self.no_component_suffix)
return {'FINISHED'}
# ============================================= menu system
class RailUVOperator(bpy.types.Operator):
"""Create a UV for rail"""
bl_idname = "ballance.rail_uv"
bl_label = "Create Rail UV"
bl_options = {'UNDO'}
def execute(self, context):
floor_rail_uv.create_rail_uv()
return {'FINISHED'}
class FloorUVOperator(bpy.types.Operator):
"""Virtoolize the UV of floor"""
bl_idname = "ballance.floor_uv"
bl_label = "Virtoolize floor UV"
bl_options = {'UNDO'}
def execute(self, context):
floor_rail_uv.virtoolize_floor_uv()
return {'FINISHED'}
class ThreeDViewerMenu(bpy.types.Menu):
bl_label = "Ballance 3D"
bl_idname = "OBJECT_MT_ballance3d_menu"
def draw(self, context):
layout = self.layout
layout.operator("ballance.rail_uv")
layout.operator("ballance.floor_uv")
# ============================================= blender call system
classes = (
ImportBM,
ExportBM,
RailUVOperator,
FloorUVOperator,
ThreeDViewerMenu
)
def menu_func_bm_import(self, context):
self.layout.operator(ImportBM.bl_idname, text="Ballance Map (.bm)")
def menu_func_bm_export(self, context):
self.layout.operator(ExportBM.bl_idname, text="Ballance Map (.bm)")
def menu_func_ballance_3d(self, context):
layout = self.layout
layout.menu(ThreeDViewerMenu.bl_idname)
def register():
for cls in classes:
bpy.utils.register_class(cls)
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_HT_header.append(menu_func_ballance_3d)
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_HT_header.remove(menu_func_ballance_3d)
for cls in classes:
bpy.utils.unregister_class(cls)
if __name__=="__main__":
register()

View File

@ -1,328 +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, config
bm_current_version = 10
def import_bm(context,filepath):
# todo: finish this
pass
def export_bm(context,filepath,export_mode, export_target, no_component_suffix):
# ============================================ alloc a temp folder
tempFolderObj = tempfile.TemporaryDirectory()
tempFolder = tempFolderObj.name
# debug
# tempFolder = "G:\\ziptest"
tempTextureFolder = os.path.join(tempFolder, "Texture")
os.makedirs(tempTextureFolder)
# ============================================ find export target
if export_mode== "COLLECTION":
objectList = bpy.data.collections[export_target].objects
else:
objectList = [bpy.data.objects[export_target]]
needSuffixChecker = no_component_suffix != ""
componentObj = set()
for obj in objectList:
if needSuffixChecker and obj.name.endwith(no_component_suffix):
pass # meshObjList.add(obj)
else:
if is_component(obj.name):
componentObj.add(obj)
else:
pass # meshObjList.add(obj)
# ============================================ export
finfo = open(os.path.join(tempFolder, "index.bm"), "wb")
finfo.write(struct.pack("I", bm_current_version))
# ====================== export object
fobject = open(os.path.join(tempFolder, "object.bm"), "wb")
meshSet = set()
meshList = []
meshCount = 0
for obj in objectList:
# only export mesh object
if obj.type != 'MESH':
continue
varis_component = obj in componentObj
# clean no mesh object
currentMesh = obj.data
if currentMesh == None:
continue
# triangle first and then group
if not varis_component:
if currentMesh not in meshSet:
mesh_triangulate(currentMesh)
meshSet.add(currentMesh)
meshList.append(currentMesh)
meshId = meshCount
meshCount += 1
else:
meshId = meshList.index(currentMesh)
# write finfo first
write_string(finfo, obj.name)
write_int(finfo, info_bm_type.OBJECT)
write_long(finfo, fobject.tell())
# write fobject
write_int(fobject, 1 if varis_component else 0)
write_worldMatrix(fobject, obj.matrix_world)
if varis_component:
write_int(fobject, get_component_id(obj.name))
else:
write_int(fobject, meshId)
fobject.close()
# ====================== export mesh
fmesh = open(os.path.join(tempFolder, "mesh.bm"), "wb")
materialSet = set()
materialList = []
for mesh in meshList:
mesh.calc_normals_split()
# write finfo first
write_string(finfo, mesh.name)
write_int(finfo, info_bm_type.MESH)
write_long(finfo, fmesh.tell())
# write fmesh
# vertices
vecList = mesh.vertices[:]
write_int(fmesh, len(vecList))
for vec in vecList:
#swap yz
write_3vector(fmesh,vec.co[0],vec.co[2],vec.co[1])
# uv
face_index_pairs = [(face, index) for index, face in enumerate(mesh.polygons)]
uv_layer = mesh.uv_layers.active.data[:]
write_int(fmesh, len(face_index_pairs) * 3)
for f, f_index in face_index_pairs:
# 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
write_2vector(fmesh, uv[0], -uv[1])
# normals
write_int(fmesh, len(face_index_pairs) * 3)
for f, f_index in face_index_pairs:
# 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
write_3vector(fmesh, nml[0], nml[2], nml[1])
# face
# get material first
currentMat = mesh.materials[:]
noMaterial = len(currentMat) == 0
for mat in currentMat:
if mat not in materialSet:
materialSet.add(mat)
materialList.append(mat)
write_int(fmesh, len(face_index_pairs))
vtIndex = []
vnIndex = []
vIndex = []
for f, f_index in face_index_pairs:
# confirm material use
if noMaterial:
usedMat = 0
else:
usedMat = materialList.index(currentMat[f.material_index])
# export face
vtIndex.clear()
vnIndex.clear()
vIndex.clear()
counter = 0
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
vIndex.append(mesh.loops[loop_index].vertex_index)
vnIndex.append(f_index * 3 + counter)
vtIndex.append(f_index * 3 + counter)
counter += 1
# reverse vertices sort
write_face(fmesh,
vIndex[2], vtIndex[2], vnIndex[2],
vIndex[1], vtIndex[1], vnIndex[1],
vIndex[0], vtIndex[0], vnIndex[0])
# set used material
write_int(fmesh, 0 if noMaterial else 1)
write_int(fmesh, usedMat)
mesh.free_normals_split()
fmesh.close()
# ====================== export material
fmaterial = open(os.path.join(tempFolder, "material.bm"), "wb")
textureSet = set()
textureList = []
textureCount = 0
for material in materialList:
# write finfo first
write_string(finfo, material.name)
write_int(finfo, info_bm_type.MATERIAL)
write_long(finfo, fmaterial.tell())
# write basic color
mat_wrap = node_shader_utils.PrincipledBSDFWrapper(material)
if mat_wrap:
use_mirror = mat_wrap.metallic != 0.0
if use_mirror:
write_3vector(fmaterial, mat_wrap.metallic, mat_wrap.metallic, mat_wrap.metallic)
else:
write_3vector(fmaterial, 1, 1, 1)
write_3vector(fmaterial, mat_wrap.base_color[0], mat_wrap.base_color[1], mat_wrap.base_color[2])
write_3vector(fmaterial, mat_wrap.specular, mat_wrap.specular, mat_wrap.specular)
# confirm texture
tex_wrap = getattr(mat_wrap, "base_color_texture", None)
if tex_wrap:
image = tex_wrap.image
if image:
# add into texture list
if image not in textureSet:
textureSet.add(image)
textureList.append(image)
currentTexture = textureCount
textureCount += 1
else:
currentTexture = textureList.index(image)
write_int(fmaterial, 1)
write_int(fmaterial, currentTexture)
else:
# no texture
write_int(fmaterial, 0)
write_int(fmaterial, 0)
else:
# no texture
write_int(fmaterial, 0)
write_int(fmaterial, 0)
else:
# no Principled BSDF. write garbage
write_3vector(fmaterial, 0.8, 0.8, 0.8)
write_3vector(fmaterial, 0.8, 0.8, 0.8)
write_3vector(fmaterial, 0.8, 0.8, 0.8)
write_int(fmaterial, 0)
write_int(fmaterial, 0)
fmaterial.close()
# ====================== export texture
ftexture = open(os.path.join(tempFolder, "texture.bm"), "wb")
source_dir = os.path.dirname(bpy.data.filepath)
existed_texture = set()
for texture in textureList:
# write finfo first
write_string(finfo, texture.name)
write_int(finfo, info_bm_type.TEXTURE)
write_long(finfo, ftexture.tell())
# confirm internal
texture_filepath = io_utils.path_reference(texture.filepath, source_dir, tempTextureFolder,
'ABSOLUTE', "", None, texture.library)
filename = os.path.basename(texture_filepath)
write_string(ftexture, filename)
if (is_external_texture(filename)):
write_int(ftexture, 1)
else:
# copy internal texture, if this file is copied, do not copy it again
write_int(ftexture, 0)
if filename not in existed_texture:
shutil.copy(texture_filepath, os.path.join(tempTextureFolder, filename))
existed_texture.add(filename)
ftexture.close()
# close info fs
finfo.close()
# ============================================ save zip and clean up folder
if os.path.isfile(filepath):
os.remove(filepath)
with zipfile.ZipFile(filepath, 'w', zipfile.ZIP_DEFLATED, 9) as zipObj:
for folderName, subfolders, filenames in os.walk(tempFolder):
for filename in filenames:
filePath = os.path.join(folderName, filename)
arcname=os.path.relpath(filePath, tempFolder)
zipObj.write(filePath, arcname)
tempFolderObj.cleanup()
# ======================================================================================= export / import assistant
class info_bm_type():
OBJECT = 0
MESH = 1
MATERIAL = 2
TEXTURE = 3
def is_component(name):
return get_component_id(name) != -1
def get_component_id(name):
return -1 # todo: finish this, -1 mean not a component
def is_external_texture(name):
if name in config.external_texture_list:
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()
# ======================================================================================= file io assistant
def write_string(fs,str):
count=len(str)
write_int(fs,count)
fs.write(str.encode("utf_32_le"))
def write_int(fs,num):
fs.write(struct.pack("I", num))
def write_long(fs,num):
fs.write(struct.pack("Q", num))
def write_worldMatrix(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_2vector(fs, u, v):
fs.write(struct.pack("ff", u, v))
def write_face(fs, v1, vn1, vt1, v2, vn2, vt2, v3, vn3, vt3):
fs.write(struct.pack("IIIIIIIII", v1, vn1, vt1, v2, vn2, vt2, v3, vn3, vt3))

View File

@ -1,85 +0,0 @@
external_texture_list = set([
'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'
])
component_list = [
]

View File

@ -1,34 +0,0 @@
import bpy,bmesh
from . import utils
def create_rail_uv():
meshList = []
for obj in bpy.context.selected_objects:
if obj.type != 'MESH':
continue
if obj.data.uv_layers.active.data == None:
utils.ShowMessageBox("You should create a UV layer for this object firstly. Then execute this operator.", "No UV layer", 'ERROR')
return
meshList.append(obj.data)
for mesh in meshList:
# vecList = mesh.vertices[:]
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):
# index = mesh.loops[loop_index].vertex_index
uv_layer[loop_index].uv[0] = 0 # vecList[index].co[0]
uv_layer[loop_index].uv[1] = 1 # vecList[index].co[1]
def virtoolize_floor_uv():
pass
def mesh_triangulate(me):
bm = bmesh.new()
bm.from_mesh(me)
bmesh.ops.triangulate(bm, faces=bm.faces)
bm.to_mesh(me)
bm.free()

View File

@ -1,8 +0,0 @@
import bpy
def ShowMessageBox(message = "", title = "Message Box", icon = 'INFO'):
def draw(self, context):
self.layout.label(text=message)
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)

View File

@ -1,3 +1,15 @@
# 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]

402
bbp_ng/.style.yapf Normal file
View 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

246
bbp_ng/OP_ADDS_bme.py Normal file
View File

@ -0,0 +1,246 @@
import bpy
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,
)
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,
)
prop_bool: bpy.props.BoolProperty(
name = 'Single Bool', description = 'Single Bool',
default = True
)
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` 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
)
## 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
)
bme_struct_cfgs : bpy.props.CollectionProperty(
name = "Cfgs",
description = "Cfg collection.",
type = BBP_PG_bme_adder_cfgs,
)
@classmethod
def poll(self, context):
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
def invoke(self, context, event):
# 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):
# 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
)
# move to cursor
UTIL_functions.add_into_scene_and_move_to_cursor(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
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()
@classmethod
def draw_blc_menu(self, layout: bpy.types.UILayout):
for ident in _g_EnumHelper_BmeStructType.get_bme_identifiers():
# draw operator
cop = layout.operator(
self.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():
bpy.utils.register_class(BBP_PG_bme_adder_cfgs)
bpy.utils.register_class(BBP_OT_add_bme_struct)
def unregister():
bpy.utils.unregister_class(BBP_OT_add_bme_struct)
bpy.utils.unregister_class(BBP_PG_bme_adder_cfgs)

494
bbp_ng/OP_ADDS_component.py Normal file
View File

@ -0,0 +1,494 @@
import bpy, mathutils
import math, typing
from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convension
from . import PROP_ballance_element, PROP_virtools_group
#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,
)
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,
)
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
def _general_create_component(
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.
"""
# 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)
#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(),
)
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
_general_create_component(
_g_EnumHelper_Component.get_selection(self.component_type),
self.general_get_component_sector(),
1, # only create one
lambda _: mathutils.Matrix.Identity(4)
)
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
_general_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')
)
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."),
),
)
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.'),
),
)
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
_general_create_component(
PROP_ballance_element.BallanceElementType.P_Modul_18,
self.general_get_component_sector(),
count,
lambda _: mathutils.Matrix.Identity(4)
)
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,
)
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
_general_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
)
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_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),
)
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
_general_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
)
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
_general_create_component(
checkp_ty,
checkp_sector,
1, # only create one
lambda _: mathutils.Matrix.Identity(4)
)
# create resetpoint
_general_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
)
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():
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_ventilator_series)
bpy.utils.register_class(BBP_OT_add_sector_component_pair)
def unregister():
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_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)

706
bbp_ng/OP_ADDS_rail.py Normal file
View File

@ -0,0 +1,706 @@
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 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 = 16,
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
)
)
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)
)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.label(text = 'No Options Available')
class BBP_OT_add_straight_rail(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()
)
)
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)
class BBP_OT_add_transition_rail(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()
)
)
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)
class BBP_OT_add_side_rail(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()
)
)
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)
class BBP_OT_add_arc_rail(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()
)
)
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)
class BBP_OT_add_spiral_rail(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()
)
)
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)
class BBP_OT_add_side_spiral_rail(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()
)
)
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)
#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]) -> 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.use_auto_smooth = True
mesh.auto_smooth_angle = math.radians(50)
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)
# 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():
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():
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)

View 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)

View File

@ -0,0 +1,545 @@
import bpy
from bpy_extras.wm_utils.progress_report import ProgressReport
import tempfile, os, typing, re
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
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture
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)
)
use_compress: bpy.props.BoolProperty(
name="Use Compress",
default = True,
)
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,
)
@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,
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')
_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,
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, 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,
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 group exporting helper
group_cret_guard: VirtoolsGroupCreationGuard = VirtoolsGroupCreationGuard(writer)
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:
# note: no need to set name, guard has set it.
vtgroup = group_cret_guard.create_group(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()
class VirtoolsGroupCreationGuard():
"""
This class 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.
This class hook the operation of Virtools group creation and check all Sector group creation.
Create essential group to make Sector_XX group successive.
Thus all group creation in this module should be passed by this class.
"""
cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
@staticmethod
def __get_group_index(group_name: str) -> int | None:
"""
Return the sector index of group name if it is. Otherwise None.
"""
regex_result = VirtoolsGroupCreationGuard.cRegexGroupSector.match(group_name)
if regex_result is not None:
return int(regex_result.group(1))
else:
return None
@staticmethod
def __get_group_name(group_index: int) -> str:
"""
Output Sector group name by given sector index
"""
if group_index == 9:
return 'Sector_9'
else:
return f'Sector_{group_index:0>2d}'
__mWriter: bmap.BMFileWriter
__mSectors: list[bmap.BMGroup]
def __init__(self, assoc_writer: bmap.BMFileWriter):
self.__mWriter = assoc_writer
self.__mSectors = []
def create_group(self, group_name: str) -> bmap.BMGroup:
"""
The hooked group creation function.
Please note the passed group name argument is just for name checking.
This function will set group name for you, not like BMFileWriter.create_group() operated.
"""
# check whether it is sector group
# note: the return sector index is 1 based, not 0
sector_idx: int | None = VirtoolsGroupCreationGuard.__get_group_index(group_name)
# if it is regular group, return normal creation
if sector_idx is None:
gp: bmap.BMGroup = self.__mWriter.create_group()
gp.set_name(group_name)
return gp
# get from sector cahce list
# enlarge sector cache list if it is not fulfilled given sector index
while sector_idx > len(self.__mSectors):
gp: bmap.BMGroup = self.__mWriter.create_group()
self.__mSectors.append(gp)
gp.set_name(self.__get_group_name(len(self.__mSectors)))
# return ordered sector from sector caches
return self.__mSectors[sector_idx - 1]
def register() -> None:
bpy.utils.register_class(BBP_OT_export_virtools)
def unregister() -> None:
bpy.utils.unregister_class(BBP_OT_export_virtools)

View 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)

View File

@ -0,0 +1,395 @@
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
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture
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]
) -> dict[bmap.BM3dObject, bpy.types.Object]:
# 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]] = {}
# 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
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()
# 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)

View File

@ -0,0 +1,273 @@
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,
)
align_y: bpy.props.BoolProperty(
name = "Y Position",
default = False,
)
align_z: bpy.props.BoolProperty(
name = "Z Position",
default = False,
)
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),
)
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),
)
#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,
)
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,
)
align_history : bpy.props.CollectionProperty(
name = "Historys",
description = "Align history.",
type = BBP_PG_legacy_align_history,
)
@classmethod
def poll(self, 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:
# 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():
bpy.utils.register_class(BBP_PG_legacy_align_history)
bpy.utils.register_class(BBP_OT_legacy_align)
def unregister():
bpy.utils.unregister_class(BBP_OT_legacy_align)
bpy.utils.unregister_class(BBP_PG_legacy_align_history)

View 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():
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():
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)

View File

@ -0,0 +1,199 @@
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)
)
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(self, 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)
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(self, 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)
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(self, 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()
return {'FINISHED'}
#endregion
def register():
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():
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)

494
bbp_ng/OP_UV_flatten_uv.py Normal file
View File

@ -0,0 +1,494 @@
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 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:
# a valid face must be
# selected, not processed, and should be rectangle
return f.select and (not f.tag) and (len(f.loops) == 4)
# 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]] = 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))))
except StopIteration:
break
# pick one face from stack and process it
(face, face_offset) = face_stack.pop()
_flatten_face_uv(face, uv_layer, flatten_param, face_offset)
print(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)
# insert horizontal neighbor if we are wood flatten uv
if flatten_param.mFlattenMethod == FlattenMethod.Wood:
# first, 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)
# then, try getting its right neighbor
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]))))
# insert vertical neighbor
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]))))
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 = new_y_axis.cross(vec1)
new_z_axis.normalize()
if not any(round(v, 7) for v in new_z_axis): # if z is a zero vector, use face normal instead
new_z_axis = typing.cast(mathutils.Vector, face.normal).normalized()
# 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
View 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 = "Create 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)

View 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():
# 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():
# 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)

306
bbp_ng/PROP_bme_material.py Normal file
View 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():
# 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():
# 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)

View 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)

View 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():
bpy.utils.register_class(BBP_PG_ptrprop_resolver)
bpy.types.Scene.bbp_ptrprop_resolver = bpy.props.PointerProperty(type = BBP_PG_ptrprop_resolver)
def unregister():
del bpy.types.Scene.bbp_ptrprop_resolver
bpy.utils.unregister_class(BBP_PG_ptrprop_resolver)

View 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 = ""
)
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():
# 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 scene 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():
# del from scene metadata
del bpy.types.Scene.active_virtools_groups
del bpy.types.Scene.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)

View File

@ -0,0 +1,736 @@
import bpy
import typing, enum, copy
from . import UTIL_virtools_types, UTIL_functions, UTIL_ballance_texture, UTIL_file_browser
from . import PROP_virtools_texture, PROP_preferences
class RawVirtoolsMaterial():
# Instance Member Declarations
mDiffuse: UTIL_virtools_types.VxColor
mAmbient: UTIL_virtools_types.VxColor
mSpecular: UTIL_virtools_types.VxColor
mEmissive: UTIL_virtools_types.VxColor
mSpecularPower: float
mTexture: bpy.types.Image | None
mTextureBorderColor: UTIL_virtools_types.VxColor
mTextureBlendMode: UTIL_virtools_types.VXTEXTURE_BLENDMODE
mTextureMinMode: UTIL_virtools_types.VXTEXTURE_FILTERMODE
mTextureMagMode: UTIL_virtools_types.VXTEXTURE_FILTERMODE
mTextureAddressMode: UTIL_virtools_types.VXTEXTURE_ADDRESSMODE
mSourceBlend: UTIL_virtools_types.VXBLEND_MODE
mDestBlend: UTIL_virtools_types.VXBLEND_MODE
mFillMode: UTIL_virtools_types.VXFILL_MODE
mShadeMode: UTIL_virtools_types.VXSHADE_MODE
mEnableAlphaTest: bool
mEnableAlphaBlend: bool
mEnablePerspectiveCorrection: bool
mEnableZWrite: bool
mEnableTwoSided: bool
mAlphaRef: int
mAlphaFunc: UTIL_virtools_types.VXCMPFUNC
mZFunc: UTIL_virtools_types.VXCMPFUNC
# Default Value Declarations
cDefaultDiffuse: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(0.7, 0.7, 0.7, 1.0)
cDefaultAmbient: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(0.3, 0.3, 0.3, 1.0)
cDefaultSpecular: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(0.5, 0.5, 0.5, 1.0)
cDefaultEmissive: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(0.0, 0.0, 0.0, 1.0)
cDefaultSpecularPower: typing.ClassVar[float] = 0.0
cDefaultTexture: typing.ClassVar[bpy.types.Image | None] = None
cDefaultTextureBorderColor: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(0.0, 0.0, 0.0, 0.0)
cDefaultTextureBlendMode: typing.ClassVar[UTIL_virtools_types.VXTEXTURE_BLENDMODE]= UTIL_virtools_types.VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MODULATEALPHA
cDefaultTextureMinMode: typing.ClassVar[UTIL_virtools_types.VXTEXTURE_FILTERMODE] = UTIL_virtools_types.VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEAR
cDefaultTextureMagMode: typing.ClassVar[UTIL_virtools_types.VXTEXTURE_FILTERMODE] = UTIL_virtools_types.VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEAR
cDefaultTextureAddressMode: typing.ClassVar[UTIL_virtools_types.VXTEXTURE_ADDRESSMODE] = UTIL_virtools_types.VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSWRAP
cDefaultSourceBlend: typing.ClassVar[UTIL_virtools_types.VXBLEND_MODE] = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_ONE
cDefaultDestBlend: typing.ClassVar[UTIL_virtools_types.VXBLEND_MODE] = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_ZERO
cDefaultFillMode: typing.ClassVar[UTIL_virtools_types.VXFILL_MODE] = UTIL_virtools_types.VXFILL_MODE.VXFILL_SOLID
cDefaultShadeMode: typing.ClassVar[UTIL_virtools_types.VXSHADE_MODE] = UTIL_virtools_types.VXSHADE_MODE.VXSHADE_GOURAUD
cDefaultEnableAlphaTest: typing.ClassVar[bool] = False
cDefaultEnableAlphaBlend: typing.ClassVar[bool] = False
cDefaultEnablePerspectiveCorrection: typing.ClassVar[bool] = True
cDefaultEnableZWrite: typing.ClassVar[bool] = True
cDefaultEnableTwoSided: typing.ClassVar[bool] = False
cDefaultAlphaRef: typing.ClassVar[int] = 0
cDefaultAlphaFunc: typing.ClassVar[UTIL_virtools_types.VXCMPFUNC] = UTIL_virtools_types.VXCMPFUNC.VXCMP_ALWAYS
cDefaultZFunc: typing.ClassVar[UTIL_virtools_types.VXCMPFUNC] = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
def __init__(self, **kwargs):
# assign default value for each component
self.mDiffuse = kwargs.get('mDiffuse', RawVirtoolsMaterial.cDefaultDiffuse).clone()
self.mAmbient = kwargs.get('mAmbient', RawVirtoolsMaterial.cDefaultAmbient).clone()
self.mSpecular = kwargs.get('mSpecular', RawVirtoolsMaterial.cDefaultSpecular).clone()
self.mSpecularPower = kwargs.get('mSpecularPower', RawVirtoolsMaterial.cDefaultSpecularPower)
self.mEmissive = kwargs.get('mEmissive', RawVirtoolsMaterial.cDefaultEmissive).clone()
self.mEnableTwoSided = kwargs.get('mEnableTwoSided', RawVirtoolsMaterial.cDefaultEnableTwoSided)
self.mTexture = kwargs.get('mTexture', RawVirtoolsMaterial.cDefaultTexture)
self.mTextureMinMode = kwargs.get('mTextureMinMode', RawVirtoolsMaterial.cDefaultTextureMinMode)
self.mTextureMagMode = kwargs.get('mTextureMagMode', RawVirtoolsMaterial.cDefaultTextureMagMode)
self.mSourceBlend = kwargs.get('mSourceBlend', RawVirtoolsMaterial.cDefaultSourceBlend)
self.mDestBlend = kwargs.get('mDestBlend', RawVirtoolsMaterial.cDefaultDestBlend)
self.mEnableAlphaBlend = kwargs.get('mEnableAlphaBlend', RawVirtoolsMaterial.cDefaultEnableAlphaBlend)
self.mShadeMode = kwargs.get('mShadeMode', RawVirtoolsMaterial.cDefaultShadeMode)
self.mFillMode = kwargs.get('mFillMode', RawVirtoolsMaterial.cDefaultFillMode)
self.mEnableAlphaTest = kwargs.get('mEnableAlphaTest', RawVirtoolsMaterial.cDefaultEnableAlphaTest)
self.mEnableZWrite = kwargs.get('mEnableZWrite', RawVirtoolsMaterial.cDefaultEnableZWrite)
self.mEnablePerspectiveCorrection = kwargs.get('mEnablePerspectiveCorrection', RawVirtoolsMaterial.cDefaultEnablePerspectiveCorrection)
self.mTextureBlendMode = kwargs.get('mTextureBlendMode', RawVirtoolsMaterial.cDefaultTextureBlendMode)
self.mTextureAddressMode = kwargs.get('mTextureAddressMode', RawVirtoolsMaterial.cDefaultTextureAddressMode)
self.mZFunc = kwargs.get('mZFunc', RawVirtoolsMaterial.cDefaultZFunc)
self.mAlphaFunc = kwargs.get('mAlphaFunc', RawVirtoolsMaterial.cDefaultAlphaFunc)
self.mTextureBorderColor = kwargs.get('mTextureBorderColor', RawVirtoolsMaterial.cDefaultTextureBorderColor).clone()
self.mAlphaRef = kwargs.get('mAlphaRef', RawVirtoolsMaterial.cDefaultAlphaRef)
def regulate(self):
# regulate colors
self.mDiffuse.regulate()
self.mAmbient.regulate()
self.mSpecular.regulate()
self.mEmissive.regulate()
self.mTextureBorderColor.regulate()
# only diffuse and texture border color can have alpha component
self.mAmbient.a = 1.0
self.mSpecular.a = 1.0
self.mEmissive.a = 1.0
# alpha ref limit
self.mAlphaRef = UTIL_functions.clamp_int(self.mAlphaRef, 0, 255)
# specular power
self.mSpecularPower = UTIL_functions.clamp_float(self.mSpecularPower, 0.0, 100.0)
#region Blender Enum Prop Helper (Virtools type specified)
_g_Helper_VXTEXTURE_BLENDMODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXTEXTURE_BLENDMODE)
_g_Helper_VXTEXTURE_FILTERMODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXTEXTURE_FILTERMODE)
_g_Helper_VXTEXTURE_ADDRESSMODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXTEXTURE_ADDRESSMODE)
_g_Helper_VXBLEND_MODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXBLEND_MODE)
_g_Helper_VXFILL_MODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXFILL_MODE)
_g_Helper_VXSHADE_MODE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXSHADE_MODE)
_g_Helper_VXCMPFUNC: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXCMPFUNC)
#endregion
class BBP_PG_virtools_material(bpy.types.PropertyGroup):
ambient: bpy.props.FloatVectorProperty(
name = "Ambient",
description = "Ambient color of the material",
subtype = 'COLOR',
min = 0.0,
max = 1.0,
size = 3,
default = RawVirtoolsMaterial.cDefaultAmbient.to_const_rgb()
)
diffuse: bpy.props.FloatVectorProperty(
name = "Diffuse",
description = "Diffuse color of the material",
subtype = 'COLOR_GAMMA',
min = 0.0,
max = 1.0,
size = 4,
default = RawVirtoolsMaterial.cDefaultDiffuse.to_const_rgba()
)
specular: bpy.props.FloatVectorProperty(
name = "Specular",
description = "Specular color of the material",
subtype = 'COLOR',
min = 0.0,
max = 1.0,
size = 3,
default = RawVirtoolsMaterial.cDefaultSpecular.to_const_rgb()
)
emissive: bpy.props.FloatVectorProperty(
name = "Emissive",
description = "Emissive color of the material",
subtype = 'COLOR',
min = 0.0,
max = 1.0,
size = 3,
default = RawVirtoolsMaterial.cDefaultEmissive.to_const_rgb()
)
specular_power: bpy.props.FloatProperty(
name = "Power",
description = "Specular highlight power",
min = 0.0,
max = 100.0,
default = RawVirtoolsMaterial.cDefaultSpecularPower
)
texture: bpy.props.PointerProperty(
type = bpy.types.Image,
name = "Texture",
description = "Texture of the material"
)
texture_border_color: bpy.props.FloatVectorProperty(
name = "Border Color",
description = "The border color is used when the texture address mode is VXTEXTURE_ADDRESSBORDER.",
subtype = 'COLOR_GAMMA',
min = 0.0,
max = 1.0,
size = 4,
default = RawVirtoolsMaterial.cDefaultTextureBorderColor.to_const_rgba()
)
texture_blend_mode: bpy.props.EnumProperty(
name = "Texture Blend",
description = "Texture blend mode",
items = _g_Helper_VXTEXTURE_BLENDMODE.generate_items(),
default = _g_Helper_VXTEXTURE_BLENDMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureBlendMode)
)
texture_min_mode: bpy.props.EnumProperty(
name = "Filter Min",
description = "Texture filter mode when the texture is minified",
items = _g_Helper_VXTEXTURE_FILTERMODE.generate_items(),
default = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureMinMode)
)
texture_mag_mode: bpy.props.EnumProperty(
name = "Filter Mag",
description = "Texture filter mode when the texture is magnified",
items = _g_Helper_VXTEXTURE_FILTERMODE.generate_items(),
default = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureMagMode)
)
texture_address_mode: bpy.props.EnumProperty(
name = "Address Mode",
description = "The address mode controls how the texture coordinates outside the range 0..1",
items = _g_Helper_VXTEXTURE_ADDRESSMODE.generate_items(),
default = _g_Helper_VXTEXTURE_ADDRESSMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureAddressMode)
)
source_blend: bpy.props.EnumProperty(
name = "Source Blend",
description = "Source blend factor",
items = _g_Helper_VXBLEND_MODE.generate_items(),
default = _g_Helper_VXBLEND_MODE.to_selection(RawVirtoolsMaterial.cDefaultSourceBlend)
)
dest_blend: bpy.props.EnumProperty(
name = "Destination Blend",
description = "Destination blend factor",
items = _g_Helper_VXBLEND_MODE.generate_items(),
default = _g_Helper_VXBLEND_MODE.to_selection(RawVirtoolsMaterial.cDefaultDestBlend)
)
fill_mode: bpy.props.EnumProperty(
name = "Fill Mode",
description = "Fill mode",
items = _g_Helper_VXFILL_MODE.generate_items(),
default = _g_Helper_VXFILL_MODE.to_selection(RawVirtoolsMaterial.cDefaultFillMode)
)
shade_mode: bpy.props.EnumProperty(
name = "Shade Mode",
description = "Shade mode",
items = _g_Helper_VXSHADE_MODE.generate_items(),
default = _g_Helper_VXSHADE_MODE.to_selection(RawVirtoolsMaterial.cDefaultShadeMode)
)
enable_alpha_test: bpy.props.BoolProperty(
name = "Alpha Test",
description = "Whether the alpha test is enabled",
default = RawVirtoolsMaterial.cDefaultEnableAlphaTest
)
enable_alpha_blend: bpy.props.BoolProperty(
name = "Blend",
description = "Whether alpha blending is enabled or not.",
default = RawVirtoolsMaterial.cDefaultEnableAlphaBlend
)
enable_perspective_correction: bpy.props.BoolProperty(
name = "Perspective Correction",
description = "Whether texture perspective correction is enabled",
default = RawVirtoolsMaterial.cDefaultEnablePerspectiveCorrection
)
enable_z_write: bpy.props.BoolProperty(
name = "Z-Buffer Write",
description = "Whether writing in ZBuffer is enabled.",
default = RawVirtoolsMaterial.cDefaultEnableZWrite
)
enable_two_sided: bpy.props.BoolProperty(
name = "Both Sided",
description = "Whether the material is both sided or not",
default = RawVirtoolsMaterial.cDefaultEnableTwoSided
)
alpha_ref: bpy.props.IntProperty(
name = "Alpha Ref Value",
description = "Alpha referential value",
min = 0,
max = 255,
default = RawVirtoolsMaterial.cDefaultAlphaRef
)
alpha_func: bpy.props.EnumProperty(
name = "Alpha Test Function",
description = "Alpha comparision function",
items = _g_Helper_VXCMPFUNC.generate_items(),
default = _g_Helper_VXCMPFUNC.to_selection(RawVirtoolsMaterial.cDefaultAlphaFunc)
)
z_func: bpy.props.EnumProperty(
name = "Z Compare Function",
description = "Z Comparison function",
items = _g_Helper_VXCMPFUNC.generate_items(),
default = _g_Helper_VXCMPFUNC.to_selection(RawVirtoolsMaterial.cDefaultZFunc)
)
#region Getter Setter
def get_virtools_material(mtl: bpy.types.Material) -> BBP_PG_virtools_material:
return mtl.virtools_material
def get_raw_virtools_material(mtl: bpy.types.Material) -> RawVirtoolsMaterial:
props: BBP_PG_virtools_material = get_virtools_material(mtl)
rawdata: RawVirtoolsMaterial = RawVirtoolsMaterial()
rawdata.mDiffuse.from_const_rgba(props.diffuse)
rawdata.mAmbient.from_const_rgb(props.ambient)
rawdata.mSpecular.from_const_rgb(props.specular)
rawdata.mEmissive.from_const_rgb(props.emissive)
rawdata.mSpecularPower = props.specular_power
rawdata.mTexture = props.texture
rawdata.mTextureBorderColor.from_const_rgba(props.texture_border_color)
rawdata.mTextureBlendMode = _g_Helper_VXTEXTURE_BLENDMODE.get_selection(props.texture_blend_mode)
rawdata.mTextureMinMode = _g_Helper_VXTEXTURE_FILTERMODE.get_selection(props.texture_min_mode)
rawdata.mTextureMagMode = _g_Helper_VXTEXTURE_FILTERMODE.get_selection(props.texture_mag_mode)
rawdata.mTextureAddressMode = _g_Helper_VXTEXTURE_ADDRESSMODE.get_selection(props.texture_address_mode)
rawdata.mSourceBlend = _g_Helper_VXBLEND_MODE.get_selection(props.source_blend)
rawdata.mDestBlend = _g_Helper_VXBLEND_MODE.get_selection(props.dest_blend)
rawdata.mFillMode = _g_Helper_VXFILL_MODE.get_selection(props.fill_mode)
rawdata.mShadeMode = _g_Helper_VXSHADE_MODE.get_selection(props.shade_mode)
rawdata.mEnableAlphaTest = props.enable_alpha_test
rawdata.mEnableAlphaBlend = props.enable_alpha_blend
rawdata.mEnablePerspectiveCorrection = props.enable_perspective_correction
rawdata.mEnableZWrite = props.enable_z_write
rawdata.mEnableTwoSided = props.enable_two_sided
rawdata.mAlphaRef = props.alpha_ref
rawdata.mAlphaFunc = _g_Helper_VXCMPFUNC.get_selection(props.alpha_func)
rawdata.mZFunc = _g_Helper_VXCMPFUNC.get_selection(props.z_func)
rawdata.regulate()
return rawdata
def set_raw_virtools_material(mtl: bpy.types.Material, rawdata: RawVirtoolsMaterial) -> None:
props: BBP_PG_virtools_material = get_virtools_material(mtl)
props.diffuse = rawdata.mDiffuse.to_const_rgba()
props.ambient = rawdata.mAmbient.to_const_rgb()
props.specular = rawdata.mSpecular.to_const_rgb()
props.emissive = rawdata.mEmissive.to_const_rgb()
props.specular_power = rawdata.mSpecularPower
props.texture = rawdata.mTexture
props.texture_border_color = rawdata.mTextureBorderColor.to_const_rgba()
props.texture_blend_mode = _g_Helper_VXTEXTURE_BLENDMODE.to_selection(rawdata.mTextureBlendMode)
props.texture_min_mode = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(rawdata.mTextureMinMode)
props.texture_mag_mode = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(rawdata.mTextureMagMode)
props.texture_address_mode = _g_Helper_VXTEXTURE_ADDRESSMODE.to_selection(rawdata.mTextureAddressMode)
props.source_blend = _g_Helper_VXBLEND_MODE.to_selection(rawdata.mSourceBlend)
props.dest_blend = _g_Helper_VXBLEND_MODE.to_selection(rawdata.mDestBlend)
props.fill_mode = _g_Helper_VXFILL_MODE.to_selection(rawdata.mFillMode)
props.shade_mode = _g_Helper_VXSHADE_MODE.to_selection(rawdata.mShadeMode)
props.enable_alpha_test = rawdata.mEnableAlphaTest
props.enable_alpha_blend = rawdata.mEnableAlphaBlend
props.enable_perspective_correction = rawdata.mEnablePerspectiveCorrection
props.enable_z_write = rawdata.mEnableZWrite
props.enable_two_sided = rawdata.mEnableTwoSided
props.alpha_ref = rawdata.mAlphaRef
props.alpha_func = _g_Helper_VXCMPFUNC.to_selection(rawdata.mAlphaFunc)
props.z_func = _g_Helper_VXCMPFUNC.to_selection(rawdata.mZFunc)
def apply_to_blender_material(mtl: bpy.types.Material):
# get raw material data
rawdata: RawVirtoolsMaterial = get_raw_virtools_material(mtl)
# enable nodes mode
mtl.use_nodes = True
# delete all existed nodes
for node in mtl.node_tree.nodes:
mtl.node_tree.nodes.remove(node)
# create ballance-style blender material
# for sockets name, see `bpy_extras.node_shader_utils` for more infos
bnode: bpy.types.ShaderNodeBsdfPrincipled = mtl.node_tree.nodes.new(type = "ShaderNodeBsdfPrincipled")
mnode: bpy.types.ShaderNodeOutputMaterial = mtl.node_tree.nodes.new(type = "ShaderNodeOutputMaterial")
mtl.node_tree.links.new(bnode.outputs["BSDF"], mnode.inputs["Surface"])
# set basic colors
metallic_value = sum(rawdata.mAmbient.to_const_rgb()) / 3
mtl.metallic = metallic_value
bnode.inputs["Metallic"].default_value = metallic_value
diffuse_value = rawdata.mDiffuse.to_const_rgba()
mtl.diffuse_color = diffuse_value
bnode.inputs["Base Color"].default_value = diffuse_value
mtl.specular_color = rawdata.mSpecular.to_const_rgb()
# too shiny, disabled.
# bnode.inputs["Emission"].default_value = rawdata.mEmissive.to_const_rgba()
mtl.specular_intensity = rawdata.mSpecularPower
bnode.inputs["Specular"].default_value = UTIL_functions.clamp_float(
rawdata.mSpecularPower, 0.0, 1.0
)
# set some alpha data
mtl.use_backface_culling = not rawdata.mEnableTwoSided
mtl.blend_method = 'BLEND' if rawdata.mEnableAlphaBlend else 'OPAQUE'
# set texture
if rawdata.mTexture is not None:
# basic texture setter
inode: bpy.types.ShaderNodeTexImage = mtl.node_tree.nodes.new(type = "ShaderNodeTexImage")
inode.image = rawdata.mTexture
mtl.node_tree.links.new(inode.outputs["Color"], bnode.inputs["Base Color"])
# todo: sync texture mapping config here
# link alpha if necessary
if rawdata.mEnableAlphaBlend:
mtl.node_tree.links.new(inode.outputs["Alpha"], bnode.inputs["Alpha"])
#endregion
#region Preset Paramters
class MaterialPresetType(enum.IntEnum):
FloorSide = enum.auto()
FloorTop = enum.auto()
TrafoPaper = enum.auto()
TraforWoodStone = enum.auto()
Rail = enum.auto()
WoodPath = enum.auto()
WoodChip = enum.auto()
class MaterialPresetData():
mDisplayName: str
mData: RawVirtoolsMaterial
def __init__(self, display_name: str, data: RawVirtoolsMaterial):
self.mDisplayName = display_name
self.mData = data
_g_MaterialPresets: dict[int, MaterialPresetData] = {
MaterialPresetType.FloorSide: MaterialPresetData(
"Floor Side",
RawVirtoolsMaterial(
mAmbient = UTIL_virtools_types.VxColor(0.0, 0.0, 0.0),
mDiffuse = UTIL_virtools_types.VxColor(122 / 255.0, 122 / 255.0, 122 / 255.0),
mSpecular = UTIL_virtools_types.VxColor(0.0, 0.0, 0.0),
mEmissive = UTIL_virtools_types.VxColor(104 / 255.0, 104 / 255.0, 104 / 255.0),
mSpecularPower = 0.0
)
),
MaterialPresetType.FloorTop: MaterialPresetData(
"Floor Top",
RawVirtoolsMaterial(
mAmbient = UTIL_virtools_types.VxColor(0.0, 0.0, 0.0),
mDiffuse = UTIL_virtools_types.VxColor(1.0, 1.0, 1.0),
mSpecular = UTIL_virtools_types.VxColor(80 / 255.0, 80 / 255.0, 80 / 255.0),
mEmissive = UTIL_virtools_types.VxColor(0.0, 0.0, 0.0),
mSpecularPower = 100.0
)
),
MaterialPresetType.TrafoPaper: MaterialPresetData(
"Transform Paper",
RawVirtoolsMaterial(
mAmbient = UTIL_virtools_types.VxColor(25 / 255.0, 25 / 255.0, 25 / 255.0),
mDiffuse = UTIL_virtools_types.VxColor(1.0, 1.0, 1.0),
mSpecular = UTIL_virtools_types.VxColor(0.0, 0.0, 0.0),
mEmissive = UTIL_virtools_types.VxColor(100 / 255.0, 100 / 255.0, 100 / 255.0),
mSpecularPower = 0.0
)
),
MaterialPresetType.TraforWoodStone: MaterialPresetData(
"Transform Stone & Wood",
RawVirtoolsMaterial(
mAmbient = UTIL_virtools_types.VxColor(25 / 255.0, 25 / 255.0, 25 / 255.0),
mDiffuse = UTIL_virtools_types.VxColor(1.0, 1.0, 1.0),
mSpecular = UTIL_virtools_types.VxColor(229 / 255.0, 229 / 255.0, 229 / 255.0),
mEmissive = UTIL_virtools_types.VxColor(60 / 255.0, 60 / 255.0, 60 / 255.0),
mSpecularPower = 0.0
)
),
MaterialPresetType.Rail: MaterialPresetData(
"Rail",
RawVirtoolsMaterial(
mAmbient = UTIL_virtools_types.VxColor(0.0, 0.0, 0.0),
mDiffuse = UTIL_virtools_types.VxColor(100 / 255.0, 118 / 255.0, 133 / 255.0),
mSpecular = UTIL_virtools_types.VxColor(210 / 255.0, 210 / 255.0, 210 / 255.0),
mEmissive = UTIL_virtools_types.VxColor(124 / 255.0, 134 / 255.0, 150 / 255.0),
mSpecularPower = 10.0
)
),
MaterialPresetType.WoodPath: MaterialPresetData(
"Wood Path",
RawVirtoolsMaterial(
mAmbient = UTIL_virtools_types.VxColor(2 / 255.0, 2 / 255.0, 2 / 255.0),
mDiffuse = UTIL_virtools_types.VxColor(1.0, 1.0, 1.0),
mSpecular = UTIL_virtools_types.VxColor(59 / 255.0, 59 / 255.0, 59 / 255.0),
mEmissive = UTIL_virtools_types.VxColor(30 / 255.0, 30 / 255.0, 30 / 255.0),
mSpecularPower = 25.0
)
),
MaterialPresetType.WoodChip: MaterialPresetData(
"Wood Chip",
RawVirtoolsMaterial(
mAmbient = UTIL_virtools_types.VxColor(25 / 255.0, 25 / 255.0, 25 / 255.0),
mDiffuse = UTIL_virtools_types.VxColor(1.0, 1.0, 1.0),
mSpecular = UTIL_virtools_types.VxColor(100 / 255.0, 100 / 255.0, 100 / 255.0),
mEmissive = UTIL_virtools_types.VxColor(50 / 255.0, 50 / 255.0, 50 / 255.0),
mSpecularPower = 50.0
)
),
}
def get_virtools_material_preset(preset_type: MaterialPresetType) -> MaterialPresetData:
return _g_MaterialPresets[preset_type]
def preset_virtools_material(mtl: bpy.types.Material, preset_type: MaterialPresetType) -> None:
# get preset raw mtl member first
# but we need create a shallow copy for it first.
preset_data: RawVirtoolsMaterial = copy.copy(_g_MaterialPresets[preset_type].mData)
# the we get texture setting from now texture
now_data: RawVirtoolsMaterial = get_raw_virtools_material(mtl)
# change preset texture to current texture
# because we do not want to change texture by preset.
# also this is the reason why i need do a shallow copy for preset data
preset_data.mTexture = now_data.mTexture
# apply preset
set_raw_virtools_material(mtl, preset_data)
# create preset enum blender helper
_g_Helper_MtlPreset: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelper(
MaterialPresetType,
lambda x: str(x.value),
lambda x: MaterialPresetType(int(x)),
lambda x: x.name,
lambda _: '',
lambda _: ''
)
#endregion
#region Operators
class BBP_OT_apply_virtools_material(bpy.types.Operator):
"""Apply Virtools Material to Blender Material."""
bl_idname = "bbp.apply_virtools_material"
bl_label = "Apply to Blender Material"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return context.material is not None
def execute(self, context):
mtl: bpy.types.Material = context.material
apply_to_blender_material(mtl)
return {'FINISHED'}
class BBP_OT_preset_virtools_material(bpy.types.Operator):
"""Preset Virtools Material with Original Ballance Data."""
bl_idname = "bbp.preset_virtools_material"
bl_label = "Preset Virtools Material"
bl_options = {'UNDO'}
preset_type: bpy.props.EnumProperty(
name = "Preset",
description = "The preset which you want to apply.",
items = _g_Helper_MtlPreset.generate_items(),
)
@classmethod
def poll(cls, context):
return context.material is not None
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
self.layout.prop(self, "preset_type")
def execute(self, context):
# get essential value
mtl: bpy.types.Material = context.material
expected_preset: MaterialPresetType = _g_Helper_MtlPreset.get_selection(self.preset_type)
# apply preset to material
preset_virtools_material(mtl, expected_preset)
return {'FINISHED'}
class BBP_OT_direct_set_virtools_texture(bpy.types.Operator, UTIL_file_browser.ImportBallanceImage):
"""Import and Assign Texture Directly"""
bl_idname = "bbp.direct_set_virtools_texture"
bl_label = "Import and Assign Texture"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
# ballance texture order this
if not PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder(): return False
# we only accept panel executing
if context.material is None: return False
# ok
return True
def draw(self, context):
pass
def invoke(self, context, event):
# preset tex folder
self.general_set_filename(PROP_preferences.get_raw_preferences().mBallanceTextureFolder)
return UTIL_file_browser.ImportBallanceImage.invoke(self, context, event)
def execute(self, context):
# get assoc mtl
mtl: bpy.types.Material = context.material
rawmtl: RawVirtoolsMaterial = get_raw_virtools_material(mtl)
# import texture according to whether it is ballance texture
texture_filepath: str = self.general_get_filename()
try_filepath: str | None = UTIL_ballance_texture.get_ballance_texture_filename(texture_filepath)
tex: bpy.types.Image
if try_filepath is None:
# load as other texture
tex = UTIL_ballance_texture.load_other_texture(texture_filepath)
# set texture props
PROP_virtools_texture.set_raw_virtools_texture(tex, PROP_virtools_texture.get_nonballance_texture_preset())
else:
# load as ballance texture
tex = UTIL_ballance_texture.load_ballance_texture(try_filepath)
# set texture props
PROP_virtools_texture.set_raw_virtools_texture(tex, PROP_virtools_texture.get_ballance_texture_preset(try_filepath))
# assign texture
rawmtl.mTexture = tex
set_raw_virtools_material(mtl, rawmtl)
return {'FINISHED'}
#endregion
class BBP_PT_virtools_material(bpy.types.Panel):
"""Show Virtools Material Properties."""
bl_label = "Virtools Material"
bl_idname = "BBP_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(self, context):
# get layout and target
layout = self.layout
props: BBP_PG_virtools_material = get_virtools_material(context.material)
# draw operator
row = layout.row()
row.operator(BBP_OT_preset_virtools_material.bl_idname, text = 'Preset', icon = "PRESET")
row.operator(BBP_OT_apply_virtools_material.bl_idname, text = 'Apply', icon = "NODETREE")
# draw data
layout.label(text="Color Parameters")
layout.prop(props, 'ambient')
layout.prop(props, 'diffuse')
layout.prop(props, 'specular')
layout.prop(props, 'emissive')
layout.prop(props, 'specular_power')
layout.separator()
layout.label(text="Mode Parameters")
layout.prop(props, 'enable_two_sided')
layout.prop(props, 'fill_mode')
layout.prop(props, 'shade_mode')
layout.separator()
layout.label(text="Texture Parameters")
# texture prop with direct importing
sublay = layout.row()
sublay.prop(props, 'texture', emboss = True)
sublay.operator(BBP_OT_direct_set_virtools_texture.bl_idname, text = '', icon = 'FILEBROWSER')
# texture detail
if props.texture is not None:
# have texture, show texture settings and enclosed by a border.
boxlayout = layout.box()
boxlayout.label(text="Virtools Texture Settings")
PROP_virtools_texture.draw_virtools_texture(props.texture, boxlayout)
layout.prop(props, 'texture_blend_mode')
layout.prop(props, 'texture_min_mode')
layout.prop(props, 'texture_mag_mode')
layout.prop(props, 'texture_address_mode')
layout.prop(props, 'enable_perspective_correction')
if (int(props.texture_address_mode) == UTIL_virtools_types.VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSBORDER.value):
layout.prop(props, 'texture_border_color')
layout.separator()
layout.label(text="Alpha Test Parameters")
layout.prop(props, 'enable_alpha_test')
if props.enable_alpha_test:
layout.prop(props, 'alpha_func')
layout.prop(props, 'alpha_ref')
layout.separator()
layout.label(text="Alpha Blend Parameters")
layout.prop(props, 'enable_alpha_blend')
if props.enable_alpha_blend:
layout.prop(props, 'source_blend')
layout.prop(props, 'dest_blend')
layout.separator()
layout.label(text="Z Write Parameters")
layout.prop(props, 'enable_z_write')
if props.enable_z_write:
layout.prop(props, 'z_func')
def register():
bpy.utils.register_class(BBP_PG_virtools_material)
bpy.utils.register_class(BBP_OT_apply_virtools_material)
bpy.utils.register_class(BBP_OT_preset_virtools_material)
bpy.utils.register_class(BBP_OT_direct_set_virtools_texture)
bpy.utils.register_class(BBP_PT_virtools_material)
# add into material metadata
bpy.types.Material.virtools_material = bpy.props.PointerProperty(type = BBP_PG_virtools_material)
def unregister():
# del from material metadata
del bpy.types.Material.virtools_material
bpy.utils.unregister_class(BBP_PT_virtools_material)
bpy.utils.unregister_class(BBP_OT_direct_set_virtools_texture)
bpy.utils.unregister_class(BBP_OT_preset_virtools_material)
bpy.utils.unregister_class(BBP_OT_apply_virtools_material)
bpy.utils.unregister_class(BBP_PG_virtools_material)

View 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():
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():
# 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)

View 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():
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():
# 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
View 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.

View File

785
bbp_ng/PyBMap/bmap.py Normal file
View 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

View 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

View 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.

View 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

513
bbp_ng/UTIL_blender_mesh.py Normal file
View File

@ -0,0 +1,513 @@
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 params __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 | None
__mTempMesh: bpy.types.Mesh | None
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 | None ##< 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()
self.__mAssocMesh.calc_normals_split()
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.free_normals_split()
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.loops:
cache.x = nml.normal.x
cache.y = nml.normal.y
cache.z = nml.normal.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 | None ##< 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]
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)
# split normals, it is IMPORTANT
self.__mAssocMesh.create_normals_split()
# 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
self.__mAssocMesh.loops.foreach_set('normal',
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().
loops_normals = array.array('f', [0.0] * (len(self.__mAssocMesh.loops) * 3))
self.__mAssocMesh.loops.foreach_get('normal', loops_normals)
# apply data
self.__mAssocMesh.normals_split_custom_set(
tuple(_nest_custom_split_normal(loops_normals))
)
# enable auto smooth. it is IMPORTANT
self.__mAssocMesh.use_auto_smooth = True
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()

503
bbp_ng/UTIL_bme.py Normal file
View File

@ -0,0 +1,503 @@
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))
# 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
face_indices_data: list[int] = 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]
for i in range(len(face_data[TOKEN_FACES_INDICES])):
# 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]
# calc indices count
face_indices: list[int] = face_data[TOKEN_FACES_INDICES]
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)
#endregion

101
bbp_ng/UTIL_file_browser.py Normal file
View 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
View 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

151
bbp_ng/UTIL_functions.py Normal file
View File

@ -0,0 +1,151 @@
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)
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)

View 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():
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():
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()

View 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(';')))

View File

@ -0,0 +1,603 @@
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 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():
cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
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 = VirtoolsGroupConvention.cRegexGroupSector.match(i)
if regex_result is not None:
last_matched_sector = int(regex_result.group(1))
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')
case BallanceObjectType.RAIL:
gp.add_group('Phys_FloorRails')
gp.add_group('Sound_HitID_02')
gp.add_group('Sound_RollID_02')
case BallanceObjectType.WOOD:
gp.add_group('Phys_Floors')
gp.add_group('Sound_HitID_03')
gp.add_group('Sound_RollID_03')
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
if info.mSector == 9:
gp.add_group('Sector_9')
else:
gp.add_group(f'Sector_{info.mSector:0>2d}')
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'PR_Resetpoint_{info.mSector:0>2d}'
case BallanceObjectType.RESETPOINT:
return f'PC_TwoFlames_{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

View 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

280
bbp_ng/__init__.py Normal file
View File

@ -0,0 +1,280 @@
bl_info = {
"name": "Ballance Blender Plugin",
"description": "Ballance mapping tools for Blender",
"author": "yyc12345",
"version": (4, 0),
"blender": (3, 6, 0),
"category": "Object",
"support": "COMMUNITY",
"warning": "Please read document before using this plugin.",
"doc_url": "https://github.com/yyc12345/BallanceBlenderHelper",
"tracker_url": "https://github.com/yyc12345/BallanceBlenderHelper/issues"
}
#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, PROP_ballance_element, PROP_bme_material
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_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)
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_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()
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_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_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_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

0
bbp_ng/icons/.gitkeep Normal file
View File

0
bbp_ng/jsons/.gitkeep Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
bbp_ng/meshes/P_Box.bin Normal file

Binary file not shown.

BIN
bbp_ng/meshes/P_Dome.bin Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
bbp_ng/raw_icons/Empty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

View File

@ -0,0 +1,7 @@
# Raw Icons
This folder contain all images used by this Blender plugin.
This folder should not be distributed in production because all of these files are in original size. It is pretty need too much time to load them in blender.
So we keep these high quality images here and provide a tools in `tools` folder. Builder should run script to generate thumbnails in `icons` folder.
Then this Blender plugin can work normally.

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Some files were not shown because too many files have changed in this diff Show More