18 Commits

Author SHA1 Message Date
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
51 changed files with 1261 additions and 464 deletions

2
.gitattributes vendored
View File

@ -1,3 +1,5 @@
# all png are binary
*.png binary
# our generated mesh should be save as binary # our generated mesh should be save as binary
*.bin binary *.bin binary
# json is data and not good for human reading(althought I edit it on my own hand.) # json is data and not good for human reading(althought I edit it on my own hand.)

152
README.md
View File

@ -2,61 +2,73 @@
[中文版本](README_ZH.md) [中文版本](README_ZH.md)
## Brief introduction ## Brief Introduction
This is a Blender plugin which is served for Ballance mapping in Blender. This is a Blender plugin which is served for Ballance mapping in Blender.
The latest commit may not be stable to use, please use the latest commit with git tag as the stable version.
This plugin contain various aspect of Ballance mapping. However, if some features can be easily gotten from other Blender plugin, this plugin will not provide them duplicatedly. We highly recommend that use this plugin with following plugins.
The latest commit may not be stable to use, please visit the Release page to get a stable version. * [BenjaminSauder/SimpleLattice](https://github.com/BenjaminSauder/SimpleLattice): Create lattice quickly to transform object.
* [egtwobits/Mesh Align Plus](https://github.com/egtwobits/mesh_mesh_align_plus): Provide powerful align functions which far beyond vanilla Blender align function.
## Technical infomation ## Technical Infomation
Used BM file spec can be found in [there](https://github.com/yyc12345/gist/blob/master/BMFileSpec/BMSpec_ZH.md)(Chinese only). Used BM file spec can be found in [there](https://github.com/yyc12345/gist/blob/master/BMFileSpec/BMSpec_ZH.md) (Chinese only).
Used tools chain principle and the file format located in `meshes` can be found in [there](https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md) (Chinese only).
The format of the files which are under the `jsons` folder and belong to the BMERevenge section, can be found in [here](https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_ZH.md) (Chinese only).
Used tools chain principle and the file format located in `meshes` can be found in [there](https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md)(Chinese only). This plugin will continuously support Blender lastest **LTS** version. This plugin will migrate to new version when the new LTS version released. Currently, it based on Blender **3.3.x**.
The format of the files which are under the `jsons` folder and belong to the BMERevenge section, can be found in [here](https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_ZH.md) ## Installation
This plugin will continuously support Blender lastest **LTS** version. This plugin will migrate to new version when the new LTS version released. Currently, it based on Blender 2.83.x. Put `ballance_blender_plugin` into Blender's plugin folder, `scripts/addons_contrib`. Then enable this plugin in Blender's preferences (DO NOT forget to configure this plugin's settings after first installation or updating plugin.).
## Function introduction ## Feature Introduction
### Plugin settings ### Plugin Settings
* External texture folder: Please fill in the Texture directory of Ballance, the plugin will call the external texture file from this directory (the texture file originally with Ballance) * External Texture Folder: Please fill in the `Texture` directory of Ballance, the plugin will refer the external texture file from this directory (the texture file originally with Ballance)
* No component collection: Objects in this collection will be forced to be set as non-Component. If left blank, this function will be shutdown. * No Component Collection: Objects located in this collection will be forced to be set as non-component. If left blank, this function will be shutdown. This function is frequently used in forced component replacement.
* Temp texture folder: used to cache texture files extracted from BM files. Please arrange a directory that will not be automatically cleaned up. Since Blender will continue to read texture files from this directory, it cannot be emptied at will. And it also does not allow files with the same name to exist, that is, if I import two BMs for two maps, and there are two files with the same name but different images in the two BMs, the later files will overwrite the previous files , And in turn caused a texture error when the first blender document was opened again. For solving this problem, please refer to the subsequent BM import / export * Temp Texture Folder: used to cache texture files extracted from BM files. Please arrange a directory that will not be automatically cleaned up. Since Blender will continue to read texture files from this directory, it can not be emptied casually.
### BM import / export Temp Texture Folder does not allow files with duplicated name. Because of this, imagine this situation, there are two texture files with the same name in two BM files, but they represent different images. When you import them one by one for different maps. The later file will overwrite the previous file, And this will cause a texture error when the first Blender document was opened again. For the solution of this issue, the best way is to force packaging once. After successfully importing the BM, click `File - External Data - Pack Resources`, then you can clear Temp Texture Folder safely. With your preference, you also can click `File - External Data - Unpack Resources` to extract textures. This operation will extract and re-refer all textures into standalone texture folder within the folder where this Blender document is.
For import, in order to prevent texture errors, the best way is to force packaging once. After successfully importing the BM, choose to pack all into the blend file, and then clear the directory where the Temp texture folder is located, and then click Unpack to file if necessary, this operation will re-depend the textures in the texture library under the project folder. ### BM Import / Export
For export, you can choose to export a collection or an object (Export mode), and specify the target (Export target). Click `File - Import - Ballance Map` to import BM file.
When name conflicts occur during importing BM, you have ability to choose different strategies for 4 different data types, Texture, Material, Mesh and Object. You can specify them to create a new instance or use current data block.
It should be noted that once the BM is exported, all the faces in the file will be converted to triangular faces, please make a backup in advance. And it is recommended to use a flat collection structure, do not put a collections within another collection, which may cause some unnecessary problems. Click `File - Export - Ballance Map` to export a BM file.
You can export a collection or an object (Export Mode), and specify target (Export Target) correspondingly.
Although plugin provide Virtools Group feature and give you ability to grouping object in Blender. The export function also depend on Tools Chain Principle. Because of this, if you do not follow Tools Chain Principle, some convenient features will be disabled, for example, your exported BM file may larger than common one.
It should be noted that once the BM is exported, all the faces in the file will be converted to triangular faces, please make a backup in advance.
It is recommended to use a flat collection structure, do not put a collection within another collection, which may cause some unnecessary problems.
The suffix name of BM file is BMX, X stands for compression. BMX and BM is the same thing.
### Ballance 3D ### Ballance 3D
Ballance 3D is a set of light tools related to 3D operations, which can be found in the upper right corner of the 3D view. Ballance 3D is a set of light tools related to 3D operations, which can be found in the upper left corner menu bar of 3D View. This menu is named as Ballance.
#### 3ds Max Align #### 3ds Max Align
Provide 3ds Max like align tools. Current active will be seen as reference object. All selected objects(except active object) will be seen as operating object (So you can select multiple objects to align to the reference object). Provide 3ds Max like align tools. Current active object will be seen as reference object. All selected objects (except active object) will be seen as operating object (So you can select multiple objects to align to a single object).
#### Create Rail UV #### Create Rail UV
To create UVs for the rails in the map, you need to select the objects that need to add UVs similar to the rails, and then click this button to create. To create UVs for the rails in the map, you need to select the objects that need to add UVs similar to the rails, and then click this button to create.
In the dialog, you can select the material to be used. You can also choose the unfolding mode. In some unfolding modes, projection axis and zoom ratio options is available. Although Ballance will process all rail UV in game internally, it is essential that give a perfect UV in designer.
In the dialog, you can select the material to be used. You can also choose the unfolding mode. For shorter rails, you can choose Point mode. For longer rails, you can use Uniform mode. If you need to manually adjust the zoom ratio, please select Scale mode and specify the ratio (not recommended). If you want that the rail have in-game UV (it represent as smooth texture), you can choose `TT_ReflectionMapping` unfolding mode. This mode is written with the reverse work of game used function. This unfolding mode may be useful when you creating advertisement image in Blender for your map.
You can also select the projection axis for better UV distribution.
#### Flatten UV #### Flatten UV
In the object editing mode, it is a operator which is used to attach the currently selected surface to the UV. And you can specific the edge which will be attached into the V axis. Note that only convex faces are supported. In the object editing mode, it is a operator which is used to attach the currently selected surface to the UV. And you can specific the edge which will be attached into the V axis.
Note that only convex face is supported. Applying this for a concave face will cause undefined behavior.
In the edit mode, select the surface, click Flatten UV, and then scroll the slider to select an edge as a reference. If the generated UV is not attached correctly, such as the FloorSide's band is pasted to the bottom, you can reselect the reference edge and redo the operation until it is correct. In the edit mode, select the surface, click Flatten UV, and then scroll the slider to select an edge as a reference.
If the generated UV is not attached correctly, such as the FloorSide's band is pasted to the bottom, you can reselect the reference edge and redo the operation until it is correct.
### Add Menu ### Quick Struct Adder
In the add menu, we have added a set of commonly used objects. After adding, the object will move to the 3D cursor. In the add menu, we have added a set of commonly used objects. After adding, the object will move to the 3D cursor.
@ -66,15 +78,95 @@ Add elements, you can also specify attributes such as section when adding (it wi
#### Rail section #### Rail section
Add rail section, you can choose monorail or rail (just decide the number of rail section loops added, and will not help you rotate the angle), as well as rail radius and rail span. Add rail section, you can choose monorail or rail (just decide the number of rail section loops added, and will not help you rotate the angle), as well as rail radius and rail span (default value is standard value).
#### Floors #### Floors
Adding floor is part of the BMERevenge project. Basic floor is a basic floor component, and Derived floor is a common component composed of basic components. The extension(length) and the side configuration can be set according to its properties. It also has the advantage of reducing vertices. A powerful floor adder feature belong to the extension of BMERevenge project.
In menu, Basic Floor is basic floor component. Derived Floor is consisted by basic floor components. Commonly, frequently used models are located in Derived Floor section.
After selecting a floor type, you can assign 2 expand value at most, according to its property. You also can use options to decide whether side faces and bottom face can be generated.
Comparing with trditional Ballance Map Editor, this function can massively reduce useless vertices.
It is recommended to merge the vertices by distance, unless there is a need to delete the surface after adding it The floor type can be simply grouped as Flat Floor, Sink Floor, Wide Floor and Platform.
Additionally, Trafo Block and Transition between Flat Floor and Sink Floor are available.
## Install It is recommended to merge the vertices by distance, unless you need do some special work.
Put `ballance_blender_plugin` into Blender's plugin folder, `scripts/addons_contrib`. Then enable this plugin in Blender's preferences (Don't forget to configure this plugin's settings). ### Virtools Group
Plugin add a new property for each Blender objects, called Virtools Group. It takes the same functionality of Group in Virtools.
Select an object, You can find `Virtools Group` panel in `Object Properties` panel.
Click Add or Delete icon to group or ungroup for object.
Double click item in list to rename it.
After click Add button, you can choose Predefined option, and select a name from all legal Ballance used group names.
Or, choose Custom option and write your own group name.
### Virtools Material
Plugin add a new property for each Blender materials, called Virtools Material. It create a bridge between Virtools Material and Blender Material.
Navigate to `Material Properties` panel, select a material, you can find `Virtools Material` panel.
In default, user created material will not enable Virtools Material feature. You need to click checkbox of `Virtools Material` panel to enable or disable it.
After enable Virtools Material, `Basic Parameters` section and `Advanced Parameters` section can be set. Set your material peroperties just like operating in Virtools.
Just like its name, `Basic Parameters` is basic material properties. `Advanced Parameters` is mainly related to transparent properties and usually used in the bottom of transparent column.
Additionally, `Basic Parameters` section provide a preset function, allowing user to use some preset material settings, which only affect 4 basic colors, just for convenient using.
In `Operation` section, `Apply Virtools Material` will clean all existed Blender material and create a new material graph according to Virtools material properties.
And, `Parse from Blender Principled BSDF` will try parsing a Principled BSDF to Virtools material.
If your material highly rely on Blender material, please execute `Parse from Blender Principled BSDF` or disable Virtools Material feature before exporting BM file, otherwise material can not be saved correctly.
### Select by Virtools Group
Plugin add a selection function according to Virtools Group in Select menu.
This function firstly have 5 different selection strategies which is exactly matched with Blender selection method. Just use it like Blender selection (Set, Extend, Subtract, Invert, Intersect).
Then, select your group name to start a selection.
If you can, using Subtract or Intersect modes would be better than other modes. Because these modes avoid analyzing too many objects.
For example, first, select a rough range, and then use the Intersect mode to filter objects, which is more efficient than directly using the Start mode to select.
### Quick Grouping
Plugin add quick grouping menu in 2 places.
You can select various objects, right click and find quick grouping menu in Object Context menu.
Also you can pick objects in Outline View and right click them, find quick grouping menu in Object menu.
#### Group into
Group selected objects into your specified group.
#### Ungroup from
Ungroup selected objects from your specified group.
#### Clear Grouping
Clean the grouping infomation for selected objects.
### Auto Grouping & Rename
In Outline View, you can find auto grouping and rename menu via right click any collection.
This plugin now support 2 name standard.
First one has been introduced in Technical Infomation chapter. In plugin, its name is `YYC Tools Chains`.
The second one is used by [Imengyu/Ballance](https://github.com/imengyu/Ballance). In plugin, its name is `Imengyu Ballance`.
All functions within this menu will only output a summary when finishing. If you want to check out some objects in detail, please click `Window - Switch System Terminal`. Plugin output a detailed report in that place.
#### Rename by Group
Rename object with proper name according to its Virtools Group properties.
This usually use when migrating original map. Some Ballance derived applications do not have Group concept. They rely on name to get group infomations.
#### Convert Name
Convert name between different name standard.
Frequently used in convertion between 2 different Ballance derived applications.
#### Auto Grouping
Auto grouping according to specified name standard.
Please pay attention that previous grouping infomations will be overwritten.
If you following some mapping standard during all mapping stages, this function will auto grouping all objects for you.

View File

@ -5,38 +5,50 @@
## 简介 ## 简介
这是一个用于Blender的插件其主要是服务于Ballance制图。 这是一个用于Blender的插件其主要是服务于Ballance制图。
请选择打了tag的最新commit使用。最新的commit不能保证其是稳定可用的。
本插件囊括了Ballance制图中可能会用到的各种功能。对于一些其它插件可以提供的功能本插件不再重复提供。建议与下列插件合用以取得更好制图效果
请使用Release中打tag的最新版本最新的commit不能保证其是稳定可用的 * [BenjaminSauder/SimpleLattice](https://github.com/BenjaminSauder/SimpleLattice):快速创建晶格以便变形物体。
* [egtwobits/Mesh Align Plus](https://github.com/egtwobits/mesh_mesh_align_plus)提供远超Blender原生的对齐功能。
## 技术信息 ## 技术信息
使用的BM文件标准可以在[这里](https://github.com/yyc12345/gist/blob/master/BMFileSpec/BMSpec_ZH.md)查找 使用的BM文件标准可以在[这里](https://github.com/yyc12345/gist/blob/master/BMFileSpec/BMSpec_ZH.md)查找
使用的制图链标准以及`meshes`文件夹下的文件的格式可以在[这里](https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md)查找 使用的制图链标准以及`meshes`文件夹下的文件的格式可以在[这里](https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md)查找
`jsons`文件夹下的隶属于BMERevenge部分的文件的格式可以在[这里](https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_ZH.md)查找 `jsons`文件夹下的隶属于BMERevenge部分的文件的格式可以在[这里](https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_ZH.md)查找
支持Blender的原则是支持当前最新的 **LTS** 版本在最新的LTS版本释出之后会花一些时间迁移插件。当前插件基于2.83.x版本 支持Blender的原则是支持当前最新的 **LTS** 版本在最新的LTS版本释出之后会花一些时间迁移插件。当前插件基于**3.3.x**版本
## 安装
`ballance_blender_plugin`直接复制到Blender插件目录`scripts/addons_contrib`内即可。然后在Blender偏好设置中启用即可请在第一次安装后或更新插件后配置插件设置
## 功能介绍 ## 功能介绍
### 插件设置 ### 插件设置
* External texture folder请填写为Ballance的Texture目录插件将从此目录下调用外置贴图文件即Ballance原本带有的贴图文件 * External texture folder请填写为Ballance的`Texture`目录插件将从此目录下调用外置贴图文件即Ballance原本带有的贴图文件
* No component collection处于此集合中的物体将被强制指定为非Component。如果留空则表示不需要这个功能。 * No component collection处于此集合中的物体将被强制指定为非Component。如果留空则表示不需要这个功能。此功能通常用于机关模型强制替换。
* Temp texture folder用于缓存从BM文件中提取的贴图文件请安排一个平时不会被自动清理的目录。由于Blender会持续从这个目录读取贴图文件因此不能随意清空。并且其也不允许同名文件存在即如果我为2个地图分别导入两个BM这两个BM中存在贴图文件名相同但图像不同的两个文件那么后来的文件将会覆盖前面的文件并进而导致前者导入后的文档再次打开时出现贴图错误。关于解决这个问题的方法请参考后续的BM导入导出 * Temp texture folder用于缓存从BM文件中提取的贴图文件请安排一个平时不会被自动清理的目录。由于Blender会持续从这个目录读取贴图文件因此不能随意清空。
Temp texture folder不允许同名文件存在即如果我为2个地图分别导入两个BM这两个BM中存在贴图文件名相同但图像不同的两个文件那么后来的文件将会覆盖前面的文件并进而导致前者导入后的文档再次打开时出现贴图错误。关于这个问题的解决方案最好的方法是强制打包一次。在导入BM成功之后选择`文件-外部数据-打包资源`然后就可以安全清空Temp texture folder所在目录或导入新BM文件。如果有需要可以再点击`文件-外部数据-解包资源`,将贴图重新依赖到工程文件夹下的独立贴图库内。
### BM导入导出 ### BM导入导出
对于导入而言为了防止贴图出错最好的方法是强制打包一次。在导入BM成功之后选择全部打包到blend文件然后清空Temp texture folder所在目录然后如果有需要可以再点击解包到文件将贴图重新依赖到工程文件夹下的贴图库内。 点击`文件-导入-Ballance Map`以导入BM文件。
在导入发生名称冲突时,可以对贴图,材质,网格,物体这四种类型的数据分别决定是使用现有数据还是创建新的数据。
对于导出可以选择导出一个集合或者是一个物体Export mode并给定对象Export target即可。 点击`文件-导出-Ballance Map`以导出BM文件。
可以选择导出一个集合或者是一个物体Export mode并给定对象Export target即可。
尽管插件提供了Virtools组功能让你可以直接在Blender中归组完毕但BM导出功能仍然受限于制图链标准。因此如果不按照制图链标准进行命名那么在导出过程中则无法享受一些便利性功能例如最终导出的文件可能会过大等。
需要注意的是,一旦导出BM文件中所有的面将全部转换为三角形面请做好备份。并且建议使用平铺的集合结构,不要在集合内嵌套集合,可能会导致一些不必要的问题。 一旦导出BM文件中所有的面将全部转换为三角形面提前做好备份。
在导出时,建议使用平铺的集合结构,不要在集合内嵌套集合,因为这样可能会导致一些不必要的问题。
BM文件的后缀名是BMX表示BM的压缩。BMX与BM为同一含义。
### Ballance 3D ### Ballance 3D
Ballance 3D是一套简单的用于制图3D相关的轻型工具集合可以在3D视图上角找到 Ballance 3D是一套简单的用于制图3D相关的轻型工具集合可以在3D视图上角菜单栏中找到菜单名称为Ballance
#### 3ds Max Align #### 3ds Max Align
@ -45,20 +57,20 @@ Ballance 3D是一套简单的用于制图3D相关的轻型工具集合可以
#### Create Rail UV #### Create Rail UV
为地图中的钢轨创建UV你需要先选中需要添加类似钢轨UV的物体然后点击这个按钮以创建。 为地图中的钢轨创建UV你需要先选中需要添加类似钢轨UV的物体然后点击这个按钮以创建。
在弹出设置窗口中可以选择使用的材质。还可以选择展开模式在部分展开模式下还可以选择投影轴和缩放大小。尽管Ballance最终会为所有钢轨重新上UV一个在界面中看着赏心悦目的钢轨贴图还是比较重要的。
在弹出设置窗口中可以选择使用的材质。还可以选择展开模式对于较短的钢轨可以选择Point模式对于较长的钢轨可以使用Uniform模式如果需要手动调整缩放比请选择Scale模式并指定比率不推荐 如果您需要在Blender中呈现游戏内钢轨的贴图效果表现为所谓的平滑贴图您可以选择`TT_ReflectionMapping`展开模式。此功能由逆向游戏所用函数得来。这在渲染地图宣传画时可能会很有用
还可以选择投影轴以获取更好的UV分布。
#### Flatten UV #### Flatten UV
在物体编辑模式下用于将当前选中面按某一边贴附到V轴上的模式展开到UV上。注意,只支持凸边面。 在物体编辑模式下用于将当前选中面按某一边贴附到V轴上的模式展开到UV上。
此功能只支持凸多边形面,对于凹多边形面会有未定义行为。
编辑模式下选中面点击Flatten UV然后滚动滑条选中一个边作为参考,如果最后生成的边贴附不对,比如把路面花纹贴到了下部,可以重新选择参考边再进行操作,直到正确为止 编辑模式下选中面点击Flatten UV然后选中一个边作为参考。
如果最后生成的边贴附不对,比如把路面花纹贴到了下部,可以重新选择参考边再进行操作,直到正确为止。
### 添加菜单 ### 快速添加结构
在添加菜单中我们添加了一较为常用的物体。添加后物体会移动到3D游标处。 在添加菜单中我们添加了一系列较为常用的物体。添加后物体会移动到3D游标处。
#### Elements #### Elements
@ -66,15 +78,95 @@ Ballance 3D是一套简单的用于制图3D相关的轻型工具集合可以
#### Rail section #### Rail section
添加钢轨截面,可以选择单轨还是双轨(只是决定添加的界面数量,并不会帮你旋转角度),以及轨道半径和轨道间距 添加钢轨截面,可以选择单轨还是双轨(只是决定添加的界面数量,并不会帮你旋转角度),以及轨道半径和轨道间距(默认值就是标准数据)。
#### Floors #### Floors
添加路面隶属于BMERevenge工程的拓展。Basic floor是基本的路面组件而Derived floor则是由基本组件组成的常用组件。可以根据其属性设置其延展以及各边是否显示。其还具有减少顶点的优点。 一个非常强大的添加路面功能隶属于BMERevenge工程的拓展。
菜单中的Basic floor是基本的路面组件而Derived floor则是由基本组件组成的常用组件。通常而言大部分需要的路面都在Derived floor中。
在选择一个路面后可以根据其本身属性设置最多2个延展方向的数值。此外还可以控制侧面和底面是否生成。
与Ballance Map Editor相比还具有减少大量无用顶点的优势。
建议添加后除非有消除面的需求外,应该立即按距离合并顶点一次以避免各类问题 可添加的路面大致分为平路面,凹路面,宽路面以及各类平台。
此外还有变球器底座,平凹转换路面可供添加。
## 安装 建议添加后除有特殊需求外,应该立即按距离合并顶点一次以避免各类问题。
`ballance_blender_plugin`直接复制到Blender插件目录`scripts/addons_contrib`内即可。然后在Blender偏好设置中启用即可记得配置插件设置 ### Virtools组
插件为每一个Blender物体添加了新的属性被称为Virtools Group。与Virtools中的组具有相同的功能。
选择一个物体,在`物体属性`面板可以找到`Virtools Group`面板。
可以点击添加与删除图标,为物体归组和取消归组。
亦可在列表中双击修改组名。
在点击添加按钮后可以选择预定义然后从所有合法的Ballance组名中选择一个添加。
或选择自定义,然后输入你想要的组名添加。
### Virtools材质
插件为每一个Blender材质添加了新的属性被称为Virtools Material。它在Virtools材质与Blender材质之间架起沟通的桥梁。
转到`材质属性`面板,选择一个材质,即可以找到`Virtools Material`面板。
默认情况下由用户创建的材质不启用Virtools Material您可以通过点击`Virtools Material`面板的复选框来启用或关闭它。
在启用Virtools Material后可以在`Basic Parameters``Advanced Parameters`中设置材质属性就像在Virtools中操作一般。
`Basic Parameters`是基础材质属性。`Advanced Parameters`则是与透明相关的材质属性,主要用于半透明柱子底部等。
另外,`Basic Parameters`部分提供了预设功能允许用户使用一些预设的材质设置这些设置只影响4种基本颜色方便使用。
`Operation`中的`Apply Virtools Material`将把Virtools Material应用到Blender材质上。
`Parse from Blender Principled BSDF`将尝试将一个原理化BSDF转换为Virtools材质数据。
如果您是从Blender材质编辑的请务必对此材质在导出前执行`Parse from Blender Principled BSDF`或关闭Virtools Material功能否则材质将无法正确保存。
### 按组选择
选择菜单中新增了一项按照Virtools归组数据进行筛选的功能。
该功能首先有5种不同的选择策略与Blender的选择方法完全匹配开始、扩选、相减、反转、相交。只需像Blender选择那样使用它。
然后,选择你需要的组的名称,然后开始一次选择或筛选。
如果可以,请尽可能使用相减或相交模式。因为这样可以避免分析过多的物体。
例如先选定一个大致的范围,然后使用相交模式过滤,比直接使用开始模式效率更高。
### 快速归组
插件在2个地方添加了为物体快速归组的功能。
可以选择一系列物体,然后右键,在物体上下文菜单中找到快速归组功能。
也可以在大纲窗口中,右键选择的物体,找到快速归组功能。
#### Group into
把选择物体归入你选择的组。
#### Ungroup from
把选择物体从你选择的组中取消归组。
#### Clear Grouping
清空选择物体的所有归组信息。
### 自动归组与重命名
在大纲视图中,对任意集合右键,可以得到自动归组与重命名菜单。
本插件目前支持两种命名标准。
其一为技术信息章节已经阐述的制图链标准,在本插件中的名称为`YYC Tools Chains`
其二为[Imengyu/Ballance](https://github.com/imengyu/Ballance)所用命名标准,在本插件中的名称为`Imengyu Ballance`
这些功能最终只会展示成功与否的一个概括性消息。如果您需要详细查看某个物体为什么不能转换,请点击`窗口-切换系统控制台`,插件在那里有更详细的输出。
#### Rename by Group
根据当前物体的归组信息,为其重命名为合适的名称。
这通常用在迁移原版地图的过程中。一些Ballance衍生程序没有Virtools组概念因此需要依赖名称来取得归组信息。
#### Convert Name
在不同命名标准之间切换。
通常用于在不同Ballance衍生程序中进行转换。
#### Auto Grouping
根据给定的命名标准,为物体自动填充归组信息。
需要注意的是,原有的归组信息会被覆盖。
在制图过程中,如果你遵守了某些命名标准,则此功能可以为你自动完成归组功能。

View File

@ -2,7 +2,7 @@ import bpy,bmesh,bpy_extras,mathutils
import pathlib,zipfile,time,os,tempfile,math import pathlib,zipfile,time,os,tempfile,math
import struct, shutil import struct, shutil
from bpy_extras import io_utils, node_shader_utils from bpy_extras import io_utils, node_shader_utils
from . import UTILS_constants, UTILS_functions, UTILS_file_io, UTILS_zip_helper, UTILS_virtools_prop from . import UTILS_constants, UTILS_functions, UTILS_file_io, UTILS_zip_helper, UTILS_virtools_prop, UTILS_icons_manager
class BALLANCE_OT_export_bm(bpy.types.Operator, bpy_extras.io_utils.ExportHelper): class BALLANCE_OT_export_bm(bpy.types.Operator, bpy_extras.io_utils.ExportHelper):
"""Save a Ballance Map File (BM file spec 1.4)""" """Save a Ballance Map File (BM file spec 1.4)"""
@ -26,9 +26,15 @@ class BALLANCE_OT_export_bm(bpy.types.Operator, bpy_extras.io_utils.ExportHelper
) )
def execute(self, context): def execute(self, context):
# detect edit mode
in_edit_mode = False
if bpy.context.object and bpy.context.object.mode == "EDIT":
in_edit_mode = True
bpy.ops.object.editmode_toggle()
if ((self.export_mode == 'COLLECTION' and context.scene.BallanceBlenderPluginProperty.collection_picker is None) or if ((self.export_mode == 'COLLECTION' and context.scene.BallanceBlenderPluginProperty.collection_picker is None) or
(self.export_mode == 'OBJECT' and context.scene.BallanceBlenderPluginProperty.object_picker is None)): (self.export_mode == 'OBJECT' and context.scene.BallanceBlenderPluginProperty.object_picker is None)):
UTILS_functions.show_message_box(("No specific target", ), "Lost parameter", 'ERROR') UTILS_functions.show_message_box(("No specific target", ), "Lost parameter", UTILS_icons_manager.blender_error_icon)
else: else:
prefs = bpy.context.preferences.addons[__package__].preferences prefs = bpy.context.preferences.addons[__package__].preferences
@ -40,11 +46,17 @@ class BALLANCE_OT_export_bm(bpy.types.Operator, bpy_extras.io_utils.ExportHelper
export_bm(context, self.filepath, export_bm(context, self.filepath,
prefs.no_component_collection, prefs.no_component_collection,
self.export_mode, context.scene.BallanceBlenderPluginProperty.object_picker) self.export_mode, context.scene.BallanceBlenderPluginProperty.object_picker)
# restore edit mode
if in_edit_mode:
bpy.ops.object.editmode_toggle()
self.report({'INFO'}, "BM File Export Finished.")
return {'FINISHED'} return {'FINISHED'}
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.prop(self, "export_mode") layout.prop(self, "export_mode", expand=True)
if self.export_mode == 'COLLECTION': if self.export_mode == 'COLLECTION':
layout.prop(context.scene.BallanceBlenderPluginProperty, "collection_picker") layout.prop(context.scene.BallanceBlenderPluginProperty, "collection_picker")
elif self.export_mode == 'OBJECT': elif self.export_mode == 'OBJECT':

View File

@ -4,7 +4,7 @@ import struct, shutil
from bpy_extras import io_utils,node_shader_utils from bpy_extras import io_utils,node_shader_utils
from bpy_extras.io_utils import unpack_list from bpy_extras.io_utils import unpack_list
from bpy_extras.image_utils import load_image from bpy_extras.image_utils import load_image
from . import UTILS_constants, UTILS_functions, UTILS_file_io, UTILS_zip_helper, UTILS_virtools_prop from . import UTILS_constants, UTILS_functions, UTILS_file_io, UTILS_zip_helper, UTILS_virtools_prop, UTILS_icons_manager
class BALLANCE_OT_import_bm(bpy.types.Operator, bpy_extras.io_utils.ImportHelper): class BALLANCE_OT_import_bm(bpy.types.Operator, bpy_extras.io_utils.ImportHelper):
"""Load a Ballance Map File (BM file spec 1.4)""" """Load a Ballance Map File (BM file spec 1.4)"""
@ -63,6 +63,8 @@ class BALLANCE_OT_import_bm(bpy.types.Operator, bpy_extras.io_utils.ImportHelper
prefs.no_component_collection, prefs.external_folder, prefs.temp_texture_folder, prefs.no_component_collection, prefs.external_folder, prefs.temp_texture_folder,
self.texture_conflict_strategy, self.material_conflict_strategy, self.texture_conflict_strategy, self.material_conflict_strategy,
self.mesh_conflict_strategy, self.object_conflict_strategy) self.mesh_conflict_strategy, self.object_conflict_strategy)
self.report({'INFO'}, "BM File Import Finished.")
return {'FINISHED'} return {'FINISHED'}
@ -89,7 +91,7 @@ def import_bm(context, bmx_filepath, prefs_fncg, prefs_externalTexture, prefs_te
# clean temp folder, output error # clean temp folder, output error
UTILS_functions.show_message_box( UTILS_functions.show_message_box(
("Unsupported BM spec. Expect: {} Gotten: {}".format(UTILS_constants.bmfile_currentVersion, index_gottenVersion), ), ("Unsupported BM spec. Expect: {} Gotten: {}".format(UTILS_constants.bmfile_currentVersion, index_gottenVersion), ),
"Unsupported BM spec", 'ERROR') "Unsupported BM spec", UTILS_icons_manager.blender_error_icon)
findex.close() findex.close()
utils_tempFolderObj.cleanup() utils_tempFolderObj.cleanup()
return return

View File

@ -5,7 +5,7 @@ class BALLANCE_OT_super_align(bpy.types.Operator):
"""Align object with 3ds Max style""" """Align object with 3ds Max style"""
bl_idname = "ballance.super_align" bl_idname = "ballance.super_align"
bl_label = "3ds Max Align" bl_label = "3ds Max Align"
bl_options = {'UNDO'} bl_options = {'REGISTER', 'UNDO'}
align_x: bpy.props.BoolProperty(name="X position") align_x: bpy.props.BoolProperty(name="X position")
align_y: bpy.props.BoolProperty(name="Y position") align_y: bpy.props.BoolProperty(name="Y position")
@ -18,6 +18,7 @@ class BALLANCE_OT_super_align(bpy.types.Operator):
('POINT', "Center (axis)", ""), ('POINT', "Center (axis)", ""),
('MAX', "Max", "") ('MAX', "Max", "")
), ),
default='POINT',
) )
target_references: bpy.props.EnumProperty( target_references: bpy.props.EnumProperty(
@ -27,6 +28,7 @@ class BALLANCE_OT_super_align(bpy.types.Operator):
('POINT', "Center (axis)", ""), ('POINT', "Center (axis)", ""),
('MAX', "Max", "") ('MAX', "Max", "")
), ),
default='POINT',
) )
@classmethod @classmethod
@ -37,9 +39,11 @@ class BALLANCE_OT_super_align(bpy.types.Operator):
_align_object(self.align_x, self.align_y, self.align_z, self.current_references, self.target_references) _align_object(self.align_x, self.align_y, self.align_z, self.current_references, self.target_references)
return {'FINISHED'} return {'FINISHED'}
"""
def invoke(self, context, event): def invoke(self, context, event):
wm = context.window_manager wm = context.window_manager
return wm.invoke_props_dialog(self) return wm.invoke_props_dialog(self)
"""
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout

View File

@ -1,12 +1,26 @@
import bpy,mathutils import bpy,mathutils
import bmesh import bmesh
import math
from . import UTILS_functions from . import UTILS_functions
class BALLANCE_OT_flatten_uv(bpy.types.Operator): class BALLANCE_OT_flatten_uv(bpy.types.Operator):
"""Flatten selected face UV. Only works for convex face""" """Flatten selected face UV. Only works for convex face"""
bl_idname = "ballance.flatten_uv" bl_idname = "ballance.flatten_uv"
bl_label = "Flatten UV" bl_label = "Flatten UV"
bl_options = {'UNDO'} bl_options = {'REGISTER', 'UNDO'}
normal_scale_correction = 5.0
sink_scale_correction = 5.0 * (math.sqrt(2.5 ** 2 + 0.7 ** 2) / 2.5)
scale_correction: bpy.props.EnumProperty(
name="Scale Correction",
description="Choose your UV scale.",
items=(
("NORMAL", "Normal Floor", "Normal floor scale, 5.0"),
("SINK", "Sink Floor", "Sink floor scale, around 5.19")
),
default='NORMAL',
)
reference_edge : bpy.props.IntProperty( reference_edge : bpy.props.IntProperty(
name="Reference edge", name="Reference edge",
@ -20,7 +34,7 @@ class BALLANCE_OT_flatten_uv(bpy.types.Operator):
@classmethod @classmethod
def poll(self, context): def poll(self, context):
obj = bpy.context.active_object obj = bpy.context.active_object
if obj == None: if obj is None:
return False return False
if obj.type != 'MESH': if obj.type != 'MESH':
return False return False
@ -28,24 +42,26 @@ class BALLANCE_OT_flatten_uv(bpy.types.Operator):
return False return False
return True return True
def invoke(self, context, event): def get_scale_correction(self):
wm = context.window_manager if self.scale_correction == 'NORMAL':
return wm.invoke_props_dialog(self) return BALLANCE_OT_flatten_uv.normal_scale_correction
elif self.scale_correction == 'SINK':
return BALLANCE_OT_flatten_uv.sink_scale_correction
else:
raise Exception("Unknow scale correction.")
def execute(self, context): def execute(self, context):
no_processed_count = _real_flatten_uv(bpy.context.active_object.data, self.reference_edge) no_processed_count = _real_flatten_uv(bpy.context.active_object.data, self.reference_edge, self.get_scale_correction())
if no_processed_count != 0: if no_processed_count != 0:
UTILS_functions.show_message_box( print("[Flatten UV] {} faces may not be processed correctly because they have problem.".format(no_processed_count))
("{} faces may not be processed correctly because they have problem.".format(no_processed_count), ),
"Warning", 'ERROR'
)
return {'FINISHED'} return {'FINISHED'}
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.prop(self, "scale_correction")
layout.prop(self, "reference_edge") layout.prop(self, "reference_edge")
def _real_flatten_uv(mesh, reference_edge): def _real_flatten_uv(mesh, reference_edge, scale_correction):
no_processed_count = 0 no_processed_count = 0
if mesh.uv_layers.active is None: if mesh.uv_layers.active is None:
@ -58,13 +74,26 @@ def _real_flatten_uv(mesh, reference_edge):
if not face.select: if not face.select:
continue continue
# check whether ref edge is legal
allPoint = len(face.loops) allPoint = len(face.loops)
if allPoint <= reference_edge: if allPoint <= reference_edge:
no_processed_count+=1 no_processed_count+=1
continue continue
# get correct new corrdinate system # 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 exceptio
# just a weird uv. user will notice this problem.
# get point
p1Relative = reference_edge p1Relative = reference_edge
p2Relative = reference_edge + 1 p2Relative = reference_edge + 1
p3Relative = reference_edge + 2 p3Relative = reference_edge + 2
@ -77,13 +106,19 @@ def _real_flatten_uv(mesh, reference_edge):
p2=mathutils.Vector(tuple(face.loops[p2Relative].vert.co[x] for x in range(3))) p2=mathutils.Vector(tuple(face.loops[p2Relative].vert.co[x] for x in range(3)))
p3=mathutils.Vector(tuple(face.loops[p3Relative].vert.co[x] for x in range(3))) p3=mathutils.Vector(tuple(face.loops[p3Relative].vert.co[x] for x in range(3)))
# get y axis
new_y_axis = p2 - p1 new_y_axis = p2 - p1
new_y_axis.normalize() new_y_axis.normalize()
vec1 = p3 - p2 vec1 = p3 - p2
vec1.normalize() vec1.normalize()
# get z axis
new_z_axis = new_y_axis.cross(vec1) new_z_axis = new_y_axis.cross(vec1)
new_z_axis.normalize() new_z_axis.normalize()
if not any(round(v, 7) for v in new_z_axis):
new_z_axis = face.normal.normalized()
# get x axis
new_x_axis = new_y_axis.cross(new_z_axis) new_x_axis = new_y_axis.cross(new_z_axis)
new_x_axis.normalize() new_x_axis.normalize()
@ -93,14 +128,14 @@ def _real_flatten_uv(mesh, reference_edge):
(0, 1.0, 0), (0, 1.0, 0),
(0, 0, 1.0) (0, 0, 1.0)
)) ))
origin_base.invert() origin_base.invert_safe()
new_base = mathutils.Matrix(( new_base = mathutils.Matrix((
(new_x_axis.x, new_y_axis.x, new_z_axis.x), (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.y, new_y_axis.y, new_z_axis.y),
(new_x_axis.z, new_y_axis.z, new_z_axis.z) (new_x_axis.z, new_y_axis.z, new_z_axis.z)
)) ))
transition_matrix = origin_base @ new_base transition_matrix = origin_base @ new_base
transition_matrix.invert() transition_matrix.invert_safe()
# process each face # process each face
for loop_index in range(allPoint): for loop_index in range(allPoint):
@ -108,9 +143,11 @@ def _real_flatten_uv(mesh, reference_edge):
vec = pp-p1 vec = pp-p1
new_vec = transition_matrix @ vec new_vec = transition_matrix @ vec
# y axis always use 5.0 to scale
# however, x need use custom scale correction.
face.loops[loop_index][uv_lay].uv = ( face.loops[loop_index][uv_lay].uv = (
(new_vec.x if new_vec.x >=0 else -new_vec.x) / 5, (new_vec.x if new_vec.x >=0 else -new_vec.x) / scale_correction,
(new_vec.y) / 5 (new_vec.y) / 5.0
) )
# Show the updates in the viewport # Show the updates in the viewport

View File

@ -1,7 +1,7 @@
import bpy,bmesh import bpy,bmesh
import mathutils import mathutils
import bpy.types import bpy.types
from . import UTILS_functions from . import UTILS_functions, UTILS_icons_manager
class BALLANCE_OT_rail_uv(bpy.types.Operator): class BALLANCE_OT_rail_uv(bpy.types.Operator):
"""Create a UV for rail""" """Create a UV for rail"""
@ -47,7 +47,7 @@ class BALLANCE_OT_rail_uv(bpy.types.Operator):
def execute(self, context): def execute(self, context):
if context.scene.BallanceBlenderPluginProperty.material_picker == None: if context.scene.BallanceBlenderPluginProperty.material_picker == None:
UTILS_functions.show_message_box(("No specific material", ), "Lost parameter", 'ERROR') UTILS_functions.show_message_box(("No specific material", ), "Lost parameter", UTILS_icons_manager.blender_error_icon)
else: else:
_create_rail_uv(self.uv_type, context.scene.BallanceBlenderPluginProperty.material_picker, self.uv_scale, self.projection_axis) _create_rail_uv(self.uv_type, context.scene.BallanceBlenderPluginProperty.material_picker, self.uv_scale, self.projection_axis)
return {'FINISHED'} return {'FINISHED'}
@ -175,7 +175,7 @@ def _create_rail_uv(rail_type, material_pointer, scale_size, projection_axis):
if len(ignoredObj) != 0: if len(ignoredObj) != 0:
UTILS_functions.show_message_box( UTILS_functions.show_message_box(
("Following objects are not processed due to they are not suit for this function now: ", ) + tuple(ignoredObj), ("Following objects are not processed due to they are not suit for this function now: ", ) + tuple(ignoredObj),
"Execution result", 'INFO' "Execution result", UTILS_icons_manager.blender_info_icon
) )
def _tt_reflection_mapping_compute(_point, _n, _refobj): def _tt_reflection_mapping_compute(_point, _n, _refobj):

View File

@ -1,5 +1,5 @@
import bpy import bpy
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop, UTILS_icons_manager
class rename_system_props(bpy.types.Operator): class rename_system_props(bpy.types.Operator):
name_standard: bpy.props.EnumProperty( name_standard: bpy.props.EnumProperty(
@ -69,6 +69,52 @@ class BALLANCE_OT_auto_grouping(rename_system_props):
# ========================================== # ==========================================
# rename misc funcs # rename misc funcs
class _RenameErrorType():
ERROR = 0
WARNING = 1
INFO = 2
@staticmethod
def cvt_err_from_int_to_str(err_t):
if err_t == _RenameErrorType.ERROR:
return "ERROR"
elif err_t == _RenameErrorType.WARNING:
return "WARNING"
elif err_t == _RenameErrorType.INFO:
return "INFO"
else:
raise Exception("Unknow error type.")
class _RenameErrorItem():
def __init__(self, err_t, description):
self.err_type = err_t
self.description = description
def get_presentation(self):
return "[{}]\t{}".format(_RenameErrorType.cvt_err_from_int_to_str(self.err_type), self.description)
class _RenameErrorReporter():
def __init__(self):
self.err_container: list[_RenameErrorItem] = []
def add_error(self, description):
self.err_container.append(_RenameErrorItem(_RenameErrorType.ERROR, description))
def add_warning(self, description):
self.err_container.append(_RenameErrorItem(_RenameErrorType.WARNING, description))
def add_info(self, description):
self.err_container.append(_RenameErrorItem(_RenameErrorType.INFO, description))
def can_report(self):
return len(self.err_container) != 0
def report(self, header):
print(header)
for i in self.err_container:
print('\t' + i.get_presentation())
def clear(self):
self.err_container.clear()
class _ObjectBasicType(): class _ObjectBasicType():
COMPONENT = 0 COMPONENT = 0
@ -140,12 +186,12 @@ def _get_sector_from_ckgroup(group_set):
# YYC Tools Chains name standard is Ballance-compatible name standard. # YYC Tools Chains name standard is Ballance-compatible name standard.
# So this functions also serving for `_get_name_info_from_group` function # So this functions also serving for `_get_name_info_from_group` function
# to help get sector field from PC/PR elements. In ordinary call(external call) # to help get sector field from PC/PR elements. In ordinary call(external call)
# The final error output should be outputed nromally. But in the call from # The final error output should be outputed normally. But in the call from
# `_get_name_info_from_group`, this function should not output any error. # `_get_name_info_from_group`, this function should not output any error.
# So parameter `call_internal` is served for this work. In common it is False # So parameter `call_internal` is served for this work. In common it is False
# to let function output error str normally. But only set it to True in # to let function output error str normally. But only set it to True in
# the call from `_get_name_info_from_group` to disable error output. # the call from `_get_name_info_from_group` to disable error output.
def _get_name_info_from_yyc_name(obj_name, call_internal = False): def _get_name_info_from_yyc_name(obj_name, err_reporter: _RenameErrorReporter, call_internal = False):
# check component first # check component first
regex_result = UTILS_constants.rename_regexYYCComponent.match(obj_name) regex_result = UTILS_constants.rename_regexYYCComponent.match(obj_name)
@ -191,11 +237,11 @@ def _get_name_info_from_yyc_name(obj_name, call_internal = False):
# only output in external calling # only output in external calling
if not call_internal: if not call_internal:
print("[ERROR]\t{}:\tName match lost.".format(obj_name)) err_reporter.add_error("Name match lost.")
return None return None
def _get_name_info_from_imengyu_name(obj_name): def _get_name_info_from_imengyu_name(obj_name, err_reporter: _RenameErrorReporter):
# check component first # check component first
regex_result = UTILS_constants.rename_regexImengyuComponent.match(obj_name) regex_result = UTILS_constants.rename_regexImengyuComponent.match(obj_name)
@ -238,10 +284,10 @@ def _get_name_info_from_imengyu_name(obj_name):
if obj_name.startswith("O_"): if obj_name.startswith("O_"):
return _NameInfoHelper(_ObjectBasicType.DECORATION) return _NameInfoHelper(_ObjectBasicType.DECORATION)
print("[ERROR]\t{}:\tName match lost.".format(obj_name)) err_reporter.add_error("Name match lost.")
return None return None
def _get_name_info_from_group(obj): def _get_name_info_from_group(obj, err_reporter: _RenameErrorReporter):
group_list = UTILS_virtools_prop.get_virtools_group_data(obj) group_list = UTILS_virtools_prop.get_virtools_group_data(obj)
if len(group_list) == 0: if len(group_list) == 0:
# name it as a decoration # name it as a decoration
@ -262,24 +308,24 @@ def _get_name_info_from_group(obj):
# these type's data should be gotten from its name # these type's data should be gotten from its name
# use _get_name_info_from_yyc_name to get it # use _get_name_info_from_yyc_name to get it
# _get_name_info_from_yyc_name is Ballance-compatible name standard # _get_name_info_from_yyc_name is Ballance-compatible name standard
data = _get_name_info_from_yyc_name(obj.name, call_internal=True) data = _get_name_info_from_yyc_name(obj.name, err_reporter, call_internal=True)
if data is None: if data is None:
print("[ERROR]\t{}:\tPC_Checkpoints or PR_Resetpoints detected. But couldn't get sector from name.".format(obj.name)) err_reporter.add_error("PC_Checkpoints or PR_Resetpoints detected. But couldn't get sector from name.")
return None return None
if data.basic_type != _ObjectBasicType.CHECKPOINT and data.basic_type != _ObjectBasicType.RESETPOINT: if data.basic_type != _ObjectBasicType.CHECKPOINT and data.basic_type != _ObjectBasicType.RESETPOINT:
# check whether it is checkpoint or resetpoint # check whether it is checkpoint or resetpoint
# if not, it mean that we got error data from name # if not, it mean that we got error data from name
# return None instead # return None instead
print("[ERROR]\t{}:\tPC_Checkpoints or PR_Resetpoints detected. But name is illegal.".format(obj.name)) err_reporter.add_error("PC_Checkpoints or PR_Resetpoints detected. But name is illegal.")
return None return None
# otherwise return data # otherwise return data
return data return data
else: else:
print("[ERROR]\t{}:\tThe match of Unique Component lost.".format(obj.name)) err_reporter.add_error("The match of Unique Component lost.")
return None return None
elif len(set_result) != 0: elif len(set_result) != 0:
# must be a weird grouping, report it # must be a weird grouping, report it
print("[ERROR]\t{}:\tA Multi-grouping Unique Component.".format(obj.name)) err_reporter.add_error("A Multi-grouping Unique Component.")
return None return None
# distinguish normal elements # distinguish normal elements
@ -291,7 +337,7 @@ def _get_name_info_from_group(obj):
gotten_sector = _get_sector_from_ckgroup(group_set) gotten_sector = _get_sector_from_ckgroup(group_set)
if gotten_sector is None: if gotten_sector is None:
# fail to get sector # fail to get sector
print("[ERROR]\t{}:\tComponent detected. But couldn't get sector from CKGroup data.".format(obj.name)) err_reporter.add_error("Component detected. But couldn't get sector from CKGroup data.")
return None return None
data = _NameInfoHelper(_ObjectBasicType.COMPONENT) data = _NameInfoHelper(_ObjectBasicType.COMPONENT)
@ -300,7 +346,7 @@ def _get_name_info_from_group(obj):
return data return data
elif len(set_result) != 0: elif len(set_result) != 0:
# must be a weird grouping, report it # must be a weird grouping, report it
print("[ERROR]\t{}:\tA Multi-grouping Component.".format(obj.name)) err_reporter.add_error("A Multi-grouping Component.")
return None return None
# distinguish road # distinguish road
@ -316,7 +362,7 @@ def _get_name_info_from_group(obj):
elif len(floor_result) == 0 and len(rail_result) > 0: elif len(floor_result) == 0 and len(rail_result) > 0:
return _NameInfoHelper(_ObjectBasicType.WOOD) return _NameInfoHelper(_ObjectBasicType.WOOD)
else: else:
print("[WARNING]\t{}:\tCan't distinguish between Floors and Rails. Suppose it is Floors".format(obj.name)) err_reporter.add_warning("Can't distinguish object between Floors and Rails. Suppose it is Floors.")
return _NameInfoHelper(_ObjectBasicType.FLOOR) return _NameInfoHelper(_ObjectBasicType.FLOOR)
elif 'Phys_FloorStopper' in group_set: elif 'Phys_FloorStopper' in group_set:
return _NameInfoHelper(_ObjectBasicType.STOPPER) return _NameInfoHelper(_ObjectBasicType.STOPPER)
@ -324,10 +370,10 @@ def _get_name_info_from_group(obj):
return _NameInfoHelper(_ObjectBasicType.DEPTH_CUBE) return _NameInfoHelper(_ObjectBasicType.DEPTH_CUBE)
# no matched # no matched
print("[ERROR]\t{}:\tGroup match lost.".format(obj.name)) err_reporter.add_error("Group match lost.")
return None return None
def _set_for_yyc_name(obj, name_info): def _set_for_yyc_name(obj, name_info, err_reporter: _RenameErrorReporter):
basic_type = name_info.basic_type basic_type = name_info.basic_type
if basic_type == _ObjectBasicType.DECORATION: if basic_type == _ObjectBasicType.DECORATION:
obj.name = "D_" obj.name = "D_"
@ -357,7 +403,7 @@ def _set_for_yyc_name(obj, name_info):
obj.name = "{}_{:0>2d}_".format(name_info.component_type, name_info.sector) obj.name = "{}_{:0>2d}_".format(name_info.component_type, name_info.sector)
def _set_for_imengyu_name(obj, name_info): def _set_for_imengyu_name(obj, name_info, err_reporter: _RenameErrorReporter):
basic_type = name_info.basic_type basic_type = name_info.basic_type
if basic_type == _ObjectBasicType.DECORATION: if basic_type == _ObjectBasicType.DECORATION:
obj.name = "O_" obj.name = "O_"
@ -388,7 +434,7 @@ def _set_for_imengyu_name(obj, name_info):
# NOTE: the implement of this function are copied from # NOTE: the implement of this function are copied from
# BallanceVirtoolsHelper/bvh/features/mapping/grouping.cpp # BallanceVirtoolsHelper/bvh/features/mapping/grouping.cpp
def _set_for_group(obj, name_info): def _set_for_group(obj, name_info, err_reporter: _RenameErrorReporter):
gps = [] gps = []
basic_type = name_info.basic_type basic_type = name_info.basic_type
@ -441,23 +487,23 @@ def _set_for_group(obj, name_info):
# ========================================== # ==========================================
# assemble funcs # assemble funcs
def _get_data(obj, standard): def _get_data(obj, standard, err_reporter: _RenameErrorReporter):
if standard == _NameStandard.YYC: if standard == _NameStandard.YYC:
return _get_name_info_from_yyc_name(obj.name) return _get_name_info_from_yyc_name(obj.name, err_reporter)
elif standard == _NameStandard.IMENGYU: elif standard == _NameStandard.IMENGYU:
return _get_name_info_from_imengyu_name(obj.name) return _get_name_info_from_imengyu_name(obj.name, err_reporter)
elif standard == _NameStandard.CKGROUP: elif standard == _NameStandard.CKGROUP:
return _get_name_info_from_group(obj) return _get_name_info_from_group(obj, err_reporter)
else: else:
raise Exception("Unknow standard") raise Exception("Unknow standard")
def _set_data(obj, name_info, standard): def _set_data(obj, name_info, standard, err_reporter: _RenameErrorReporter):
if standard == _NameStandard.YYC: if standard == _NameStandard.YYC:
return _set_for_yyc_name(obj, name_info) return _set_for_yyc_name(obj, name_info, err_reporter)
elif standard == _NameStandard.IMENGYU: elif standard == _NameStandard.IMENGYU:
return _set_for_imengyu_name(obj, name_info) return _set_for_imengyu_name(obj, name_info, err_reporter)
elif standard == _NameStandard.CKGROUP: elif standard == _NameStandard.CKGROUP:
return _set_for_group(obj, name_info) return _set_for_group(obj, name_info, err_reporter)
else: else:
raise Exception("Unknow standard") raise Exception("Unknow standard")
@ -467,23 +513,43 @@ def _rename_core(source_std, dest_std):
# we do not to do anything # we do not to do anything
return return
# create fail counter and error reporter
failed_obj_counter = 0 failed_obj_counter = 0
all_obj_counter = 0 all_obj_counter = 0
err_reporter = _RenameErrorReporter()
print('============') print('============')
print('Rename system report') print('Rename System Report')
print('------------') print('------------')
for obj in _get_selected_objects(): for obj in _get_selected_objects():
# set counter and name
all_obj_counter += 1 all_obj_counter += 1
info = _get_data(obj, source_std) old_name = new_name = obj.name
# get data
info = _get_data(obj, source_std, err_reporter)
# do operation according to whether getting data successfully
if info is None: if info is None:
failed_obj_counter += 1 failed_obj_counter += 1
continue else:
_set_data(obj, info, dest_std, err_reporter)
# refresh obj name
new_name = obj.name
# report result
if err_reporter.can_report():
if new_name == old_name:
report_header = 'For object "{}"'.format(new_name)
else:
report_header = 'For object "{}" (Old name: "{}")'.format(new_name, old_name)
err_reporter.report(report_header)
# clear report
err_reporter.clear()
_set_data(obj, info, dest_std)
print('------------') print('------------')
print('All/failed - {}/{}'.format(all_obj_counter, failed_obj_counter)) print('All / Failed - {} / {}'.format(all_obj_counter, failed_obj_counter))
print('============') print('============')
UTILS_functions.show_message_box( UTILS_functions.show_message_box(
@ -491,6 +557,5 @@ def _rename_core(source_std, dest_std):
'View console to get more detail', 'View console to get more detail',
'All: {}'.format(all_obj_counter), 'All: {}'.format(all_obj_counter),
'Failed: {}'.format(failed_obj_counter)), 'Failed: {}'.format(failed_obj_counter)),
"Info", "Info", UTILS_icons_manager.blender_error_icon
"INFO"
) )

View File

@ -1,43 +1,124 @@
import bpy, mathutils import bpy, mathutils
from . import UTILS_constants, UTILS_functions from . import UTILS_constants, UTILS_functions, UTILS_icons_manager
# ================================================= actual add # =============== Common Class ================
class common_add_component_props(bpy.types.Operator):
attentionElements = ("PC_TwoFlames", "PR_Resetpoint")
uniqueElements = ("PS_FourFlames", "PE_Balloon")
class BALLANCE_OT_add_components(bpy.types.Operator): elements_sector: bpy.props.IntProperty(
"""Add sector related elements""" name="Sector",
description="Define which sector the object will be grouped in",
min=1, max=8,
default=1,
)
def get_component_name(self, raw_comp_name):
if raw_comp_name in self.uniqueElements:
return raw_comp_name + "_01"
elif raw_comp_name in self.attentionElements:
return raw_comp_name + "_0" + str(self.elements_sector)
else:
return raw_comp_name + "_0" + str(self.elements_sector) + "_"
def parent_draw(self, parent_layout, raw_comp_name):
if raw_comp_name not in self.uniqueElements:
parent_layout.prop(self, 'elements_sector')
class BALLANCE_OT_add_components(common_add_component_props):
"""Add Elements"""
bl_idname = "ballance.add_components" bl_idname = "ballance.add_components"
bl_label = "Add elements" bl_label = "Add Elements"
bl_options = {'UNDO'} bl_options = {'UNDO'}
elements_type: bpy.props.EnumProperty( elements_type: bpy.props.EnumProperty(
name="Type", name="Type",
description="This element type", description="This element type",
items=tuple(map(lambda x: (x, x, ""), UTILS_constants.bmfile_componentList)), #items=tuple(map(lambda x: (x, x, ""), UTILS_constants.bmfile_componentList)),
) items=tuple(
# token, display name, descriptions, icon, index
attentionElements = ["PC_TwoFlames", "PR_Resetpoint"] (blk, blk, "", UTILS_icons_manager.get_element_icon(blk), idx)
uniqueElements = ["PS_FourFlames", "PE_Balloon"] for idx, blk in enumerate(UTILS_constants.bmfile_componentList)
),
elements_sector: bpy.props.IntProperty(
name="Sector",
description="Define which sector the object will be grouped in",
min=1,
max=8,
default=1,
) )
def execute(self, context): def execute(self, context):
# get name # get name
if self.elements_type in self.uniqueElements: finalObjectName = self.get_component_name(self.elements_type)
finalObjectName = self.elements_type + "_01"
elif self.elements_type in self.attentionElements:
finalObjectName = self.elements_type + "_0" + str(self.elements_sector)
else:
finalObjectName = self.elements_type + "_0" + str(self.elements_sector) + "_"
# create object # create object
loadedMesh = UTILS_functions.load_component( loadedMesh = UTILS_functions.load_component(
UTILS_constants.bmfile_componentList.index(self.elements_type)) UTILS_constants.bmfile_componentList.index(self.elements_type)
)
obj = bpy.data.objects.new(finalObjectName, loadedMesh)
UTILS_functions.add_into_scene_and_move_to_cursor(obj)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
# attension notice
if self.elements_type in self.attentionElements:
layout.label(text="NOTE: Check Sector ID carefully.")
if self.elements_type in self.uniqueElements:
layout.label(text="NOTE: This element have unique name.")
# cfg
layout.prop(self, "elements_type")
self.parent_draw(layout, self.elements_type)
@classmethod
def draw_blc_menu(self, layout):
for item in UTILS_constants.bmfile_componentList:
cop = layout.operator(
self.bl_idname, text=item,
icon_value = UTILS_icons_manager.get_element_icon(item))
cop.elements_type = item
class BALLANCE_OT_add_components_dup(common_add_component_props):
"""Add Duplicated Elements"""
bl_idname = "ballance.add_components_dup"
bl_label = "Add Duplicated Elements"
bl_options = {'UNDO'}
can_duplicated_elements = (
'P_Extra_Point', 'P_Modul_18', 'P_Modul_26'
)
elements_type: bpy.props.EnumProperty(
name="Type",
description="This element type",
#items=tuple(map(lambda x: (x, x, ""), UTILS_constants.bmfile_componentList)),
items=tuple(
# token, display name, descriptions, icon, index
(blk, blk, "", UTILS_icons_manager.get_element_icon(blk), idx)
for idx, blk in enumerate(can_duplicated_elements)
),
)
elements_dup_times: bpy.props.IntProperty(
name="Duplication Count",
description="How many this element should be duplicated.",
min=2, max=64,
soft_min=2, soft_max=32,
default=2,
)
def execute(self, context):
# get name
finalObjectName = self.get_component_name(self.elements_type)
# load mesh
loadedMesh = UTILS_functions.load_component(
UTILS_constants.bmfile_componentList.index(self.elements_type)
)
# create object
for i in range(self.elements_dup_times):
obj = bpy.data.objects.new(finalObjectName, loadedMesh) obj = bpy.data.objects.new(finalObjectName, loadedMesh)
UTILS_functions.add_into_scene_and_move_to_cursor(obj) UTILS_functions.add_into_scene_and_move_to_cursor(obj)
@ -50,7 +131,104 @@ class BALLANCE_OT_add_components(bpy.types.Operator):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.prop(self, "elements_type") layout.prop(self, "elements_type")
if self.elements_type not in self.uniqueElements: self.parent_draw(layout, self.elements_type)
layout.prop(self, "elements_sector") layout.prop(self, "elements_dup_times")
if self.elements_type in self.attentionElements:
layout.label(text="Please note that sector is suffix.") @classmethod
def draw_blc_menu(self, layout):
for item in self.can_duplicated_elements:
cop = layout.operator(
self.bl_idname, text=item,
icon_value = UTILS_icons_manager.get_element_icon(item))
cop.elements_type = item
class BALLANCE_OT_add_components_series(common_add_component_props):
"""Add Elements with a Series."""
bl_idname = "ballance.add_components_series"
bl_label = "Add Series Elements"
bl_options = {'REGISTER', 'UNDO'}
supported_series = {
# format: key: (description: str, real_component: str, unit_transition: mathutils.Vector, default_span: float)
# key will become enum property's identifier
"MODUL_41": ('Tilting Block Series', 'P_Modul_41', mathutils.Vector((1.0, 0.0, 0.0)), 6.0022),
"MODUL_18_V": ('Fan Vertical Series', 'P_Modul_18', mathutils.Vector((0.0, 0.0, 1.0)), 15),
"MODUL_18_H": ('Fan Horizonal Series', 'P_Modul_18', mathutils.Vector((1.0, 0.0, 0.0)), 30),
}
# the updator for default span
def element_type_updated(self, context):
# set span
self.elements_span = BALLANCE_OT_add_components_series.supported_series[self.elements_type][3]
# blender required
return None
elements_type: bpy.props.EnumProperty(
name="Type",
description="This element type",
#items=tuple(map(lambda x: (x, x, ""), UTILS_constants.bmfile_componentList)),
items=tuple(
# token, display name, descriptions, icon, index
(skey, sitem[0], "", UTILS_icons_manager.get_element_icon(sitem[1]), idx)
for (idx, (skey, sitem)) in enumerate(supported_series.items())
),
default=0,
update=element_type_updated
)
elements_dup_times: bpy.props.IntProperty(
name="Duplication Count",
description="How many this element should be duplicated.",
min=2, max=64,
soft_min=2, soft_max=32,
default=2,
)
elements_span: bpy.props.FloatProperty(
name="Elements Span",
description="The span between each elements.",
min=0.0,
default=0.0,
)
def invoke(self, context, event):
# force trigger span update once to treat span normally
self.element_type_updated(context)
return self.execute(context)
def execute(self, context):
# get unit span and real element name for loading mesh and creating name
(_, real_element_name, unit_span, _) = self.supported_series[self.elements_type]
# get name
finalObjectName = self.get_component_name(real_element_name)
# load mesh
loadedMesh = UTILS_functions.load_component(
UTILS_constants.bmfile_componentList.index(real_element_name)
)
# create object
for i in range(self.elements_dup_times):
obj = bpy.data.objects.new(finalObjectName, loadedMesh)
UTILS_functions.add_into_scene_and_move_to_cursor(obj)
obj.matrix_world.translation += unit_span * (self.elements_span * i)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.prop(self, "elements_type")
self.parent_draw(layout, self.elements_type)
layout.prop(self, "elements_dup_times")
layout.prop(self, "elements_span")
@classmethod
def draw_blc_menu(self, layout):
for key, item in self.supported_series.items():
cop = layout.operator(
self.bl_idname, text=item[0],
icon_value = UTILS_icons_manager.get_element_icon(item[1]))
cop.elements_type = key

View File

@ -4,18 +4,45 @@ import ast
from bpy_extras import io_utils,node_shader_utils from bpy_extras import io_utils,node_shader_utils
# from bpy_extras.io_utils import unpack_list # from bpy_extras.io_utils import unpack_list
from bpy_extras.image_utils import load_image from bpy_extras.image_utils import load_image
from . import UTILS_constants, UTILS_functions, UTILS_safe_eval from . import UTILS_constants, UTILS_functions, UTILS_safe_eval, UTILS_icons_manager
class BALLANCE_OT_add_floors(bpy.types.Operator): class BALLANCE_OT_add_floors(bpy.types.Operator):
"""Add Ballance floor""" """Add Ballance floor"""
bl_idname = "ballance.add_floors" bl_idname = "ballance.add_floors"
bl_label = "Add floor" bl_label = "Add floor"
bl_options = {'UNDO'} bl_options = {'REGISTER', 'UNDO'}
# the updator for default side value
def floor_type_updated(self, context):
# get floor prototype
floor_prototype = UTILS_constants.floor_blockDict[self.floor_type]
# try sync default value
default_sides = floor_prototype['DefaultSideConfig']
self.use_2d_top = default_sides['UseTwoDTop']
self.use_2d_right = default_sides['UseTwoDRight']
self.use_2d_bottom = default_sides['UseTwoDBottom']
self.use_2d_left = default_sides['UseTwoDLeft']
self.use_3d_top = default_sides['UseThreeDTop']
self.use_3d_bottom = default_sides['UseThreeDBottom']
# blender required
return None
floor_type: bpy.props.EnumProperty( floor_type: bpy.props.EnumProperty(
name="Type", name="Type",
description="Floor type", description="Floor type",
items=tuple((x, x, "") for x in UTILS_constants.floor_blockDict.keys()), #items=tuple(
# # token, display name, descriptions
# (blk, blk, "")
# for blk in UTILS_constants.floor_blockDict.keys()
#),
items=tuple(
# token, display name, descriptions, icon, index
(blk, blk, "", UTILS_icons_manager.get_floor_icon(blk), idx)
for idx, blk in enumerate(UTILS_constants.floor_blockDict.keys())
),
update=floor_type_updated
) )
expand_length_1 : bpy.props.IntProperty( expand_length_1 : bpy.props.IntProperty(
@ -40,26 +67,30 @@ class BALLANCE_OT_add_floors(bpy.types.Operator):
) )
use_2d_top : bpy.props.BoolProperty( use_2d_top : bpy.props.BoolProperty(
name="Top edge" name="Top edge",
default=True
) )
use_2d_right : bpy.props.BoolProperty( use_2d_right : bpy.props.BoolProperty(
name="Right edge" name="Right edge",
default=False
) )
use_2d_bottom : bpy.props.BoolProperty( use_2d_bottom : bpy.props.BoolProperty(
name="Bottom edge" name="Bottom edge",
default=True
) )
use_2d_left : bpy.props.BoolProperty( use_2d_left : bpy.props.BoolProperty(
name="Left edge" name="Left edge",
default=True
) )
use_3d_top : bpy.props.BoolProperty( use_3d_top : bpy.props.BoolProperty(
name="Top face" name="Top face",
default=True
) )
use_3d_bottom : bpy.props.BoolProperty( use_3d_bottom : bpy.props.BoolProperty(
name="Bottom face" name="Bottom face",
default=True
) )
previous_floor_type = ''
@classmethod @classmethod
def poll(self, context): def poll(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences prefs = bpy.context.preferences.addons[__package__].preferences
@ -115,25 +146,25 @@ class BALLANCE_OT_add_floors(bpy.types.Operator):
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event): def invoke(self, context, event):
wm = context.window_manager # yyc marked. Blumia reported.
return wm.invoke_props_dialog(self) # prepare settings before registing
# otherwise the mesh will not be created when first run.
# (do not change any properties)
# yyc marked again.
# now I migrate default side value setter to updator of enum property.
# nothing need to process in here now.
# trigger default side props updator
self.floor_type_updated(context)
return self.execute(context)
def draw(self, context): def draw(self, context):
# get floor prototype # get floor prototype
floor_prototype = UTILS_constants.floor_blockDict[self.floor_type] floor_prototype = UTILS_constants.floor_blockDict[self.floor_type]
# try sync default value
if self.previous_floor_type != self.floor_type:
self.previous_floor_type = self.floor_type
default_sides = floor_prototype['DefaultSideConfig']
self.use_2d_top = default_sides['UseTwoDTop']
self.use_2d_right = default_sides['UseTwoDRight']
self.use_2d_bottom = default_sides['UseTwoDBottom']
self.use_2d_left = default_sides['UseTwoDLeft']
self.use_3d_top = default_sides['UseThreeDTop']
self.use_3d_bottom = default_sides['UseThreeDBottom']
# show property # show property
layout = self.layout layout = self.layout
col = layout.column() col = layout.column()
@ -149,12 +180,13 @@ class BALLANCE_OT_add_floors(bpy.types.Operator):
col.prop(self, "expand_length_2") col.prop(self, "expand_length_2")
col.label(text="Unit size: " + floor_prototype['UnitSize']) col.label(text="Unit size: " + floor_prototype['UnitSize'])
col.label(text="Expand mode: " + floor_prototype['ExpandType']) col.label(text="Expand mode: " + floor_prototype['ExpandType'])
grids = col.grid_flow(row_major=True, columns=3) grids = col.grid_flow(row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
grids.alignment = 'CENTER'
grids.separator() grids.separator()
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][0]) grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][0])
grids.separator() grids.separator()
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][3]) grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][3])
grids.template_icon(icon_value = UTILS_constants.icons_floorDict[self.floor_type]) grids.template_icon(icon_value = UTILS_icons_manager.get_floor_icon(self.floor_type))
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][1]) grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][1])
grids.separator() grids.separator()
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][2]) grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][2])
@ -168,12 +200,13 @@ class BALLANCE_OT_add_floors(bpy.types.Operator):
col.separator() col.separator()
col.label(text="Sides") col.label(text="Sides")
grids = col.grid_flow(row_major=True, columns=3) grids = col.grid_flow(row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
grids.alignment = 'CENTER'
grids.separator() grids.separator()
grids.prop(self, "use_2d_top") grids.prop(self, "use_2d_top")
grids.separator() grids.separator()
grids.prop(self, "use_2d_left") grids.prop(self, "use_2d_left")
grids.template_icon(icon_value = UTILS_constants.icons_floorDict[self.floor_type]) grids.template_icon(icon_value = UTILS_icons_manager.get_floor_icon(self.floor_type))
grids.prop(self, "use_2d_right") grids.prop(self, "use_2d_right")
grids.separator() grids.separator()
grids.prop(self, "use_2d_bottom") grids.prop(self, "use_2d_bottom")
@ -221,7 +254,7 @@ def _create_or_get_material(material_name, prefs_externalTexture):
try_item['data']['ambient'], try_item['data']['diffuse'], try_item['data']['ambient'], try_item['data']['diffuse'],
try_item['data']['specular'], try_item['data']['emissive'], try_item['data']['specular'], try_item['data']['emissive'],
try_item['data']['power'], try_item['data']['power'],
False, False, False, False, False, False, True, False,
texture) texture)
) )
break break

View File

@ -10,55 +10,41 @@ class BALLANCE_OT_add_rails(bpy.types.Operator):
rail_type: bpy.props.EnumProperty( rail_type: bpy.props.EnumProperty(
name="Type", name="Type",
description="Rail type", description="Rail type",
items=(('MONO', "Monorail", ""), items=(
('MONO', "Monorail", ""),
('DOUBLE', "Rail", ""), ('DOUBLE', "Rail", ""),
), ),
default='DOUBLE',
) )
rail_radius: bpy.props.FloatProperty( rail_radius: bpy.props.FloatProperty(
name="Rail radius", name="Rail Radius",
description="Define rail section radius", description="Define rail section radius",
default=0.375, default=0.375,
) )
rail_span: bpy.props.FloatProperty( rail_span: bpy.props.FloatProperty(
name="Rail span", name="Rail Span",
description="Define rail span", description="The length between 2 single rails.",
default=3.75, default=3.75,
) )
def execute(self, context): def execute(self, context):
bpy.ops.object.select_all(action='DESELECT')
# create one first # create one first
bpy.ops.mesh.primitive_circle_add(vertices=8, firstObj = _create_ballance_circle(self.rail_radius, (0.0, 0.0, 0.0))
radius=self.rail_radius,
fill_type='NOTHING',
calc_uvs=False,
enter_editmode=False,
align='WORLD',
location=(0.0, 0.0, 0.0))
firstObj = bpy.context.selected_objects[0]
# for double rail # for double rail
if self.rail_type == 'DOUBLE': if self.rail_type == 'DOUBLE':
bpy.ops.object.select_all(action='DESELECT') # create another one
bpy.ops.mesh.primitive_circle_add(vertices=8, secondObj = _create_ballance_circle(self.rail_radius, (self.rail_span, 0.0, 0.0))
radius=self.rail_radius,
fill_type='NOTHING',
calc_uvs=False,
enter_editmode=False,
align='WORLD',
location=(self.rail_span, 0.0, 0.0))
secondObj = bpy.context.selected_objects[0]
# merge # merge
bpy.ops.object.select_all(action='DESELECT') firstObj = _merge_two_circle(firstObj, secondObj)
bpy.context.view_layer.objects.active = firstObj
firstObj.select_set(True)
secondObj.select_set(True)
bpy.ops.object.join()
# rename
if self.rail_type == 'DOUBLE':
firstObj.name = "A_Rail_"
else:
firstObj.name = "A_Rail_Mono_"
# apply 3d cursor # apply 3d cursor
UTILS_functions.move_to_cursor(firstObj) UTILS_functions.move_to_cursor(firstObj)
@ -75,3 +61,80 @@ class BALLANCE_OT_add_rails(bpy.types.Operator):
if self.rail_type == 'DOUBLE': if self.rail_type == 'DOUBLE':
layout.prop(self, "rail_span") layout.prop(self, "rail_span")
class BALLANCE_OT_add_tunnels(bpy.types.Operator):
"""Add rail"""
bl_idname = "ballance.add_tunnels"
bl_label = "Add tunnel section"
bl_options = {'UNDO'}
use_outside: bpy.props.BoolProperty(
name="Double Sides",
description="Create tunnel section with double sides, not a single face.",
default=True,
)
inside_radius: bpy.props.FloatProperty(
name="Inside Radius",
description="Tunnel inside radius",
default=2.5,
)
outside_radius: bpy.props.FloatProperty(
name="Outside Radius",
description="Tunnel outside radius",
default=2.6,
)
def execute(self, context):
# create one first
firstObj = _create_ballance_circle(self.inside_radius, (0.0, 0.0, 0.0))
# for double rail
if self.use_outside:
# create another one
secondObj = _create_ballance_circle(self.outside_radius, (0.0, 0.0, 0.0))
# merge
firstObj = _merge_two_circle(firstObj, secondObj)
# rename
firstObj.name = "A_Rail_Tunnel_"
# apply 3d cursor
UTILS_functions.move_to_cursor(firstObj)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.prop(self, "use_outside")
layout.prop(self, "inside_radius")
if self.use_outside:
layout.prop(self, "outside_radius")
def _create_ballance_circle(radius, loc):
bpy.ops.object.select_all(action='DESELECT')
bpy.ops.mesh.primitive_circle_add(
vertices=8,
radius=radius,
fill_type='NOTHING',
calc_uvs=False,
enter_editmode=False,
align='WORLD',
location=loc
)
created_obj = bpy.context.selected_objects[0]
bpy.ops.object.select_all(action='DESELECT')
return created_obj
def _merge_two_circle(obj1, obj2):
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active = obj1
obj1.select_set(True)
obj2.select_set(True)
bpy.ops.object.join()
return obj1

View File

@ -1,64 +1,61 @@
import bpy import bpy
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop, UTILS_icons_manager
class common_group_name_props(bpy.types.Operator): class BALLANCE_OT_select_virtools_group(UTILS_virtools_prop.common_group_name_props):
use_custom_name: bpy.props.BoolProperty(
name="Use Custom Name",
description="Whether use user defined group name.",
default=False,
)
group_name: bpy.props.EnumProperty(
name="Group Name",
description="Pick vanilla Ballance group name.",
items=tuple((x, x, "") for x in UTILS_constants.propsVtGroups_availableGroups),
)
custom_group_name: bpy.props.StringProperty(
name="Custom Group Name",
description="Input your custom group name.",
default="",
)
def get_group_name_string(self):
return str(self.custom_group_name if self.use_custom_name else self.group_name)
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
class BALLANCE_OT_select_virtools_group(common_group_name_props):
"""Select objects by Virtools Group.""" """Select objects by Virtools Group."""
bl_idname = "ballance.select_virtools_group" bl_idname = "ballance.select_virtools_group"
bl_label = "Select by Virtools Group" bl_label = "Select by Virtools Group"
bl_options = {'UNDO'} bl_options = {'UNDO'}
merge_selection: bpy.props.BoolProperty( selection_type: bpy.props.EnumProperty(
name="Merge Selection", name="Mode",
description="Merge selection, rather than re-select them.", description="Selection mode",
default=False, items=(
) ('SET', "Set", "Sets a new selection.", "SELECT_SET", 0),
('EXTEND', "Extend", "Adds newly selected items to the existing selection.", "SELECT_EXTEND", 1),
ignore_hide: bpy.props.BoolProperty( ('SUBTRACT', "Subtract", "Removes newly selected items from the existing selection.", "SELECT_SUBTRACT", 2),
name="Ignore Hide Property", ('DIFFERENCE', "Invert", "Inverts the selection.", "SELECT_DIFFERENCE", 3),
description="Select objects without considering visibility.", ('INTERSECT', "Intersect", "Selects items that intersect with the existing selection.", "SELECT_INTERSECT", 4),
default=False, ),
default='SET'
) )
def execute(self, context): def execute(self, context):
if self.selection_type == 'SET':
# iterate object # iterate object
for obj in bpy.context.scene.objects: for obj in bpy.context.scene.objects:
# ignore hidden objects # check group and decide whether select this obj
if (not self.ignore_hide) and obj.hide_get() == True: obj.select_set(UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string()))
continue
# check group elif self.selection_type == 'EXTEND':
# also iterate all objects
for obj in bpy.context.scene.objects:
# directly add if group matched. do not deselect anything
if UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string()): if UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string()):
# select object
obj.select_set(True) obj.select_set(True)
else: elif self.selection_type == 'SUBTRACT':
# if not in merge mode, deselect them # subtract only involving selected item. so we get selected objest first
if not self.merge_selection: # and iterate it to reduce useless operations
selected = bpy.context.selected_objects[:]
for obj in selected:
# remove matched only
if UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string()):
obj.select_set(False)
elif self.selection_type == '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:
# use xor to select
# in_selected XOR in_group
obj.select_set((obj in selected_set) ^ UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string()))
elif self.selection_type == 'INTERSECT':
# like subtract, only iterate selected obj
selected = bpy.context.selected_objects[:]
for obj in selected:
# remove not matched
if not UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string()):
obj.select_set(False) obj.select_set(False)
return {'FINISHED'} return {'FINISHED'}
@ -66,72 +63,14 @@ class BALLANCE_OT_select_virtools_group(common_group_name_props):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
row = layout.row() layout.label(text='Selection Parameters')
row.prop(self, 'ignore_hide') layout.prop(self, 'selection_type', expand=True, icon_only=True)
row.prop(self, 'merge_selection')
layout.separator() layout.separator()
layout.prop(self, 'use_custom_name') layout.label(text='Group Parameters')
if (self.use_custom_name): self.parent_draw(layout)
layout.prop(self, 'custom_group_name')
else:
layout.prop(self, 'group_name')
class BALLANCE_OT_filter_virtools_group(common_group_name_props): class BALLANCE_OT_ctx_set_group(UTILS_virtools_prop.common_group_name_props):
"""Filter objects by Virtools Group."""
bl_idname = "ballance.filter_virtools_group"
bl_label = "Filter by Virtools Group"
bl_options = {'UNDO'}
reverse_selection: bpy.props.BoolProperty(
name="Reverse",
description="Reverse operation. Remove matched objects.",
default=False,
)
ignore_hide: bpy.props.BoolProperty(
name="Ignore Hide Property",
description="Select objects without considering visibility.",
default=False,
)
def execute(self, context):
# make a copy for all objects, to ensure it is not viotile
# becuase we need deselect some objects in for statement
selected = bpy.context.selected_objects[:]
# iterate object
for obj in selected:
# ignore hidden objects
if (not self.ignore_hide) and obj.hide_get() == True:
continue
# check group and decide select
is_selected = UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string())
if self.reverse_selection:
is_selected = not is_selected
# select object
obj.select_set(is_selected)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, 'ignore_hide')
row.prop(self, 'reverse_selection')
layout.separator()
layout.prop(self, 'use_custom_name')
if (self.use_custom_name):
layout.prop(self, 'custom_group_name')
else:
layout.prop(self, 'group_name')
class BALLANCE_OT_ctx_set_group(common_group_name_props):
"""Grouping selected objects""" """Grouping selected objects"""
bl_idname = "ballance.ctx_set_group" bl_idname = "ballance.ctx_set_group"
bl_label = "Grouping Objects" bl_label = "Grouping Objects"
@ -152,7 +91,10 @@ class BALLANCE_OT_ctx_set_group(common_group_name_props):
# throw a warning if some objects have duplicated group # throw a warning if some objects have duplicated group
if has_duplicated: if has_duplicated:
UTILS_functions.show_message_box(("Some objects have duplicated group name.", "These objects have been omitted.", ), "Duplicated Group", 'ERROR') UTILS_functions.show_message_box(
("Some objects have duplicated group name.", "These objects have been omitted.", ),
"Duplicated Group", UTILS_icons_manager.blender_error_icon
)
return {'FINISHED'} return {'FINISHED'}
@ -162,13 +104,9 @@ class BALLANCE_OT_ctx_set_group(common_group_name_props):
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.prop(self, 'use_custom_name') self.parent_draw(layout)
if (self.use_custom_name):
layout.prop(self, 'custom_group_name')
else:
layout.prop(self, 'group_name')
class BALLANCE_OT_ctx_unset_group(common_group_name_props): class BALLANCE_OT_ctx_unset_group(UTILS_virtools_prop.common_group_name_props):
"""Ungrouping selected objects""" """Ungrouping selected objects"""
bl_idname = "ballance.ctx_unset_group" bl_idname = "ballance.ctx_unset_group"
bl_label = "Ungrouping Objects" bl_label = "Ungrouping Objects"
@ -189,17 +127,16 @@ class BALLANCE_OT_ctx_unset_group(common_group_name_props):
# throw a warning if some objects have duplicated group # throw a warning if some objects have duplicated group
if lack_group: if lack_group:
UTILS_functions.show_message_box(("Some objects lack specified group name.", "These objects have been omitted.", ), "Lack Group", 'ERROR') UTILS_functions.show_message_box(
("Some objects lack specified group name.", "These objects have been omitted.", ),
"Lack Group", UTILS_icons_manager.blender_error_icon
)
return {'FINISHED'} return {'FINISHED'}
def draw(self, context): def draw(self, context):
layout = self.layout layout = self.layout
layout.prop(self, 'use_custom_name') self.parent_draw(layout)
if (self.use_custom_name):
layout.prop(self, 'custom_group_name')
else:
layout.prop(self, 'group_name')
class BALLANCE_OT_ctx_clear_group(bpy.types.Operator): class BALLANCE_OT_ctx_clear_group(bpy.types.Operator):
"""Clear Virtools Groups for selected objects""" """Clear Virtools Groups for selected objects"""
@ -211,11 +148,14 @@ class BALLANCE_OT_ctx_clear_group(bpy.types.Operator):
def poll(self, context): def poll(self, context):
return len(bpy.context.selected_objects) != 0 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): def execute(self, context):
# iterate object # iterate object
for obj in bpy.context.selected_objects: for obj in bpy.context.selected_objects:
UTILS_virtools_prop.clear_virtools_group_data(obj) UTILS_virtools_prop.clear_virtools_group_data(obj)
return {'FINISHED'} return {'FINISHED'}

View File

@ -1,55 +1,26 @@
import bpy import bpy
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop, UTILS_icons_manager
class BALLANCE_OT_add_virtools_group(bpy.types.Operator): class BALLANCE_OT_add_virtools_group(UTILS_virtools_prop.common_group_name_props):
"""Add a Virtools Group for Active Object.""" """Add a Virtools Group for Active Object."""
bl_idname = "ballance.add_virtools_group" bl_idname = "ballance.add_virtools_group"
bl_label = "Add Virtools Group" bl_label = "Add Virtools Group"
bl_options = {'UNDO'} bl_options = {'UNDO'}
use_custom_name: bpy.props.BoolProperty(
name="Use Custom Name",
description="Whether use user defined group name.",
default=False,
)
group_name: bpy.props.EnumProperty(
name="Group Name",
description="Pick vanilla Ballance group name.",
items=tuple((x, x, "") for x in UTILS_constants.propsVtGroups_availableGroups),
)
custom_group_name: bpy.props.StringProperty(
name="Custom Group Name",
description="Input your custom group name.",
default="",
)
@classmethod @classmethod
def poll(self, context): def poll(self, context):
return context.object is not None return context.object is not None
def execute(self, context): def execute(self, context):
# get name first
gotten_group_name = str(self.custom_group_name if self.use_custom_name else self.group_name)
# try adding # try adding
obj = context.object obj = context.object
if not UTILS_virtools_prop.add_virtools_group_data(obj, gotten_group_name): if not UTILS_virtools_prop.add_virtools_group_data(obj, self.get_group_name_string()):
UTILS_functions.show_message_box(("Group name is duplicated!", ), "Duplicated Name", 'ERROR') UTILS_functions.show_message_box(("Group name is duplicated!", ), "Duplicated Name", UTILS_icons_manager.blender_error_icon)
return {'FINISHED'} return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context): def draw(self, context):
self.layout.prop(self, 'use_custom_name') self.parent_draw(self.layout)
if (self.use_custom_name):
self.layout.prop(self, 'custom_group_name')
else:
self.layout.prop(self, 'group_name')
class BALLANCE_OT_rm_virtools_group(bpy.types.Operator): class BALLANCE_OT_rm_virtools_group(bpy.types.Operator):
@ -73,9 +44,29 @@ class BALLANCE_OT_rm_virtools_group(bpy.types.Operator):
UTILS_virtools_prop.remove_virtools_group_data_by_index(obj, int(UTILS_virtools_prop.get_active_virtools_group(obj))) UTILS_virtools_prop.remove_virtools_group_data_by_index(obj, int(UTILS_virtools_prop.get_active_virtools_group(obj)))
return {'FINISHED'} return {'FINISHED'}
class BALLANCE_OT_clear_virtools_group(bpy.types.Operator):
"""Clear All Virtools Group for Active Object."""
bl_idname = "ballance.clear_virtools_group"
bl_label = "Clear Virtools Group"
bl_options = {'UNDO'}
@classmethod
def poll(self, 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):
obj = context.object
UTILS_virtools_prop.clear_virtools_group_data(obj)
return {'FINISHED'}
class BALLANCE_UL_virtools_group(bpy.types.UIList): class BALLANCE_UL_virtools_group(bpy.types.UIList):
def draw_item(self, context, layout, data, item, icon, active_data, active_propname): def draw_item(self, context, layout, data, item, icon, active_data, active_propname):
layout.prop(item, 'group_name', icon='GROUP', emboss=False, text="") layout.label(text=item.group_name, translate=False, icon='GROUP')
#layout.prop(item, 'group_name', icon='GROUP', emboss=False, text="")
class BALLANCE_PT_virtools_group(bpy.types.Panel): class BALLANCE_PT_virtools_group(bpy.types.Panel):
"""Show Virtools Group Properties.""" """Show Virtools Group Properties."""
@ -100,3 +91,5 @@ class BALLANCE_PT_virtools_group(bpy.types.Panel):
col = row.column(align=True) col = row.column(align=True)
col.operator(BALLANCE_OT_add_virtools_group.bl_idname, icon='ADD', text="") col.operator(BALLANCE_OT_add_virtools_group.bl_idname, icon='ADD', text="")
col.operator(BALLANCE_OT_rm_virtools_group.bl_idname, icon='REMOVE', text="") col.operator(BALLANCE_OT_rm_virtools_group.bl_idname, icon='REMOVE', text="")
col.separator()
col.operator(BALLANCE_OT_clear_virtools_group.bl_idname, icon='TRASH', text="")

View File

@ -1,5 +1,5 @@
import bpy import bpy
from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop from . import UTILS_constants, UTILS_functions, UTILS_virtools_prop, UTILS_icons_manager
class BALLANCE_OT_apply_virtools_material(bpy.types.Operator): class BALLANCE_OT_apply_virtools_material(bpy.types.Operator):
"""Apply Virtools Material to Blender Material.""" """Apply Virtools Material to Blender Material."""
@ -19,7 +19,7 @@ class BALLANCE_OT_apply_virtools_material(bpy.types.Operator):
if mtl_data[0]: if mtl_data[0]:
UTILS_functions.create_material_nodes(mtl, mtl_data) UTILS_functions.create_material_nodes(mtl, mtl_data)
else: else:
UTILS_functions.show_message_box(("Virtools Material is not enabled.", ), "Apply Failed", 'ERROR') UTILS_functions.show_message_box(("Virtools Material is not enabled.", ), "Apply Failed", UTILS_icons_manager.blender_error_icon)
return {'FINISHED'} return {'FINISHED'}
@ -37,12 +37,54 @@ class BALLANCE_OT_parse_virtools_material(bpy.types.Operator):
mtl = context.material mtl = context.material
mtl_data = UTILS_functions.parse_material_nodes(mtl) mtl_data = UTILS_functions.parse_material_nodes(mtl)
if mtl_data is None: if mtl_data is None:
UTILS_functions.show_message_box(("Fail to parse Principled BSDF.", ), "Parsing Failed", 'ERROR') UTILS_functions.show_message_box(("Fail to parse Principled BSDF.", ), "Parsing Failed", UTILS_icons_manager.blender_error_icon)
else: else:
UTILS_virtools_prop.set_virtools_material_data(mtl, mtl_data) UTILS_virtools_prop.set_virtools_material_data(mtl, mtl_data)
return {'FINISHED'} return {'FINISHED'}
class BALLANCE_OT_preset_virtools_material(bpy.types.Operator):
"""Preset Virtools Material with Original Ballance Data."""
bl_idname = "ballance.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=tuple(
(str(idx), item["human-readable"], "Suit for: " + ", ".join(item["member"]))
for idx, item in enumerate(UTILS_constants.floor_materialStatistic)
),
)
@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):
preset_idx = int(self.preset_type)
preset_data = UTILS_constants.floor_materialStatistic[preset_idx]
# get data self and only change core colors
mtl = context.material
vtmtl = UTILS_virtools_prop.get_virtools_material(mtl)
vtmtl.ambient = preset_data['data']['ambient']
vtmtl.diffuse = preset_data['data']['diffuse']
vtmtl.specular = preset_data['data']['specular']
vtmtl.emissive = preset_data['data']['emissive']
vtmtl.specular_power = preset_data['data']['power']
return {'FINISHED'}
class BALLANCE_PT_virtools_material(bpy.types.Panel): class BALLANCE_PT_virtools_material(bpy.types.Panel):
"""Show Virtools Material Properties.""" """Show Virtools Material Properties."""
bl_label = "Virtools Material" bl_label = "Virtools Material"
@ -69,7 +111,9 @@ class BALLANCE_PT_virtools_material(bpy.types.Panel):
layout.enabled = target.enable_virtools_material layout.enabled = target.enable_virtools_material
# draw layout # draw layout
layout.label(text="Basic Parameters") row = layout.row()
row.label(text="Basic Parameters")
row.operator(BALLANCE_OT_preset_virtools_material.bl_idname, text="", icon="PRESET")
layout.prop(target, 'ambient') layout.prop(target, 'ambient')
layout.prop(target, 'diffuse') layout.prop(target, 'diffuse')
layout.prop(target, 'specular') layout.prop(target, 'specular')
@ -86,6 +130,6 @@ class BALLANCE_PT_virtools_material(bpy.types.Panel):
layout.separator() layout.separator()
layout.label(text="Operations") layout.label(text="Operations")
layout.operator("ballance.apply_virtools_material", icon="NODETREE") layout.operator(BALLANCE_OT_apply_virtools_material.bl_idname, icon="NODETREE")
layout.operator("ballance.parse_virtools_material", icon="HIDE_OFF") layout.operator(BALLANCE_OT_parse_virtools_material.bl_idname, icon="HIDE_OFF")

View File

@ -169,9 +169,10 @@ floor_textureReflactionMap = {
"BallStone": "Ball_Stone.bmp" "BallStone": "Ball_Stone.bmp"
} }
# WARNING: this data is shared with `BallanceVirtoolsPlugin/bvh/features/mapping/fix_texture.cpp` # WARNING: this data is shared with `BallanceVirtoolsPlugin/bvh/features/mapping/bmfile_fix_texture.cpp`
floor_materialStatistic = [ floor_materialStatistic = [
{ {
"human-readable": "Floor Side",
"member": [ "member": [
"FloorSide", "FloorSide",
"FloorTopBorder_ForSide", "FloorTopBorder_ForSide",
@ -186,6 +187,7 @@ floor_materialStatistic = [
} }
}, },
{ {
"human-readable": "Floor Top",
"member": [ "member": [
"FloorTopBorder", "FloorTopBorder",
"FloorTopBorderless", "FloorTopBorderless",
@ -202,6 +204,7 @@ floor_materialStatistic = [
} }
}, },
{ {
"human-readable": "Transform Paper",
"member": [ "member": [
"BallPaper" "BallPaper"
], ],
@ -214,6 +217,7 @@ floor_materialStatistic = [
} }
}, },
{ {
"human-readable": "Transform Stone & Wood",
"member": [ "member": [
"BallStone", "BallStone",
"BallWood" "BallWood"
@ -225,6 +229,45 @@ floor_materialStatistic = [
"emissive": (60 / 255.0, 60 / 255.0, 60 / 255.0), "emissive": (60 / 255.0, 60 / 255.0, 60 / 255.0),
"power": 0 "power": 0
} }
},
{
"human-readable": "Rail",
"member": [
"Rail"
],
"data": {
"ambient": (0.0, 0.0, 0.0),
"diffuse": (100 / 255.0, 118 / 255.0, 133 / 255.0),
"specular": (210 / 255.0, 210 / 255.0, 210 / 255.0),
"emissive": (124 / 255.0, 134 / 255.0, 150 / 255.0),
"power": 10
}
},
{
"human-readable": "Wood Path",
"member": [
"WoodPanel"
],
"data": {
"ambient": (2 / 255.0, 2 / 255.0, 2 / 255.0),
"diffuse": (1.0, 1.0, 1.0),
"specular": (59 / 255.0, 59 / 255.0, 59 / 255.0),
"emissive": (30 / 255.0, 30 / 255.0, 30 / 255.0),
"power": 25
}
},
{
"human-readable": "Wood Chip",
"member": [
"WoodPlain2"
],
"data": {
"ambient": (25 / 255.0, 25 / 255.0, 25 / 255.0),
"diffuse": (1.0, 1.0, 1.0),
"specular": (100 / 255.0, 100 / 255.0, 100 / 255.0),
"emissive": (50 / 255.0, 50 / 255.0, 50 / 255.0),
"power": 50
}
} }
] ]
@ -247,11 +290,6 @@ for walk_root, walk_dirs, walk_files in os.walk(os.path.join(os.path.dirname(__f
floor_derivedBlockList.append(item["Type"]) floor_derivedBlockList.append(item["Type"])
floor_blockDict[item["Type"]] = item floor_blockDict[item["Type"]] = item
icons_floor = None
icons_floorDict = {}
# blenderIcon_elements = None
# blenderIcon_elements_dict = {}
rename_normalComponentsGroupName = set([ rename_normalComponentsGroupName = set([
"P_Extra_Life", "P_Extra_Life",
"P_Extra_Point", "P_Extra_Point",
@ -353,7 +391,9 @@ propsVtGroups_availableGroups = (
"Phys_Floors", "Phys_Floors",
"Phys_FloorRails", "Phys_FloorRails",
"Phys_FloorStopper" "Phys_FloorStopper",
"Shadow"
) )

View File

@ -105,7 +105,7 @@ def parse_material_nodes(mtl):
# return value # return value
return (True, return (True,
mtl_ambient, mtl_diffuse, mtl_specular, mtl_emissive, mtl_specularPower, mtl_ambient, mtl_diffuse, mtl_specular, mtl_emissive, mtl_specularPower,
False, False, False, False, False, False, True, False,
mtl_texture mtl_texture
) )
@ -116,8 +116,21 @@ def parse_material_nodes(mtl):
# load component # load component
def load_component(component_id): def load_component(component_id):
# get file first # get component name from id
component_name = UTILS_constants.bmfile_componentList[component_id] component_name = UTILS_constants.bmfile_componentList[component_id]
# create real mesh.
# if component mesh is existed, use existed one.
(mesh, skip_init) = create_instance_with_option(
UTILS_constants.BmfileInfoType.MESH,
"BlcBldPlg_EleMesh_" + component_name,
'CURRENT'
)
if skip_init:
return mesh
# mesh is not existing. start to load mesh
# get file first
selected_file = os.path.join( selected_file = os.path.join(
os.path.dirname(__file__), os.path.dirname(__file__),
'meshes', 'meshes',
@ -127,9 +140,6 @@ def load_component(component_id):
# read file. please note this sector is sync with import_bm's mesh's code. when something change, please change each other. # read file. please note this sector is sync with import_bm's mesh's code. when something change, please change each other.
fmesh = open(selected_file, 'rb') fmesh = open(selected_file, 'rb')
# create real mesh, we don't need to consider name. blender will solve duplicated name
mesh = bpy.data.meshes.new('mesh_' + component_name)
vList = [] vList = []
vnList = [] vnList = []
faceList = [] faceList = []
@ -204,6 +214,10 @@ def create_instance_with_option(instance_type, instance_name, instance_opt,
For object, you should provide `extra_mesh`. For object, you should provide `extra_mesh`.
For texture, you should provide `extra_texture_path` and `extra_texture_filename`. For texture, you should provide `extra_texture_path` and `extra_texture_filename`.
Value type:
`instance_type`: one integer in UTILS_constants.BmfileInfoType
`instance_name`: a string of new data block name
`instance_opt`: 'RENAME' or 'CURRENT'
""" """
def get_instance(): def get_instance():

View File

@ -0,0 +1,98 @@
import bpy
import bpy.utils.previews
import os
from . import UTILS_constants
blender_info_icon = 'INFO'
blender_warning_icon = 'ERROR'
blender_error_icon = 'CANCEL'
# universal icon loader, all icon are stored in this preview collection
universal_icons = None
# empty icon for placeholder
empty_icon_id = 0
# a map. key is block name, value is loaded icon id
floor_icons_map: dict = {}
element_icons_map: dict = {}
groupext_icons_map: dict = {}
group_name_conv_map: dict = {
"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 register_icons():
global universal_icons
global empty_icon_id
global floor_icons_map, element_icons_map, groupext_icons_map
# create preview collection and get icon folder
icon_path = os.path.join(os.path.dirname(__file__), "icons")
universal_icons = bpy.utils.previews.new()
# load empty
universal_icons.load("BlcBldPlg_EmptyIcon", os.path.join(icon_path, "Empty.png"), 'IMAGE')
empty_icon_id = universal_icons["BlcBldPlg_EmptyIcon"].icon_id
# add floor icon
for key, value in UTILS_constants.floor_blockDict.items():
blockIconName = "BlcBldPlg_FloorIcon_" + key
universal_icons.load(blockIconName, os.path.join(icon_path, "floor", value["BindingDisplayTexture"]), 'IMAGE')
floor_icons_map[key] = universal_icons[blockIconName].icon_id
# add elements icon
for elename in UTILS_constants.bmfile_componentList:
blockIconName = "BlcBldPlg_ElementIcon_" + elename
universal_icons.load(blockIconName, os.path.join(icon_path, "element", elename + '.png'), 'IMAGE')
element_icons_map[elename] = universal_icons[blockIconName].icon_id
# add extra group icon
for grp in ("SoundID_01", "SoundID_02", "SoundID_03"):
blockIconName = "BlcBldPlg_GroupIcon_" + grp
universal_icons.load(blockIconName, os.path.join(icon_path, "group", grp + '.png'), 'IMAGE')
groupext_icons_map[grp] = universal_icons[blockIconName].icon_id
def unregister_icons():
global universal_icons
global floor_icons_map, element_icons_map, groupext_icons_map
bpy.utils.previews.remove(universal_icons)
floor_icons_map.clear()
element_icons_map.clear()
groupext_icons_map.clear()
def get_floor_icon(floor_blk_name: str):
# default return empty icon
return floor_icons_map.get(floor_blk_name, empty_icon_id)
def get_element_icon(element_name: str):
# default return empty icon
return element_icons_map.get(element_name, empty_icon_id)
def get_group_icon(group_name: str):
# try parse string
# if not found, return self
conv_name = group_name_conv_map.get(group_name, group_name)
# get from extra group icon first
idx = groupext_icons_map.get(conv_name, empty_icon_id)
if idx != empty_icon_id:
return idx
# if failed, get from element. if still failed, return empty icon
return get_element_icon(conv_name)
# no matter how, register icon always
# and no unregister call
register_icons()

View File

@ -1,5 +1,5 @@
import bpy import bpy
from . import UTILS_constants, UTILS_functions from . import UTILS_constants, UTILS_functions, UTILS_icons_manager
class BALLANCE_PG_virtools_material(bpy.types.PropertyGroup): class BALLANCE_PG_virtools_material(bpy.types.PropertyGroup):
enable_virtools_material: bpy.props.BoolProperty( enable_virtools_material: bpy.props.BoolProperty(
@ -7,29 +7,37 @@ class BALLANCE_PG_virtools_material(bpy.types.PropertyGroup):
default=False, default=False,
) )
ambient: bpy.props.FloatVectorProperty(name="Ambient", ambient: bpy.props.FloatVectorProperty(
name="Ambient",
subtype='COLOR', subtype='COLOR',
min=0.0, min=0.0,
max=1.0, max=1.0,
default=[0.0,0.0,0.0]) default=[76 / 255, 76 / 255, 76 / 255]
)
diffuse: bpy.props.FloatVectorProperty(name="Diffuse", diffuse: bpy.props.FloatVectorProperty(
name="Diffuse",
subtype='COLOR', subtype='COLOR',
min=0.0, min=0.0,
max=1.0, max=1.0,
default=[0.0,0.0,0.0]) default=[178 / 255, 178 / 255, 178 / 255]
)
specular: bpy.props.FloatVectorProperty(name="Specular", specular: bpy.props.FloatVectorProperty(
name="Specular",
subtype='COLOR', subtype='COLOR',
min=0.0, min=0.0,
max=1.0, max=1.0,
default=[0.0,0.0,0.0]) default=[127 / 255, 127 / 255, 127 / 255]
)
emissive: bpy.props.FloatVectorProperty(name="Emissive", emissive: bpy.props.FloatVectorProperty(
name="Emissive",
subtype='COLOR', subtype='COLOR',
min=0.0, min=0.0,
max=1.0, max=1.0,
default=[0.0,0.0,0.0]) default=[0.0, 0.0, 0.0]
)
specular_power: bpy.props.FloatProperty( specular_power: bpy.props.FloatProperty(
name="Specular Power", name="Specular Power",
@ -53,7 +61,7 @@ class BALLANCE_PG_virtools_material(bpy.types.PropertyGroup):
z_buffer: bpy.props.BoolProperty( z_buffer: bpy.props.BoolProperty(
name="Z Buffer", name="Z Buffer",
description="ZFunc: VXCMP_LESSEQUAL.", description="ZFunc: VXCMP_LESSEQUAL.",
default=False, default=True,
) )
two_sided: bpy.props.BoolProperty( two_sided: bpy.props.BoolProperty(
@ -73,6 +81,45 @@ class BALLANCE_PG_virtools_group(bpy.types.PropertyGroup):
default="" default=""
) )
class common_group_name_props(bpy.types.Operator):
group_name_source: bpy.props.EnumProperty(
name="Group Name Source",
items=(('DEFINED', "Predefined", "Pre-defined group name."),
('CUSTOM', "Custom", "User specified group name."),
),
)
group_name: bpy.props.EnumProperty(
name="Group Name",
description="Pick vanilla Ballance group name.",
#items=tuple((x, x, "") for x in UTILS_constants.propsVtGroups_availableGroups),
items=tuple(
# token, display name, descriptions, icon, index
(grp, grp, "", UTILS_icons_manager.get_group_icon(grp), idx)
for idx, grp in enumerate(UTILS_constants.propsVtGroups_availableGroups)
),
)
custom_group_name: bpy.props.StringProperty(
name="Custom Group Name",
description="Input your custom group name.",
default="",
)
def parent_draw(self, parent_layout):
parent_layout.prop(self, 'group_name_source', expand=True)
if (self.group_name_source == 'CUSTOM'):
parent_layout.prop(self, 'custom_group_name')
else:
parent_layout.prop(self, 'group_name') # do not translate group name. it's weird
def get_group_name_string(self):
return str(self.custom_group_name if self.group_name_source == 'CUSTOM' else self.group_name)
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def get_virtools_material(mtl): def get_virtools_material(mtl):
return mtl.virtools_material return mtl.virtools_material

View File

@ -2,8 +2,8 @@ bl_info={
"name":"Ballance Blender Plugin", "name":"Ballance Blender Plugin",
"description":"Ballance mapping tools for Blender", "description":"Ballance mapping tools for Blender",
"author":"yyc12345", "author":"yyc12345",
"version":(3,0), "version":(3,1),
"blender":(2,83,0), "blender":(3,3,0),
"category":"Object", "category":"Object",
"support":"TESTING", "support":"TESTING",
"warning": "Please read document before using this plugin.", "warning": "Please read document before using this plugin.",
@ -13,9 +13,8 @@ bl_info={
# ============================================= # =============================================
# import system # import system
import bpy, bpy_extras import bpy
import bpy.utils.previews
import os
# import my code (with reload) # import my code (with reload)
if "bpy" in locals(): if "bpy" in locals():
import importlib import importlib
@ -33,6 +32,8 @@ if "bpy" in locals():
importlib.reload(UTILS_virtools_prop) importlib.reload(UTILS_virtools_prop)
if "UTILS_safe_eval" in locals(): if "UTILS_safe_eval" in locals():
importlib.reload(UTILS_safe_eval) importlib.reload(UTILS_safe_eval)
if "UTILS_icons_manager" in locals():
importlib.reload(UTILS_icons_manager)
if "BMFILE_export" in locals(): if "BMFILE_export" in locals():
importlib.reload(BMFILE_export) importlib.reload(BMFILE_export)
@ -63,7 +64,7 @@ if "bpy" in locals():
if "PROPS_virtools_material" in locals(): if "PROPS_virtools_material" in locals():
importlib.reload(PROPS_virtools_material) importlib.reload(PROPS_virtools_material)
from . import UTILS_constants, UTILS_functions, UTILS_preferences, UTILS_virtools_prop, UTILS_safe_eval from . import UTILS_constants, UTILS_functions, UTILS_preferences, UTILS_virtools_prop, UTILS_safe_eval, UTILS_icons_manager
from . import BMFILE_export, BMFILE_import from . import BMFILE_export, BMFILE_import
from . import MODS_3dsmax_align, MODS_flatten_uv, MODS_rail_uv from . import MODS_3dsmax_align, MODS_flatten_uv, MODS_rail_uv
from . import OBJS_add_components, OBJS_add_floors, OBJS_add_rails, OBJS_group_opers from . import OBJS_add_components, OBJS_add_floors, OBJS_add_rails, OBJS_group_opers
@ -85,20 +86,8 @@ class BALLANCE_MT_ThreeDViewerMenu(bpy.types.Menu):
layout.operator(MODS_rail_uv.BALLANCE_OT_rail_uv.bl_idname) layout.operator(MODS_rail_uv.BALLANCE_OT_rail_uv.bl_idname)
layout.operator(MODS_flatten_uv.BALLANCE_OT_flatten_uv.bl_idname) layout.operator(MODS_flatten_uv.BALLANCE_OT_flatten_uv.bl_idname)
class BALLANCE_MT_OutlinerMenu(bpy.types.Menu):
"""Ballance rename operators"""
bl_idname = "BALLANCE_MT_OutlinerMenu"
bl_label = "Ballance"
def draw(self, context):
layout = self.layout
oprt = layout.operator(NAMES_rename_system.BALLANCE_OT_rename_by_group.bl_idname)
oprt = layout.operator(NAMES_rename_system.BALLANCE_OT_convert_name.bl_idname)
oprt = layout.operator(NAMES_rename_system.BALLANCE_OT_auto_grouping.bl_idname)
class BALLANCE_MT_AddFloorMenu(bpy.types.Menu): class BALLANCE_MT_AddFloorMenu(bpy.types.Menu):
"""Add Ballance floor""" """Add Ballance Floor"""
bl_idname = "BALLANCE_MT_AddFloorMenu" bl_idname = "BALLANCE_MT_AddFloorMenu"
bl_label = "Floors" bl_label = "Floors"
@ -109,7 +98,7 @@ class BALLANCE_MT_AddFloorMenu(bpy.types.Menu):
for item in UTILS_constants.floor_basicBlockList: for item in UTILS_constants.floor_basicBlockList:
cop = layout.operator( cop = layout.operator(
OBJS_add_floors.BALLANCE_OT_add_floors.bl_idname, OBJS_add_floors.BALLANCE_OT_add_floors.bl_idname,
text=item, icon_value = UTILS_constants.icons_floorDict[item]) text=item, icon_value = UTILS_icons_manager.get_floor_icon(item))
cop.floor_type = item cop.floor_type = item
layout.separator() layout.separator()
@ -117,9 +106,50 @@ class BALLANCE_MT_AddFloorMenu(bpy.types.Menu):
for item in UTILS_constants.floor_derivedBlockList: for item in UTILS_constants.floor_derivedBlockList:
cop = layout.operator( cop = layout.operator(
OBJS_add_floors.BALLANCE_OT_add_floors.bl_idname, OBJS_add_floors.BALLANCE_OT_add_floors.bl_idname,
text=item, icon_value = UTILS_constants.icons_floorDict[item]) text=item, icon_value = UTILS_icons_manager.get_floor_icon(item))
cop.floor_type = item cop.floor_type = item
class BALLANCE_MT_AddRailMenu(bpy.types.Menu):
"""Add Ballance Rail"""
bl_idname = "BALLANCE_MT_AddRailMenu"
bl_label = "Rails"
def draw(self, context):
layout = self.layout
layout.operator(OBJS_add_rails.BALLANCE_OT_add_rails.bl_idname, text="Rail Section")
layout.operator(OBJS_add_rails.BALLANCE_OT_add_tunnels.bl_idname, text="Tunnel Section")
class BALLANCE_MT_AddNormalElementsMenu(bpy.types.Menu):
"""Add Ballance Elements"""
bl_idname = "BALLANCE_MT_AddNormalElementsMenu"
bl_label = "Elements"
def draw(self, context):
layout = self.layout
OBJS_add_components.BALLANCE_OT_add_components.draw_blc_menu(layout)
class BALLANCE_MT_AddDupElementsMenu(bpy.types.Menu):
"""Add Ballance Elements"""
bl_idname = "BALLANCE_MT_AddDupElementsMenu"
bl_label = "Elements"
def draw(self, context):
layout = self.layout
OBJS_add_components.BALLANCE_OT_add_components_dup.draw_blc_menu(layout)
class BALLANCE_MT_AddElementsMenu(bpy.types.Menu):
"""Add Ballance Elements"""
bl_idname = "BALLANCE_MT_AddElementsMenu"
bl_label = "Elements"
def draw(self, context):
layout = self.layout
layout.label(text="Basic Elements")
OBJS_add_components.BALLANCE_OT_add_components.draw_blc_menu(layout)
layout.separator()
layout.label(text="Duplicated Elements")
OBJS_add_components.BALLANCE_OT_add_components_dup.draw_blc_menu(layout)
layout.separator()
layout.label(text="Elements Series")
OBJS_add_components.BALLANCE_OT_add_components_series.draw_blc_menu(layout)
# ============================================= # =============================================
# blender call system # blender call system
@ -137,27 +167,32 @@ classes = (
BALLANCE_MT_ThreeDViewerMenu, BALLANCE_MT_ThreeDViewerMenu,
OBJS_add_components.BALLANCE_OT_add_components, OBJS_add_components.BALLANCE_OT_add_components,
OBJS_add_components.BALLANCE_OT_add_components_dup,
OBJS_add_components.BALLANCE_OT_add_components_series,
OBJS_add_rails.BALLANCE_OT_add_rails, OBJS_add_rails.BALLANCE_OT_add_rails,
OBJS_add_rails.BALLANCE_OT_add_tunnels,
OBJS_add_floors.BALLANCE_OT_add_floors, OBJS_add_floors.BALLANCE_OT_add_floors,
BALLANCE_MT_AddFloorMenu, BALLANCE_MT_AddFloorMenu,
BALLANCE_MT_AddRailMenu,
BALLANCE_MT_AddElementsMenu,
NAMES_rename_system.BALLANCE_OT_rename_by_group, NAMES_rename_system.BALLANCE_OT_rename_by_group,
NAMES_rename_system.BALLANCE_OT_convert_name, NAMES_rename_system.BALLANCE_OT_convert_name,
NAMES_rename_system.BALLANCE_OT_auto_grouping, NAMES_rename_system.BALLANCE_OT_auto_grouping,
BALLANCE_MT_OutlinerMenu,
UTILS_virtools_prop.BALLANCE_PG_virtools_material, UTILS_virtools_prop.BALLANCE_PG_virtools_material,
UTILS_virtools_prop.BALLANCE_PG_virtools_group, UTILS_virtools_prop.BALLANCE_PG_virtools_group,
PROPS_virtools_group.BALLANCE_OT_add_virtools_group, PROPS_virtools_group.BALLANCE_OT_add_virtools_group,
PROPS_virtools_group.BALLANCE_OT_rm_virtools_group, PROPS_virtools_group.BALLANCE_OT_rm_virtools_group,
PROPS_virtools_group.BALLANCE_OT_clear_virtools_group,
PROPS_virtools_group.BALLANCE_UL_virtools_group, PROPS_virtools_group.BALLANCE_UL_virtools_group,
PROPS_virtools_group.BALLANCE_PT_virtools_group, PROPS_virtools_group.BALLANCE_PT_virtools_group,
PROPS_virtools_material.BALLANCE_OT_apply_virtools_material, PROPS_virtools_material.BALLANCE_OT_apply_virtools_material,
PROPS_virtools_material.BALLANCE_OT_parse_virtools_material, PROPS_virtools_material.BALLANCE_OT_parse_virtools_material,
PROPS_virtools_material.BALLANCE_OT_preset_virtools_material,
PROPS_virtools_material.BALLANCE_PT_virtools_material, PROPS_virtools_material.BALLANCE_PT_virtools_material,
OBJS_group_opers.BALLANCE_OT_select_virtools_group, OBJS_group_opers.BALLANCE_OT_select_virtools_group,
OBJS_group_opers.BALLANCE_OT_filter_virtools_group,
OBJS_group_opers.BALLANCE_OT_ctx_set_group, OBJS_group_opers.BALLANCE_OT_ctx_set_group,
OBJS_group_opers.BALLANCE_OT_ctx_unset_group, OBJS_group_opers.BALLANCE_OT_ctx_unset_group,
OBJS_group_opers.BALLANCE_OT_ctx_clear_group, OBJS_group_opers.BALLANCE_OT_ctx_clear_group,
@ -175,20 +210,26 @@ def menu_func_ballance_add(self, context):
layout = self.layout layout = self.layout
layout.separator() layout.separator()
layout.label(text="Ballance") layout.label(text="Ballance")
layout.operator_menu_enum(
OBJS_add_components.BALLANCE_OT_add_components.bl_idname,
"elements_type", icon='MESH_ICOSPHERE', text="Elements")
layout.operator(OBJS_add_rails.BALLANCE_OT_add_rails.bl_idname, icon='MESH_CIRCLE', text="Rail section")
layout.menu(BALLANCE_MT_AddFloorMenu.bl_idname, icon='MESH_CUBE') layout.menu(BALLANCE_MT_AddFloorMenu.bl_idname, icon='MESH_CUBE')
layout.menu(BALLANCE_MT_AddRailMenu.bl_idname, icon='MESH_CIRCLE')
layout.menu(BALLANCE_MT_AddElementsMenu.bl_idname, icon='MESH_ICOSPHERE')
#layout.operator_menu_enum(
# OBJS_add_components.BALLANCE_OT_add_components.bl_idname,
# "elements_type", icon='MESH_ICOSPHERE', text="Elements")
def menu_func_ballance_rename(self, context): def menu_func_ballance_rename(self, context):
layout = self.layout layout = self.layout
layout.menu(BALLANCE_MT_OutlinerMenu.bl_idname) layout.separator()
col = layout.column()
col.operator_context = 'INVOKE_DEFAULT'
col.label(text="Ballance")
col.operator(NAMES_rename_system.BALLANCE_OT_rename_by_group.bl_idname, icon='GREASEPENCIL')
col.operator(NAMES_rename_system.BALLANCE_OT_convert_name.bl_idname, icon='ARROW_LEFTRIGHT')
col.operator(NAMES_rename_system.BALLANCE_OT_auto_grouping.bl_idname, icon='GROUP')
def menu_func_ballance_select(self, context): def menu_func_ballance_select(self, context):
layout = self.layout layout = self.layout
layout.separator() layout.separator()
layout.label(text="Ballance") layout.label(text="Ballance")
layout.operator(OBJS_group_opers.BALLANCE_OT_select_virtools_group.bl_idname, icon='SELECT_SET') layout.operator(OBJS_group_opers.BALLANCE_OT_select_virtools_group.bl_idname, icon='RESTRICT_SELECT_OFF')
layout.operator(OBJS_group_opers.BALLANCE_OT_filter_virtools_group.bl_idname, icon='FILTER')
def menu_func_ballance_grouping(self, context): def menu_func_ballance_grouping(self, context):
layout = self.layout layout = self.layout
layout.separator() layout.separator()
@ -202,12 +243,7 @@ def menu_func_ballance_grouping(self, context):
def register(): def register():
# we need init all icon first # we need init all icon first
icon_path = os.path.join(os.path.dirname(__file__), "icons") #UTILS_icons_manager.register_icons()
UTILS_constants.icons_floor = bpy.utils.previews.new()
for key, value in UTILS_constants.floor_blockDict.items():
blockIconName = "Ballance_FloorIcon_" + key
UTILS_constants.icons_floor.load(blockIconName, os.path.join(icon_path, "floor", value["BindingDisplayTexture"]), 'IMAGE')
UTILS_constants.icons_floorDict[key] = UTILS_constants.icons_floor[blockIconName].icon_id
for cls in classes: for cls in classes:
bpy.utils.register_class(cls) bpy.utils.register_class(cls)
@ -220,10 +256,13 @@ def register():
bpy.types.VIEW3D_MT_editor_menus.prepend(menu_func_ballance_3d) bpy.types.VIEW3D_MT_editor_menus.prepend(menu_func_ballance_3d)
bpy.types.VIEW3D_MT_add.append(menu_func_ballance_add) bpy.types.VIEW3D_MT_add.append(menu_func_ballance_add)
bpy.types.OUTLINER_HT_header.append(menu_func_ballance_rename) bpy.types.OUTLINER_MT_collection.append(menu_func_ballance_rename)
bpy.types.VIEW3D_MT_select_object.append(menu_func_ballance_select) bpy.types.VIEW3D_MT_select_object.append(menu_func_ballance_select)
bpy.types.VIEW3D_MT_object_context_menu.append(menu_func_ballance_grouping) bpy.types.VIEW3D_MT_object_context_menu.append(menu_func_ballance_grouping)
bpy.types.OUTLINER_MT_object.append(menu_func_ballance_grouping) # share the same menu
def unregister(): def unregister():
bpy.types.TOPBAR_MT_file_import.remove(menu_func_bm_import) bpy.types.TOPBAR_MT_file_import.remove(menu_func_bm_import)
@ -231,10 +270,12 @@ def unregister():
bpy.types.VIEW3D_MT_editor_menus.remove(menu_func_ballance_3d) bpy.types.VIEW3D_MT_editor_menus.remove(menu_func_ballance_3d)
bpy.types.VIEW3D_MT_add.remove(menu_func_ballance_add) bpy.types.VIEW3D_MT_add.remove(menu_func_ballance_add)
bpy.types.OUTLINER_HT_header.remove(menu_func_ballance_rename) bpy.types.OUTLINER_MT_collection.remove(menu_func_ballance_rename)
bpy.types.VIEW3D_MT_select_object.remove(menu_func_ballance_select) bpy.types.VIEW3D_MT_select_object.remove(menu_func_ballance_select)
bpy.types.VIEW3D_MT_object_context_menu.remove(menu_func_ballance_grouping) bpy.types.VIEW3D_MT_object_context_menu.remove(menu_func_ballance_grouping)
bpy.types.OUTLINER_MT_object.append(menu_func_ballance_grouping)
UTILS_virtools_prop.unregister_props() UTILS_virtools_prop.unregister_props()
del bpy.types.Scene.BallanceBlenderPluginProperty del bpy.types.Scene.BallanceBlenderPluginProperty
@ -243,7 +284,7 @@ def unregister():
bpy.utils.unregister_class(cls) bpy.utils.unregister_class(cls)
# we need uninstall all icon after all classes unregister # we need uninstall all icon after all classes unregister
bpy.utils.previews.remove(UTILS_constants.icons_floor) #UTILS_icons_manager.unregister_icons()
if __name__=="__main__": if __name__=="__main__":
register() register()

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 663 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 818 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 984 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 992 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 444 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 691 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 807 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 695 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 550 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 251 B