19 Commits

Author SHA1 Message Date
12d5f8c236 [doc] bump up version. fix document.
- bump up version to 3.3
- fix documents
  * rewrite the supported version.
  * update suggested plugin link.
  * notify user some installation changes. order user change installation folder.
2023-07-21 10:02:36 +08:00
6fe856fa8e [fix] migrate to blender 3.6 LTS
- change bl_info["support"] to COMMUNITY. because Blender do not support TESTINg anymore.
- now plugin should be installed in addons folder, not addons_contrib, due to blender changes.
- remove the reference about mesh.polypons.loop_total. because it is readonly now. (blender 3.6 changed)
- change uv assign method. use new properties instead. (MeshUVLoop is deprecated in blender 3.5 and removed in blender 4.0)
2023-07-01 12:56:07 +08:00
c2a85a2d86 [fix] fix empty material slot export error.
- fix the issue that the object with empty material slot will cause a None crash when exporting BM file.
2023-05-25 20:54:50 +08:00
dafb679780 [doc] add document for flatten uv and bump up version.
- add document for flatten uv
- bump up version to 3.2
2023-03-11 16:57:17 +08:00
f3663a4280 [fix] fix fatal div zero issue
- fix div error exception when flatten uv - scale size == 0
2023-03-09 21:24:05 +08:00
9c8d365ab6 [feat] add ref. point feature for flatten uv
- add reference point & uv feature for flatten.
- due to first feature, flatten uv now can process more structure of ballance floor, such as looping floor with low edge count.
- give flatten uv 2 different modes.
- original flatten uv method still existed as a scale size mode.
2023-03-09 21:18:13 +08:00
0be036fcea [fix] fix various bugs
- update README to keep update with plugin design
    * refactor Select by Virtools Group section
    * add material preset function description
- fix issue about flatten uv may get zero base vector.
- update elements adder menu.
- bump up version to 3.1
2023-01-31 10:50:07 +08:00
e153e51abd [feat] improve element adder
- fix ambiguous default side setter for floor adder. use "update" field of EnumProperty instead. also applied for dup element with span adder.
- divide component adder into 3 different adder, to process different add strategies.
- update elements placeholder adder. this change will force all element placeholder share a single mesh in the whole map.
- add series element adder.
2023-01-30 22:49:49 +08:00
d292ce389a [fix] add icons for sound hit/roll id group
- add 3 extra icons for groups Sound_(Roll/Hit)ID_(01|02|03).
- add a empty placeholder icon to make some UI more clear
- finish add group icons.
2023-01-30 15:00:29 +08:00
807e006245 [feat] add full element icons
- add more element icons. now element icons is not problem.
- change icon load strategy. now icon is loaded outside plugin. this operations might slow down blender but now I can apply my custom map to some operators to get better using experience.
- use new element icons to decorate some group name to let user know what this group stands for.
2023-01-30 11:12:15 +08:00
8d7a982e50 [fix] fix add floor alignment and add some new icons.
- try setting center alignment for add floor popup operator. it is not center alignment now because blender's impl but it is better than previous layout.
- add some components icon for more directly visual.
2023-01-29 21:39:24 +08:00
ddf6b7befe [fix] fix misc not important issues
- correct all icon usage. use 'CANCEL' instead of 'ERROR' to indicate error correctly. and move these icon const into UTILS_icons_manager.py
- auto rename generated rail section to match the default name standard.
- give more clear signal about import and export bm file by using Operator.report() function.
2023-01-29 10:32:43 +08:00
a300ddbb49 [fix] fix fatal error when use flatten uv for sink floor top.
- default behavior of flatten uv only suit for normal floor
- add extra property called scale correction to allow use specific custom uv scale.
2023-01-28 20:44:39 +08:00
c9e51c9b6a [feat] change function "selected by Virtools groups"
- remove ignore hidden property. because it is rarely used.
- support more select mode. like blender selection.
- merge function filter by group and select by group as a united function.
2023-01-28 20:05:18 +08:00
b58f837a94 [feat] add confirm window for some dangerous opers
- add clear oper in Virtools group panel.
- add confirm window for clear Virtools groups opers. both panel and context menu.
2023-01-28 11:15:41 +08:00
5fe865c621 [feat] improve rename system
- rename system now support display renamed name and old name at the same time to solve the problem that user can not find renamed objects.
- correct some typo of rename system report.
2023-01-28 10:39:31 +08:00
9b9fc9cde8 [feat] promote experience about floor creation.
- change bl_options and rewrite invoke and draw functions to let floor creating window become more visual. credit: BLumia.
- also change 3ds max align and flatten uv presentation after changing creating floor window.
- seperate icon loader/unload module.
2023-01-27 16:28:32 +08:00
ef459a210d [feat] promote virtools material
- set the default value of virtools material like virtools creation.
- add preset in virtools panel for convenient using.
2023-01-25 14:57:30 +08:00
7680d11c0e [fix] fix various issues
- add edit mode switch before bm export to prevent potential error
- let the default value of ZBuffer in Virtools Material become True
- fix the issue that duplicated elements adder do not understand enable option.
2023-01-18 00:03:11 +08:00
51 changed files with 1026 additions and 371 deletions

2
.gitattributes vendored
View File

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

View File

@ -9,7 +9,7 @@ The latest commit may not be stable to use, please use the latest commit with gi
This plugin contain various aspect of Ballance mapping. However, if some features can be easily gotten from other Blender plugin, this plugin will not provide them duplicatedly. We highly recommend that use this plugin with following plugins.
* [BenjaminSauder/SimpleLattice](https://github.com/BenjaminSauder/SimpleLattice): Create lattice quickly to transform object.
* [egtwobits/Mesh Align Plus](https://github.com/egtwobits/mesh_mesh_align_plus): Provide powerful align functions which far beyond vanilla Blender align function.
* [JulienHeijmans/quicksnap](https://github.com/JulienHeijmans/quicksnap): Provide powerful align functions which far beyond vanilla Blender align function.
## Technical Infomation
@ -17,11 +17,14 @@ Used BM file spec can be found in [there](https://github.com/yyc12345/gist/blob/
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).
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**.
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.6.x**.
## Installation
Put `ballance_blender_plugin` into Blender's plugin folder, `scripts/addons_contrib`. Then enable this plugin in Blender's preferences (DO NOT forget to configure this plugin's settings after first installation or updating plugin.).
Put `ballance_blender_plugin` into Blender's plugin folder, `scripts/addons`. Then enable this plugin in Blender's preferences (DO NOT forget to configure this plugin's settings after first installation or updating plugin.).
> **Note**
After the version 3.3 supporting Blender 3.6 LTS, you should install this plugin in `scripts/addons`, not `scripts/addons_contrib` due to Blender do not support testing plugin anymore. If you still have old version in `scripts/addons_contrib`, please **DELETE** it **BEFORE** install the new version.
## Feature Introduction
@ -68,6 +71,10 @@ Note that only convex face is supported. Applying this for a concave face will c
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.
For the UV flatten by plugin, it must have a scale property. For example, the UV scale of normal floor is 5. However, the UV scale of sink floor is slightly larger than 5. Because the sink floor is "sink" in the floor block. There are 2 methods provided by plugin to getting this proper scale number. You can choose one from Scale Mode.
The first method is that user specify a direct scale number. You just need select Scale Size in Scale Mode and fill with a proper scale number. This option is frequently used for fill a large borderless floor.
The second method is reference point mode. You need specify a reference point and corresponding U component of its UV. Plugin will calculate the scale size automatically. This method is used for expanding a path of floor.
### 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.
@ -109,26 +116,22 @@ Navigate to `Material Properties` panel, select a material, you can find `Virtoo
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.
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 Group
### Select by Virtools Group
Plugin add 2 selection functions according to Virtools Group in Select menu.
Plugin add a selection function according to Virtools Group in Select menu.
#### Select by Virtools Group
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.
Select objects in active collection according to its Virtools Group properties.
The hidden object also can be selected if you check `Ignore Hide Property`.
Check `Merge Selection` will merge current selection and previous selection.
#### Filter by Virtools Group
Filter current selected object by its Virtools Group properties.
Check `Reverse` remove objects matching the requirements, not keep them.
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

View File

@ -9,7 +9,7 @@
本插件囊括了Ballance制图中可能会用到的各种功能。对于一些其它插件可以提供的功能本插件不再重复提供。建议与下列插件合用以取得更好制图效果
* [BenjaminSauder/SimpleLattice](https://github.com/BenjaminSauder/SimpleLattice):快速创建晶格以便变形物体。
* [egtwobits/Mesh Align Plus](https://github.com/egtwobits/mesh_mesh_align_plus)提供远超Blender原生的对齐功能。
* [JulienHeijmans/quicksnap](https://github.com/JulienHeijmans/quicksnap)提供远超Blender原生的对齐功能。
## 技术信息
@ -17,11 +17,14 @@
使用的制图链标准以及`meshes`文件夹下的文件的格式可以在[这里](https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md)查找
`jsons`文件夹下的隶属于BMERevenge部分的文件的格式可以在[这里](https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_ZH.md)查找
支持Blender的原则是支持当前最新的 **LTS** 版本在最新的LTS版本释出之后会花一些时间迁移插件。当前插件基于**3.3.x**版本
支持Blender的原则是支持当前最新的 **LTS** 版本在最新的LTS版本释出之后会花一些时间迁移插件。当前插件基于**3.6.x**版本
## 安装
`ballance_blender_plugin`直接复制到Blender插件目录`scripts/addons_contrib`内即可。然后在Blender偏好设置中启用即可请在第一次安装后或更新插件后配置插件设置
`ballance_blender_plugin`直接复制到Blender插件目录`scripts/addons`内即可。然后在Blender偏好设置中启用即可请在第一次安装后或更新插件后配置插件设置
> **Note**
在对标Blender 3.6 LTS的3.3版本之后,您应该将此插件安装在`scripts/addons`中,而不是`scripts/addons_contrib`因为Blender不再支持Testing类型插件。 如果您在`scripts/addons_contrib`中仍有旧版本,请在安装新版本**之前删除**它。
## 功能介绍
@ -68,6 +71,10 @@ Ballance 3D是一套简单的用于制图3D相关的轻型工具集合可以
编辑模式下选中面点击Flatten UV然后选中一个边作为参考。
如果最后生成的边贴附不对,比如把路面花纹贴到了下部,可以重新选择参考边再进行操作,直到正确为止。
对于粘贴的贴图的UV需要具有一定缩放比如对于平路面这个缩放比是5而对于凹路面则要比5大一些因为凹路面由于凹进路面。为了方便确认这个缩放值我们提供了两种方式可以在Scale Mode种选择。
一种是用户直接指定选择Scale Mode为Scale Size并填写合适的缩放数值即可。此选项适合平谱无边框路面。
另一种即为参考点模式。用户指定一个参考点并指定此参考点在U轴上的位置插件会自动计算缩放值应为多少。此选项适合展开路面路径的贴图。
### 快速添加结构
在添加菜单中我们添加了一系列较为常用的物体。添加后物体会移动到3D游标处。
@ -109,7 +116,8 @@ Ballance 3D是一套简单的用于制图3D相关的轻型工具集合可以
默认情况下由用户创建的材质不启用Virtools Material您可以通过点击`Virtools Material`面板的复选框来启用或关闭它。
在启用Virtools Material后可以在`Basic Parameters``Advanced Parameters`中设置材质属性就像在Virtools中操作一般。
`Basic Parameters`是基础材质属性。`Advanced Parameters`则是与透明相关的材质属性,主要用于半透明柱子底部等。
`Basic Parameters`是基础材质属性。`Advanced Parameters`则是与透明相关的材质属性,主要用于半透明柱子底部等。
另外,`Basic Parameters`部分提供了预设功能允许用户使用一些预设的材质设置这些设置只影响4种基本颜色方便使用。
`Operation`中的`Apply Virtools Material`将把Virtools Material应用到Blender材质上。
`Parse from Blender Principled BSDF`将尝试将一个原理化BSDF转换为Virtools材质数据。
@ -117,21 +125,13 @@ Ballance 3D是一套简单的用于制图3D相关的轻型工具集合可以
### 按组选择
选择菜单中新增了项按照Virtools归组数据进行筛选的功能。
选择菜单中新增了项按照Virtools归组数据进行筛选的功能。
#### Select by Virtools Group
该功能首先有5种不同的选择策略与Blender的选择方法完全匹配开始、扩选、相减、反转、相交。只需像Blender选择那样使用它。
然后,选择你需要的组的名称,然后开始一次选择或筛选。
将对当前活动集合内的物体按照其Virtools Group属性进行选择
勾选`Ignore Hide Property`后,即使是隐藏的物体,也会被筛选。
勾选`Merge Selection`,将会把选中的物体合并到当前选定的内容中。
#### Filter by Virtools Group
将会按照Virtools Group属性过滤当前选中物体。
勾选`Reverse`将会反向操作,即去除掉符合条件的物体。
如果可以,请尽可能使用`Filter by Virtools Group`而不是`Select by Virtools Group`。因为这样可以避免分析过多的物体。
例如先选定一个大致的范围,然后使用`Filter by Virtools Group`过滤,比直接使用`Select by Virtools Group`效率更高。
如果可以,请尽可能使用相减或相交模式。因为这样可以避免分析过多的物体
例如先选定一个大致的范围,然后使用相交模式过滤,比直接使用开始模式效率更高。
### 快速归组

View File

@ -2,7 +2,7 @@ import bpy,bmesh,bpy_extras,mathutils
import pathlib,zipfile,time,os,tempfile,math
import struct, shutil
from bpy_extras import io_utils, node_shader_utils
from . import UTILS_constants, UTILS_functions, UTILS_file_io, UTILS_zip_helper, UTILS_virtools_prop
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):
"""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):
# 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
(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:
prefs = bpy.context.preferences.addons[__package__].preferences
@ -40,6 +46,12 @@ class BALLANCE_OT_export_bm(bpy.types.Operator, bpy_extras.io_utils.ExportHelper
export_bm(context, self.filepath,
prefs.no_component_collection,
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'}
def draw(self, context):
@ -161,14 +173,14 @@ def export_bm(context, bmx_filepath, prefs_fncg, opts_exportMode, opts_exportTar
mesh_faceIndexPairs = [(face, index) for index, face in enumerate(mesh.polygons)]
UTILS_file_io.write_uint32(fmesh, len(mesh_faceIndexPairs) * 3)
if mesh.uv_layers.active is not None:
uv_layer = mesh.uv_layers.active.data[:]
uv_layer = mesh.uv_layers.active.uv
for f, f_index in mesh_faceIndexPairs:
# it should be triangle face, otherwise throw a error
if (f.loop_total != 3):
raise Exception("Not a triangle", f.poly.loop_total)
for loop_index in range(f.loop_start, f.loop_start + f.loop_total):
uv = uv_layer[loop_index].uv
uv = uv_layer[loop_index].vector
# reverse v
UTILS_file_io.write_2vector(fmesh, uv[0], -uv[1])
else:
@ -190,6 +202,10 @@ def export_bm(context, bmx_filepath, prefs_fncg, opts_exportMode, opts_exportTar
mesh_usedBlenderMtl = mesh.materials[:]
mesh_noMaterial = len(mesh_usedBlenderMtl) == 0
for mat in mesh_usedBlenderMtl:
# skip empty mtl slot
if mat is None:
continue
# add into mtl set
if mat not in materialSet:
materialSet.add(mat)
materialList.append(mat)
@ -200,7 +216,10 @@ def export_bm(context, bmx_filepath, prefs_fncg, opts_exportMode, opts_exportTar
mesh_vIndex = []
for f, f_index in mesh_faceIndexPairs:
# confirm material use
if mesh_noMaterial:
# a face without mtl have 2 situations. first is the whole object do not have mtl
# another is this face use an empty mtl slot.
mesh_faceNoMtl = mesh_noMaterial or (mesh_usedBlenderMtl[f.material_index] is None)
if mesh_faceNoMtl:
mesh_materialIndex = 0
else:
mesh_materialIndex = materialList.index(mesh_usedBlenderMtl[f.material_index])
@ -223,7 +242,7 @@ def export_bm(context, bmx_filepath, prefs_fncg, opts_exportMode, opts_exportTar
mesh_vIndex[0], mesh_vtIndex[0], mesh_vnIndex[0])
# set used material
UTILS_file_io.write_bool(fmesh, not mesh_noMaterial)
UTILS_file_io.write_bool(fmesh, not mesh_faceNoMtl)
UTILS_file_io.write_uint32(fmesh, mesh_materialIndex)
# free splited normals
@ -299,6 +318,8 @@ def export_bm(context, bmx_filepath, prefs_fncg, opts_exportMode, opts_exportTar
# get absolute texture path
texture_filepath = io_utils.path_reference(texture.filepath, texture_blenderFilePath, utils_tempTextureFolder,
'ABSOLUTE', "", None, texture.library)
# texture_filepath = bpy.path.abspath(texture.filepath, start=texture_blenderFilePath, library=texture.library)
# get file name and write it
texture_filename = os.path.basename(texture_filepath)
UTILS_file_io.write_string(ftexture, texture_filename)

View File

@ -4,7 +4,7 @@ import struct, shutil
from bpy_extras import io_utils,node_shader_utils
from bpy_extras.io_utils import unpack_list
from bpy_extras.image_utils import load_image
from . import UTILS_constants, UTILS_functions, UTILS_file_io, UTILS_zip_helper, UTILS_virtools_prop
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):
"""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,
self.texture_conflict_strategy, self.material_conflict_strategy,
self.mesh_conflict_strategy, self.object_conflict_strategy)
self.report({'INFO'}, "BM File Import Finished.")
return {'FINISHED'}
@ -89,7 +91,7 @@ def import_bm(context, bmx_filepath, prefs_fncg, prefs_externalTexture, prefs_te
# clean temp folder, output error
UTILS_functions.show_message_box(
("Unsupported BM spec. Expect: {} Gotten: {}".format(UTILS_constants.bmfile_currentVersion, index_gottenVersion), ),
"Unsupported BM spec", 'ERROR')
"Unsupported BM spec", UTILS_icons_manager.blender_error_icon)
findex.close()
utils_tempFolderObj.cleanup()
return
@ -258,10 +260,10 @@ def import_bm(context, bmx_filepath, prefs_fncg, prefs_externalTexture, prefs_te
mesh_target.vertices.foreach_set("co", unpack_list(mesh_vList))
mesh_target.loops.foreach_set("vertex_index", unpack_list(_flat_vertices_index(mesh_faceList)))
mesh_target.loops.foreach_set("normal", unpack_list(_flat_vertices_normal(mesh_faceList, mesh_vnList)))
mesh_target.uv_layers[0].data.foreach_set("uv", unpack_list(_flat_vertices_uv(mesh_faceList, mesh_vtList)))
mesh_target.uv_layers[0].uv.foreach_set("vector", unpack_list(_flat_vertices_uv(mesh_faceList, mesh_vtList))) # Blender 3.5 CHANGED
for i in range(len(mesh_faceList)):
mesh_target.polygons[i].loop_start = i * 3
mesh_target.polygons[i].loop_total = 3
# mesh_target.polygons[i].loop_total = 3 # Blender 3.6 CHANGED
if mesh_faceList[i][9] != -1:
mesh_target.polygons[i].material_index = mesh_faceList[i][9]

View File

@ -5,7 +5,7 @@ class BALLANCE_OT_super_align(bpy.types.Operator):
"""Align object with 3ds Max style"""
bl_idname = "ballance.super_align"
bl_label = "3ds Max Align"
bl_options = {'UNDO'}
bl_options = {'REGISTER', 'UNDO'}
align_x: bpy.props.BoolProperty(name="X position")
align_y: bpy.props.BoolProperty(name="Y position")
@ -18,7 +18,8 @@ class BALLANCE_OT_super_align(bpy.types.Operator):
('POINT', "Center (axis)", ""),
('MAX', "Max", "")
),
)
default='POINT',
)
target_references: bpy.props.EnumProperty(
name="Target (Other Objects)",
@ -27,7 +28,8 @@ class BALLANCE_OT_super_align(bpy.types.Operator):
('POINT', "Center (axis)", ""),
('MAX', "Max", "")
),
)
default='POINT',
)
@classmethod
def poll(self, context):
@ -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)
return {'FINISHED'}
"""
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
"""
def draw(self, context):
layout = self.layout

View File

@ -1,26 +1,69 @@
import bpy,mathutils
import bmesh
import math
from . import UTILS_functions
class ScaleDataUnion(object):
def __init__(self):
self.UseRefPoint: bool = None
def SetAsScale(self, scale_num: float):
self.UseRefPoint: bool = False
self.ScaleSize: float = scale_num
def SetAsRefPoint(self, ref_point: int, ref_point_uv: float):
self.UseRefPoint: bool = True
self.ReferencePoint: int = ref_point
self.ReferenceUV: float = ref_point_uv
class BALLANCE_OT_flatten_uv(bpy.types.Operator):
"""Flatten selected face UV. Only works for convex face"""
bl_idname = "ballance.flatten_uv"
bl_label = "Flatten UV"
bl_options = {'UNDO'}
bl_options = {'REGISTER', 'UNDO'}
reference_edge : bpy.props.IntProperty(
name="Reference edge",
description="The references edge of UV. It will be placed in V axis.",
name="Reference Edge",
description="The references edge of UV.\nIt will be placed in V axis.",
min=0,
soft_min=0,
soft_max=3,
soft_min=0, soft_max=3,
default=0,
)
scale_mode: bpy.props.EnumProperty(
name="Scale Mode",
items=(('NUM', "Scale Size", "Scale UV with specific number."),
('REF', "Ref. Point", "Scale UV with Reference Point feature."),
),
)
scale_number : bpy.props.FloatProperty(
name="Scale Size",
description="The size which will be applied for scale.",
min=0,
soft_min=0, soft_max=5,
default=5.0,
step=0.1, precision=1,
)
reference_point : bpy.props.IntProperty(
name="Reference Point",
description="The references point of UV.\nIt's U component will be set to the number specified by Reference Point UV.\nThis point index is related to the start point of reference edge.",
min=2, # 0 and 1 is invalid. we can not order the reference edge to be set on the outside of uv axis
soft_min=2, soft_max=3,
default=2,
)
reference_uv : bpy.props.FloatProperty(
name="Reference Point UV",
description="The U component which should be applied to references point in UV.",
soft_min=0, soft_max=1,
default=0.5,
step=0.1, precision=2,
)
@classmethod
def poll(self, context):
obj = bpy.context.active_object
if obj == None:
if obj is None:
return False
if obj.type != 'MESH':
return False
@ -28,24 +71,38 @@ class BALLANCE_OT_flatten_uv(bpy.types.Operator):
return False
return True
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def execute(self, context):
no_processed_count = _real_flatten_uv(bpy.context.active_object.data, self.reference_edge)
# construct scale data
scale_data: ScaleDataUnion = ScaleDataUnion()
if self.scale_mode == 'NUM':
scale_data.SetAsScale(self.scale_number)
else:
scale_data.SetAsRefPoint(self.reference_point, self.reference_uv)
# do flatten uv and report
no_processed_count = _real_flatten_uv(bpy.context.active_object.data, self.reference_edge, scale_data)
if no_processed_count != 0:
UTILS_functions.show_message_box(
("{} faces may not be processed correctly because they have problem.".format(no_processed_count), ),
"Warning", 'ERROR'
)
print("[Flatten UV] {} faces may not be processed correctly because they have problem.".format(no_processed_count))
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.emboss = 'NORMAL'
layout.prop(self, "reference_edge")
def _real_flatten_uv(mesh, reference_edge):
layout.separator()
layout.label(text="Scale Mode")
layout.prop(self, "scale_mode", expand=True)
layout.separator()
layout.label(text="Scale Config")
if self.scale_mode == 'NUM':
layout.prop(self, "scale_number")
else:
layout.prop(self, "reference_point")
layout.prop(self, "reference_uv")
def _real_flatten_uv(mesh, reference_edge, scale_data: ScaleDataUnion):
no_processed_count = 0
if mesh.uv_layers.active is None:
@ -53,18 +110,44 @@ def _real_flatten_uv(mesh, reference_edge):
mesh.uv_layers.new(do_init=False)
bm = bmesh.from_edit_mesh(mesh)
uv_lay = bm.loops.layers.uv.active
uv_lay = bm.loops.layers.uv.active # NOTE: this is a part of bmesh. not affected by Blender 3.5 CHANGED.
for face in bm.faces:
# ========== only process selected face ==========
if not face.select:
continue
# ========== resolve reference edge and point ==========
# check reference validation
allPoint = len(face.loops)
if allPoint <= reference_edge:
no_processed_count+=1
if reference_edge >= allPoint: # reference edge overflow
no_processed_count += 1
continue
# get correct new corrdinate system
# check scale validation
if scale_data.UseRefPoint:
if ((scale_data.ReferencePoint <= 1) # reference point too low
or (scale_data.ReferencePoint >= allPoint)): # reference point overflow
no_processed_count += 1
continue
else:
if round(scale_data.ScaleSize, 7) == 0.0: # invalid scale size
no_processed_count += 1
continue
# ========== get correct new corrdinate system ==========
# yyc mark:
# we use 3 points located in this face to calc
# the base of this local uv corredinate system.
# however if this 3 points are set in a line,
# this method will cause a error, zero vector error.
#
# if z axis is zero vector, we will try using face normal instead
# to try getting correct data.
#
# zero base is not important. because it will not raise any math exception
# just a weird uv. user will notice this problem.
# get point
p1Relative = reference_edge
p2Relative = reference_edge + 1
p3Relative = reference_edge + 2
@ -73,17 +156,23 @@ def _real_flatten_uv(mesh, reference_edge):
if p3Relative >= allPoint:
p3Relative -= allPoint
p1=mathutils.Vector(tuple(face.loops[p1Relative].vert.co[x] for x in range(3)))
p2=mathutils.Vector(tuple(face.loops[p2Relative].vert.co[x] for x in range(3)))
p3=mathutils.Vector(tuple(face.loops[p3Relative].vert.co[x] for x in range(3)))
p1 = mathutils.Vector(tuple(face.loops[p1Relative].vert.co[x] for x in range(3)))
p2 = mathutils.Vector(tuple(face.loops[p2Relative].vert.co[x] for x in range(3)))
p3 = mathutils.Vector(tuple(face.loops[p3Relative].vert.co[x] for x in range(3)))
# get y axis
new_y_axis = p2 - p1
new_y_axis.normalize()
vec1 = p3 - p2
vec1.normalize()
# get z axis
new_z_axis = new_y_axis.cross(vec1)
new_z_axis.normalize()
if not any(round(v, 7) for v in new_z_axis): # if z is a zero vector, use face normal instead
new_z_axis = face.normal.normalized()
# get x axis
new_x_axis = new_y_axis.cross(new_z_axis)
new_x_axis.normalize()
@ -93,28 +182,59 @@ def _real_flatten_uv(mesh, reference_edge):
(0, 1.0, 0),
(0, 0, 1.0)
))
origin_base.invert()
origin_base.invert_safe()
new_base = mathutils.Matrix((
(new_x_axis.x, new_y_axis.x, new_z_axis.x),
(new_x_axis.y, new_y_axis.y, new_z_axis.y),
(new_x_axis.z, new_y_axis.z, new_z_axis.z)
))
transition_matrix = origin_base @ new_base
transition_matrix.invert()
transition_matrix.invert_safe()
# process each face
# ========== rescale correction ==========
if scale_data.UseRefPoint:
# ref point method
# get reference point from loop
refpRelative = p1Relative + scale_data.ReferencePoint
if refpRelative >= allPoint:
refpRelative -= allPoint
pRef = mathutils.Vector(tuple(face.loops[refpRelative].vert.co[x] for x in range(3))) - p1
# calc its U component
vec_u = abs((transition_matrix @ pRef).x)
if round(vec_u, 7) == 0.0:
rescale = 1 # fallback. rescale = 1 will not affect anything
else:
rescale = scale_data.ReferenceUV / vec_u
else:
# scale size method
# apply rescale directly
rescale = 1.0 / scale_data.ScaleSize
# construct matrix
# we only rescale U component (X component)
# and 5.0 scale for V component (Y component)
scale_matrix = mathutils.Matrix((
(rescale, 0, 0),
(0, 1.0 / 5.0, 0),
(0, 0, 1.0)
))
# order can not be changed. we order do transition first, then scale it.
rescale_transition_matrix = scale_matrix @ transition_matrix
# ========== process each face ==========
for loop_index in range(allPoint):
pp = mathutils.Vector(tuple(face.loops[loop_index].vert.co[x] for x in range(3)))
vec = pp-p1
new_vec = transition_matrix @ vec
pp = mathutils.Vector(tuple(face.loops[loop_index].vert.co[x] for x in range(3))) - p1
ppuv = rescale_transition_matrix @ pp
# y axis always use 5.0 to scale
# however, x need use custom scale correction which has been calculated by our matrix.
face.loops[loop_index][uv_lay].uv = (
(new_vec.x if new_vec.x >=0 else -new_vec.x) / 5,
(new_vec.y) / 5
abs(ppuv.x),
ppuv.y
)
# Show the updates in the viewport
bmesh.update_edit_mesh(mesh)
return no_processed_count

View File

@ -1,7 +1,7 @@
import bpy,bmesh
import mathutils
import bpy.types
from . import UTILS_functions
from . import UTILS_functions, UTILS_icons_manager
class BALLANCE_OT_rail_uv(bpy.types.Operator):
"""Create a UV for rail"""
@ -47,7 +47,7 @@ class BALLANCE_OT_rail_uv(bpy.types.Operator):
def execute(self, context):
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:
_create_rail_uv(self.uv_type, context.scene.BallanceBlenderPluginProperty.material_picker, self.uv_scale, self.projection_axis)
return {'FINISHED'}
@ -141,15 +141,17 @@ def _create_rail_uv(rail_type, material_pointer, scale_size, projection_axis):
)
real_scale = 1.0 / maxLength
uv_layer = mesh.uv_layers.active.data
# Blender 3.5 CHANGED: mesh.uv_layers.active.data -> mesh.uv_layers.active.uv
# .uv -> .vector
uv_layer = mesh.uv_layers.active.uv
for poly in mesh.polygons:
for loop_index in range(poly.loop_start, poly.loop_start + poly.loop_total):
# get correspond vec index
index = mesh.loops[loop_index].vertex_index
if rail_type == 'POINT':
# set to 1 point
uv_layer[loop_index].uv[0] = 0
uv_layer[loop_index].uv[1] = 1
uv_layer[loop_index].vector[0] = 0
uv_layer[loop_index].vector[1] = 1
elif rail_type == 'SCALE' or rail_type == 'UNIFORM':
# following xy -> uv scale
#
@ -157,16 +159,16 @@ def _create_rail_uv(rail_type, material_pointer, scale_size, projection_axis):
# use X axis: Y->U Z->V
# use Y axis: X->U Z->V
if projection_axis == 'X':
uv_layer[loop_index].uv[0] = vecList[index].co[1] * real_scale
uv_layer[loop_index].uv[1] = vecList[index].co[2] * real_scale
uv_layer[loop_index].vector[0] = vecList[index].co[1] * real_scale
uv_layer[loop_index].vector[1] = vecList[index].co[2] * real_scale
elif projection_axis == 'Y':
uv_layer[loop_index].uv[0] = vecList[index].co[0] * real_scale
uv_layer[loop_index].uv[1] = vecList[index].co[2] * real_scale
uv_layer[loop_index].vector[0] = vecList[index].co[0] * real_scale
uv_layer[loop_index].vector[1] = vecList[index].co[2] * real_scale
elif projection_axis == 'Z':
uv_layer[loop_index].uv[0] = vecList[index].co[0] * real_scale
uv_layer[loop_index].uv[1] = vecList[index].co[1] * real_scale
uv_layer[loop_index].vector[0] = vecList[index].co[0] * real_scale
uv_layer[loop_index].vector[1] = vecList[index].co[1] * real_scale
elif rail_type == 'TT':
(uv_layer[loop_index].uv[0], uv_layer[loop_index].uv[1]) = _tt_reflection_mapping_compute(
(uv_layer[loop_index].vector[0], uv_layer[loop_index].vector[1]) = _tt_reflection_mapping_compute(
vecList[index].co,
mesh.loops[loop_index].normal,
(0.0, 0.0, 0.0)
@ -175,7 +177,7 @@ def _create_rail_uv(rail_type, material_pointer, scale_size, projection_axis):
if len(ignoredObj) != 0:
UTILS_functions.show_message_box(
("Following objects are not processed due to they are not suit for this function now: ", ) + tuple(ignoredObj),
"Execution result", 'INFO'
"Execution result", UTILS_icons_manager.blender_info_icon
)
def _tt_reflection_mapping_compute(_point, _n, _refobj):

View File

@ -1,5 +1,5 @@
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):
name_standard: bpy.props.EnumProperty(
@ -69,6 +69,52 @@ class BALLANCE_OT_auto_grouping(rename_system_props):
# ==========================================
# 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():
COMPONENT = 0
@ -140,12 +186,12 @@ def _get_sector_from_ckgroup(group_set):
# YYC Tools Chains name standard is Ballance-compatible name standard.
# So this functions also serving for `_get_name_info_from_group` function
# to help get sector field from PC/PR elements. In ordinary call(external call)
# The final error output should be outputed nromally. But in the call from
# 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.
# So parameter `call_internal` is served for this work. In common it is False
# to let function output error str normally. But only set it to True in
# the call from `_get_name_info_from_group` to disable error output.
def _get_name_info_from_yyc_name(obj_name, call_internal = False):
def _get_name_info_from_yyc_name(obj_name, err_reporter: _RenameErrorReporter, call_internal = False):
# check component first
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
if not call_internal:
print("[ERROR]\t{}:\tName match lost.".format(obj_name))
err_reporter.add_error("Name match lost.")
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
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_"):
return _NameInfoHelper(_ObjectBasicType.DECORATION)
print("[ERROR]\t{}:\tName match lost.".format(obj_name))
err_reporter.add_error("Name match lost.")
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)
if len(group_list) == 0:
# 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
# use _get_name_info_from_yyc_name to get it
# _get_name_info_from_yyc_name is Ballance-compatible name standard
data = _get_name_info_from_yyc_name(obj.name, call_internal=True)
data = _get_name_info_from_yyc_name(obj.name, err_reporter, call_internal=True)
if data is None:
print("[ERROR]\t{}:\tPC_Checkpoints or PR_Resetpoints detected. But couldn't get sector from name.".format(obj.name))
err_reporter.add_error("PC_Checkpoints or PR_Resetpoints detected. But couldn't get sector from name.")
return None
if data.basic_type != _ObjectBasicType.CHECKPOINT and data.basic_type != _ObjectBasicType.RESETPOINT:
# check whether it is checkpoint or resetpoint
# if not, it mean that we got error data from name
# return None instead
print("[ERROR]\t{}:\tPC_Checkpoints or PR_Resetpoints detected. But name is illegal.".format(obj.name))
err_reporter.add_error("PC_Checkpoints or PR_Resetpoints detected. But name is illegal.")
return None
# otherwise return data
return data
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
elif len(set_result) != 0:
# 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
# distinguish normal elements
@ -291,7 +337,7 @@ def _get_name_info_from_group(obj):
gotten_sector = _get_sector_from_ckgroup(group_set)
if gotten_sector is None:
# fail to get sector
print("[ERROR]\t{}:\tComponent detected. But couldn't get sector from CKGroup data.".format(obj.name))
err_reporter.add_error("Component detected. But couldn't get sector from CKGroup data.")
return None
data = _NameInfoHelper(_ObjectBasicType.COMPONENT)
@ -300,7 +346,7 @@ def _get_name_info_from_group(obj):
return data
elif len(set_result) != 0:
# 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
# distinguish road
@ -316,7 +362,7 @@ def _get_name_info_from_group(obj):
elif len(floor_result) == 0 and len(rail_result) > 0:
return _NameInfoHelper(_ObjectBasicType.WOOD)
else:
print("[WARNING]\t{}:\tCan't distinguish between Floors and Rails. Suppose it is Floors".format(obj.name))
err_reporter.add_warning("Can't distinguish object between Floors and Rails. Suppose it is Floors.")
return _NameInfoHelper(_ObjectBasicType.FLOOR)
elif 'Phys_FloorStopper' in group_set:
return _NameInfoHelper(_ObjectBasicType.STOPPER)
@ -324,10 +370,10 @@ def _get_name_info_from_group(obj):
return _NameInfoHelper(_ObjectBasicType.DEPTH_CUBE)
# no matched
print("[ERROR]\t{}:\tGroup match lost.".format(obj.name))
err_reporter.add_error("Group match lost.")
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
if basic_type == _ObjectBasicType.DECORATION:
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)
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
if basic_type == _ObjectBasicType.DECORATION:
obj.name = "O_"
@ -388,7 +434,7 @@ def _set_for_imengyu_name(obj, name_info):
# NOTE: the implement of this function are copied from
# BallanceVirtoolsHelper/bvh/features/mapping/grouping.cpp
def _set_for_group(obj, name_info):
def _set_for_group(obj, name_info, err_reporter: _RenameErrorReporter):
gps = []
basic_type = name_info.basic_type
@ -441,23 +487,23 @@ def _set_for_group(obj, name_info):
# ==========================================
# assemble funcs
def _get_data(obj, standard):
def _get_data(obj, standard, err_reporter: _RenameErrorReporter):
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:
return _get_name_info_from_imengyu_name(obj.name)
return _get_name_info_from_imengyu_name(obj.name, err_reporter)
elif standard == _NameStandard.CKGROUP:
return _get_name_info_from_group(obj)
return _get_name_info_from_group(obj, err_reporter)
else:
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:
return _set_for_yyc_name(obj, name_info)
return _set_for_yyc_name(obj, name_info, err_reporter)
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:
return _set_for_group(obj, name_info)
return _set_for_group(obj, name_info, err_reporter)
else:
raise Exception("Unknow standard")
@ -467,23 +513,43 @@ def _rename_core(source_std, dest_std):
# we do not to do anything
return
# create fail counter and error reporter
failed_obj_counter = 0
all_obj_counter = 0
err_reporter = _RenameErrorReporter()
print('============')
print('Rename system report')
print('Rename System Report')
print('------------')
for obj in _get_selected_objects():
# set counter and name
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:
failed_obj_counter += 1
continue
else:
_set_data(obj, info, dest_std, err_reporter)
# refresh obj name
new_name = obj.name
_set_data(obj, info, dest_std)
# 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()
print('------------')
print('All/failed - {}/{}'.format(all_obj_counter, failed_obj_counter))
print('All / Failed - {} / {}'.format(all_obj_counter, failed_obj_counter))
print('============')
UTILS_functions.show_message_box(
@ -491,6 +557,5 @@ def _rename_core(source_std, dest_std):
'View console to get more detail',
'All: {}'.format(all_obj_counter),
'Failed: {}'.format(failed_obj_counter)),
"Info",
"INFO"
"Info", UTILS_icons_manager.blender_error_icon
)

View File

@ -1,23 +1,10 @@
import bpy, mathutils
from . import UTILS_constants, UTILS_functions
# ================================================= actual add
class BALLANCE_OT_add_components(bpy.types.Operator):
"""Add sector related elements"""
bl_idname = "ballance.add_components"
bl_label = "Add elements"
bl_options = {'UNDO'}
elements_type: bpy.props.EnumProperty(
name="Type",
description="This element type",
items=tuple(map(lambda x: (x, x, ""), UTILS_constants.bmfile_componentList)),
)
from . import UTILS_constants, UTILS_functions, UTILS_icons_manager
# =============== Common Class ================
class common_add_component_props(bpy.types.Operator):
attentionElements = ("PC_TwoFlames", "PR_Resetpoint")
uniqueElements = ("PS_FourFlames", "PE_Balloon")
canDuplicatedElements = ('P_Extra_Point', 'P_Modul_18', 'P_Modul_26')
elements_sector: bpy.props.IntProperty(
name="Sector",
@ -26,41 +13,46 @@ class BALLANCE_OT_add_components(bpy.types.Operator):
default=1,
)
elements_duplicated: bpy.props.BoolProperty(
name="Duplicated",
description="Whether duplicate elements (Nong xxx / 脓xxx)",
default=False,
)
elements_dup_times: bpy.props.IntProperty(
name="Duplication Times",
description="How many this element should be duplicated.",
min=1, max=64,
soft_min=1, soft_max=32,
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_label = "Add Elements"
bl_options = {'UNDO'}
elements_type: bpy.props.EnumProperty(
name="Type",
description="This element type",
#items=tuple(map(lambda x: (x, x, ""), UTILS_constants.bmfile_componentList)),
items=tuple(
# token, display name, descriptions, icon, index
(blk, blk, "", UTILS_icons_manager.get_element_icon(blk), idx)
for idx, blk in enumerate(UTILS_constants.bmfile_componentList)
),
)
def execute(self, context):
# get name
if self.elements_type in self.uniqueElements:
finalObjectName = self.elements_type + "_01"
elif self.elements_type in self.attentionElements:
finalObjectName = self.elements_type + "_0" + str(self.elements_sector)
else:
finalObjectName = self.elements_type + "_0" + str(self.elements_sector) + "_"
finalObjectName = self.get_component_name(self.elements_type)
# create object
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)
# extra duplication
if self.elements_type in self.canDuplicatedElements:
for i in range(self.elements_dup_times - 1):
obj = bpy.data.objects.new(finalObjectName, loadedMesh)
UTILS_functions.add_into_scene_and_move_to_cursor(obj)
return {'FINISHED'}
def invoke(self, context, event):
@ -71,16 +63,172 @@ class BALLANCE_OT_add_components(bpy.types.Operator):
layout = self.layout
# attension notice
if self.elements_type in self.attentionElements:
layout.label(text="Please note that sector is suffix.")
if self.elements_type in self.canDuplicatedElements:
layout.label(text="This element can use duplication feature.")
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")
if self.elements_type not in self.uniqueElements:
layout.prop(self, "elements_sector")
self.parent_draw(layout, self.elements_type)
if self.elements_type in self.canDuplicatedElements:
layout.separator()
layout.prop(self, "elements_duplicated")
layout.prop(self, "elements_dup_times")
@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)
UTILS_functions.add_into_scene_and_move_to_cursor(obj)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
layout.prop(self, "elements_type")
self.parent_draw(layout, self.elements_type)
layout.prop(self, "elements_dup_times")
@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.io_utils import unpack_list
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):
"""Add Ballance floor"""
bl_idname = "ballance.add_floors"
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(
name="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(
@ -40,26 +67,30 @@ class BALLANCE_OT_add_floors(bpy.types.Operator):
)
use_2d_top : bpy.props.BoolProperty(
name="Top edge"
name="Top edge",
default=True
)
use_2d_right : bpy.props.BoolProperty(
name="Right edge"
name="Right edge",
default=False
)
use_2d_bottom : bpy.props.BoolProperty(
name="Bottom edge"
name="Bottom edge",
default=True
)
use_2d_left : bpy.props.BoolProperty(
name="Left edge"
name="Left edge",
default=True
)
use_3d_top : bpy.props.BoolProperty(
name="Top face"
name="Top face",
default=True
)
use_3d_bottom : bpy.props.BoolProperty(
name="Bottom face"
name="Bottom face",
default=True
)
previous_floor_type = ''
@classmethod
def poll(self, context):
prefs = bpy.context.preferences.addons[__package__].preferences
@ -115,25 +146,25 @@ class BALLANCE_OT_add_floors(bpy.types.Operator):
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
# yyc marked. Blumia reported.
# 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):
# get floor prototype
floor_prototype = UTILS_constants.floor_blockDict[self.floor_type]
# try sync default value
if self.previous_floor_type != self.floor_type:
self.previous_floor_type = self.floor_type
default_sides = floor_prototype['DefaultSideConfig']
self.use_2d_top = default_sides['UseTwoDTop']
self.use_2d_right = default_sides['UseTwoDRight']
self.use_2d_bottom = default_sides['UseTwoDBottom']
self.use_2d_left = default_sides['UseTwoDLeft']
self.use_3d_top = default_sides['UseThreeDTop']
self.use_3d_bottom = default_sides['UseThreeDBottom']
# show property
layout = self.layout
col = layout.column()
@ -149,12 +180,13 @@ class BALLANCE_OT_add_floors(bpy.types.Operator):
col.prop(self, "expand_length_2")
col.label(text="Unit size: " + floor_prototype['UnitSize'])
col.label(text="Expand mode: " + floor_prototype['ExpandType'])
grids = col.grid_flow(row_major=True, columns=3)
grids = col.grid_flow(row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
grids.alignment = 'CENTER'
grids.separator()
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][0])
grids.separator()
grids.label(text=UTILS_constants.floor_expandDirectionMap[floor_prototype['InitColumnDirection']][floor_prototype['ExpandType']][3])
grids.template_icon(icon_value = UTILS_constants.icons_floorDict[self.floor_type])
grids.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.separator()
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.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.prop(self, "use_2d_top")
grids.separator()
grids.prop(self, "use_2d_left")
grids.template_icon(icon_value = UTILS_constants.icons_floorDict[self.floor_type])
grids.template_icon(icon_value = UTILS_icons_manager.get_floor_icon(self.floor_type))
grids.prop(self, "use_2d_right")
grids.separator()
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']['specular'], try_item['data']['emissive'],
try_item['data']['power'],
False, False, False, False,
False, False, True, False,
texture)
)
break
@ -436,13 +469,18 @@ def _load_basic_floor(mesh, floor_type, rotation, height_multiplier, d1, d2, sid
_virtual_foreach_set(mesh.vertices, "co", global_offset_vec, vecList)
_virtual_foreach_set(mesh.loops, "vertex_index", global_offset_loops, faceList)
_virtual_foreach_set(mesh.loops, "normal", global_offset_loops, normalList)
_virtual_foreach_set(mesh.uv_layers[0].data, "uv", global_offset_loops, uvList)
# Blender 3.5 CHANGED. MeshUVLoop is deprecated and removed in 4.0
# See https://wiki.blender.org/wiki/Reference/Release_Notes/3.5/Python_API
# use MeshUVLoopLayer.uv[i].vector instead. MeshUVLoopLayer can be fetched from `mesh.uv_layers[i]` or `mesh.uv_layers.active`
_virtual_foreach_set(mesh.uv_layers[0].uv, "vector", global_offset_loops, uvList)
cache_counter = 0
for i in range(len(faceMatList)):
indCount = faceIndList[i]
mesh.polygons[i + global_offset_polygons].loop_start = global_offset_loops + cache_counter
mesh.polygons[i + global_offset_polygons].loop_total = indCount
# Blender 3.6 CHANGED. loop_total is readonly now. the count of consumed vertices is decided by next loop's loop_start
# See: https://wiki.blender.org/wiki/Reference/Release_Notes/3.6/Python_API
# mesh.polygons[i + global_offset_polygons].loop_total = indCount
mesh.polygons[i + global_offset_polygons].material_index = faceMatList[i]
mesh.polygons[i + global_offset_polygons].use_smooth = True
cache_counter += indCount

View File

@ -10,9 +10,11 @@ class BALLANCE_OT_add_rails(bpy.types.Operator):
rail_type: bpy.props.EnumProperty(
name="Type",
description="Rail type",
items=(('MONO', "Monorail", ""),
('DOUBLE', "Rail", ""),
),
items=(
('MONO', "Monorail", ""),
('DOUBLE', "Rail", ""),
),
default='DOUBLE',
)
rail_radius: bpy.props.FloatProperty(
@ -38,6 +40,11 @@ class BALLANCE_OT_add_rails(bpy.types.Operator):
# merge
firstObj = _merge_two_circle(firstObj, secondObj)
# rename
if self.rail_type == 'DOUBLE':
firstObj.name = "A_Rail_"
else:
firstObj.name = "A_Rail_Mono_"
# apply 3d cursor
UTILS_functions.move_to_cursor(firstObj)
@ -89,6 +96,8 @@ class BALLANCE_OT_add_tunnels(bpy.types.Operator):
# merge
firstObj = _merge_two_circle(firstObj, secondObj)
# rename
firstObj.name = "A_Rail_Tunnel_"
# apply 3d cursor
UTILS_functions.move_to_cursor(firstObj)

View File

@ -1,5 +1,5 @@
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_select_virtools_group(UTILS_virtools_prop.common_group_name_props):
"""Select objects by Virtools Group."""
@ -7,32 +7,55 @@ class BALLANCE_OT_select_virtools_group(UTILS_virtools_prop.common_group_name_pr
bl_label = "Select by Virtools Group"
bl_options = {'UNDO'}
merge_selection: bpy.props.BoolProperty(
name="Merge Selection",
description="Merge selection, rather than re-select them.",
default=False,
)
ignore_hide: bpy.props.BoolProperty(
name="Ignore Hide Property",
description="Select objects without considering visibility.",
default=False,
selection_type: bpy.props.EnumProperty(
name="Mode",
description="Selection mode",
items=(
('SET', "Set", "Sets a new selection.", "SELECT_SET", 0),
('EXTEND', "Extend", "Adds newly selected items to the existing selection.", "SELECT_EXTEND", 1),
('SUBTRACT', "Subtract", "Removes newly selected items from the existing selection.", "SELECT_SUBTRACT", 2),
('DIFFERENCE', "Invert", "Inverts the selection.", "SELECT_DIFFERENCE", 3),
('INTERSECT', "Intersect", "Selects items that intersect with the existing selection.", "SELECT_INTERSECT", 4),
),
default='SET'
)
def execute(self, context):
# iterate object
for obj in bpy.context.scene.objects:
# ignore hidden objects
if (not self.ignore_hide) and obj.hide_get() == True:
continue
if self.selection_type == 'SET':
# iterate object
for obj in bpy.context.scene.objects:
# check group and decide whether select this obj
obj.select_set(UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string()))
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()):
obj.select_set(True)
elif self.selection_type == 'SUBTRACT':
# subtract only involving selected item. so we get selected objest first
# 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)
# check group
if UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string()):
# select object
obj.select_set(True)
else:
# if not in merge mode, deselect them
if not self.merge_selection:
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)
return {'FINISHED'}
@ -40,63 +63,13 @@ class BALLANCE_OT_select_virtools_group(UTILS_virtools_prop.common_group_name_pr
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, 'ignore_hide')
row.prop(self, 'merge_selection')
layout.label(text='Selection Parameters')
layout.prop(self, 'selection_type', expand=True, icon_only=True)
layout.separator()
layout.label(text='Group Parameters')
self.parent_draw(layout)
class BALLANCE_OT_filter_virtools_group(UTILS_virtools_prop.common_group_name_props):
"""Filter objects by Virtools Group."""
bl_idname = "ballance.filter_virtools_group"
bl_label = "Filter by Virtools Group"
bl_options = {'UNDO'}
reverse_selection: bpy.props.BoolProperty(
name="Reverse",
description="Reverse operation. Remove matched objects.",
default=False,
)
ignore_hide: bpy.props.BoolProperty(
name="Ignore Hide Property",
description="Select objects without considering visibility.",
default=False,
)
def execute(self, context):
# make a copy for all objects, to ensure it is not viotile
# becuase we need deselect some objects in for statement
selected = bpy.context.selected_objects[:]
# iterate object
for obj in selected:
# ignore hidden objects
if (not self.ignore_hide) and obj.hide_get() == True:
continue
# check group and decide select
is_selected = UTILS_virtools_prop.check_virtools_group_data(obj, self.get_group_name_string())
if self.reverse_selection:
is_selected = not is_selected
# select object
obj.select_set(is_selected)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
row = layout.row()
row.prop(self, 'ignore_hide')
row.prop(self, 'reverse_selection')
layout.separator()
self.parent_draw(layout)
class BALLANCE_OT_ctx_set_group(UTILS_virtools_prop.common_group_name_props):
"""Grouping selected objects"""
bl_idname = "ballance.ctx_set_group"
@ -118,7 +91,10 @@ class BALLANCE_OT_ctx_set_group(UTILS_virtools_prop.common_group_name_props):
# throw a warning if some objects have duplicated group
if has_duplicated:
UTILS_functions.show_message_box(("Some objects have duplicated group name.", "These objects have been omitted.", ), "Duplicated Group", 'ERROR')
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'}
@ -151,7 +127,10 @@ class BALLANCE_OT_ctx_unset_group(UTILS_virtools_prop.common_group_name_props):
# throw a warning if some objects have duplicated group
if lack_group:
UTILS_functions.show_message_box(("Some objects lack specified group name.", "These objects have been omitted.", ), "Lack Group", 'ERROR')
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'}
@ -169,11 +148,14 @@ class BALLANCE_OT_ctx_clear_group(bpy.types.Operator):
def poll(self, context):
return len(bpy.context.selected_objects) != 0
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_confirm(self, event)
def execute(self, context):
# iterate object
for obj in bpy.context.selected_objects:
UTILS_virtools_prop.clear_virtools_group_data(obj)
return {'FINISHED'}

View File

@ -1,5 +1,5 @@
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(UTILS_virtools_prop.common_group_name_props):
"""Add a Virtools Group for Active Object."""
@ -15,7 +15,7 @@ class BALLANCE_OT_add_virtools_group(UTILS_virtools_prop.common_group_name_props
# try adding
obj = context.object
if not UTILS_virtools_prop.add_virtools_group_data(obj, self.get_group_name_string()):
UTILS_functions.show_message_box(("Group name is duplicated!", ), "Duplicated Name", 'ERROR')
UTILS_functions.show_message_box(("Group name is duplicated!", ), "Duplicated Name", UTILS_icons_manager.blender_error_icon)
return {'FINISHED'}
@ -44,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)))
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):
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):
"""Show Virtools Group Properties."""
@ -71,3 +91,5 @@ class BALLANCE_PT_virtools_group(bpy.types.Panel):
col = row.column(align=True)
col.operator(BALLANCE_OT_add_virtools_group.bl_idname, icon='ADD', text="")
col.operator(BALLANCE_OT_rm_virtools_group.bl_idname, icon='REMOVE', text="")
col.separator()
col.operator(BALLANCE_OT_clear_virtools_group.bl_idname, icon='TRASH', text="")

View File

@ -1,5 +1,5 @@
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):
"""Apply Virtools Material to Blender Material."""
@ -19,7 +19,7 @@ class BALLANCE_OT_apply_virtools_material(bpy.types.Operator):
if mtl_data[0]:
UTILS_functions.create_material_nodes(mtl, mtl_data)
else:
UTILS_functions.show_message_box(("Virtools Material is not enabled.", ), "Apply Failed", 'ERROR')
UTILS_functions.show_message_box(("Virtools Material is not enabled.", ), "Apply Failed", UTILS_icons_manager.blender_error_icon)
return {'FINISHED'}
@ -37,12 +37,54 @@ class BALLANCE_OT_parse_virtools_material(bpy.types.Operator):
mtl = context.material
mtl_data = UTILS_functions.parse_material_nodes(mtl)
if mtl_data is None:
UTILS_functions.show_message_box(("Fail to parse Principled BSDF.", ), "Parsing Failed", 'ERROR')
UTILS_functions.show_message_box(("Fail to parse Principled BSDF.", ), "Parsing Failed", UTILS_icons_manager.blender_error_icon)
else:
UTILS_virtools_prop.set_virtools_material_data(mtl, mtl_data)
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):
"""Show Virtools Material Properties."""
bl_label = "Virtools Material"
@ -69,7 +111,9 @@ class BALLANCE_PT_virtools_material(bpy.types.Panel):
layout.enabled = target.enable_virtools_material
# 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, 'diffuse')
layout.prop(target, 'specular')
@ -86,6 +130,6 @@ class BALLANCE_PT_virtools_material(bpy.types.Panel):
layout.separator()
layout.label(text="Operations")
layout.operator("ballance.apply_virtools_material", icon="NODETREE")
layout.operator("ballance.parse_virtools_material", icon="HIDE_OFF")
layout.operator(BALLANCE_OT_apply_virtools_material.bl_idname, icon="NODETREE")
layout.operator(BALLANCE_OT_parse_virtools_material.bl_idname, icon="HIDE_OFF")

View File

@ -169,13 +169,14 @@ floor_textureReflactionMap = {
"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 = [
{
"human-readable": "Floor Side",
"member": [
"FloorSide",
"FloorTopBorder_ForSide",
"FloorTopBorderless_ForSide"
"FloorSide",
"FloorTopBorder_ForSide",
"FloorTopBorderless_ForSide"
],
"data": {
"ambient": (0, 0, 0),
@ -186,12 +187,13 @@ floor_materialStatistic = [
}
},
{
"human-readable": "Floor Top",
"member": [
"FloorTopBorder",
"FloorTopBorderless",
"FloorTopFlat",
"FloorTopProfil",
"FloorTopProfilFlat"
"FloorTopBorder",
"FloorTopBorderless",
"FloorTopFlat",
"FloorTopProfil",
"FloorTopProfilFlat"
],
"data": {
"ambient": (0, 0, 0),
@ -202,8 +204,9 @@ floor_materialStatistic = [
}
},
{
"human-readable": "Transform Paper",
"member": [
"BallPaper"
"BallPaper"
],
"data": {
"ambient": (25 / 255.0, 25 / 255.0, 25 / 255.0),
@ -213,10 +216,11 @@ floor_materialStatistic = [
"power": 0
}
},
{
{
"human-readable": "Transform Stone & Wood",
"member": [
"BallStone",
"BallWood"
"BallStone",
"BallWood"
],
"data": {
"ambient": (25 / 255.0, 25 / 255.0, 25 / 255.0),
@ -225,6 +229,45 @@ floor_materialStatistic = [
"emissive": (60 / 255.0, 60 / 255.0, 60 / 255.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_blockDict[item["Type"]] = item
icons_floor = None
icons_floorDict = {}
# blenderIcon_elements = None
# blenderIcon_elements_dict = {}
rename_normalComponentsGroupName = set([
"P_Extra_Life",
"P_Extra_Point",

View File

@ -105,7 +105,7 @@ def parse_material_nodes(mtl):
# return value
return (True,
mtl_ambient, mtl_diffuse, mtl_specular, mtl_emissive, mtl_specularPower,
False, False, False, False,
False, False, True, False,
mtl_texture
)
@ -116,8 +116,21 @@ def parse_material_nodes(mtl):
# load component
def load_component(component_id):
# get file first
# get component name from 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(
os.path.dirname(__file__),
'meshes',
@ -126,9 +139,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.
fmesh = open(selected_file, 'rb')
# create real mesh, we don't need to consider name. blender will solve duplicated name
mesh = bpy.data.meshes.new('mesh_' + component_name)
vList = []
vnList = []
@ -168,8 +178,7 @@ def load_component(component_id):
mesh.loops.foreach_set("normal", unpack_list(_flat_component_vertices_normal(faceList, vnList)))
for i in range(len(faceList)):
mesh.polygons[i].loop_start = i * 3
mesh.polygons[i].loop_total = 3
# mesh.polygons[i].loop_total = 3 # Blender 3.6 CHANGED
mesh.polygons[i].use_smooth = True
mesh.validate(clean_customdata=False)
@ -204,6 +213,10 @@ def create_instance_with_option(instance_type, instance_name, instance_opt,
For object, you should provide `extra_mesh`.
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():

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
from . import UTILS_constants, UTILS_functions
from . import UTILS_constants, UTILS_functions, UTILS_icons_manager
class BALLANCE_PG_virtools_material(bpy.types.PropertyGroup):
enable_virtools_material: bpy.props.BoolProperty(
@ -7,29 +7,37 @@ class BALLANCE_PG_virtools_material(bpy.types.PropertyGroup):
default=False,
)
ambient: bpy.props.FloatVectorProperty(name="Ambient",
subtype='COLOR',
min=0.0,
max=1.0,
default=[0.0,0.0,0.0])
ambient: bpy.props.FloatVectorProperty(
name="Ambient",
subtype='COLOR',
min=0.0,
max=1.0,
default=[76 / 255, 76 / 255, 76 / 255]
)
diffuse: bpy.props.FloatVectorProperty(name="Diffuse",
subtype='COLOR',
min=0.0,
max=1.0,
default=[0.0,0.0,0.0])
diffuse: bpy.props.FloatVectorProperty(
name="Diffuse",
subtype='COLOR',
min=0.0,
max=1.0,
default=[178 / 255, 178 / 255, 178 / 255]
)
specular: bpy.props.FloatVectorProperty(name="Specular",
subtype='COLOR',
min=0.0,
max=1.0,
default=[0.0,0.0,0.0])
specular: bpy.props.FloatVectorProperty(
name="Specular",
subtype='COLOR',
min=0.0,
max=1.0,
default=[127 / 255, 127 / 255, 127 / 255]
)
emissive: bpy.props.FloatVectorProperty(name="Emissive",
subtype='COLOR',
min=0.0,
max=1.0,
default=[0.0,0.0,0.0])
emissive: bpy.props.FloatVectorProperty(
name="Emissive",
subtype='COLOR',
min=0.0,
max=1.0,
default=[0.0, 0.0, 0.0]
)
specular_power: bpy.props.FloatProperty(
name="Specular Power",
@ -53,7 +61,7 @@ class BALLANCE_PG_virtools_material(bpy.types.PropertyGroup):
z_buffer: bpy.props.BoolProperty(
name="Z Buffer",
description="ZFunc: VXCMP_LESSEQUAL.",
default=False,
default=True,
)
two_sided: bpy.props.BoolProperty(
@ -84,7 +92,12 @@ class common_group_name_props(bpy.types.Operator):
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((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(

View File

@ -2,10 +2,10 @@ bl_info={
"name":"Ballance Blender Plugin",
"description":"Ballance mapping tools for Blender",
"author":"yyc12345",
"version":(3,0),
"blender":(3,3,0),
"version":(3,3),
"blender":(3,6,0),
"category":"Object",
"support":"TESTING",
"support":"COMMUNITY",
"warning": "Please read document before using this plugin.",
"wiki_url": "https://github.com/yyc12345/BallanceBlenderHelper",
"tracker_url": "https://github.com/yyc12345/BallanceBlenderHelper/issues"
@ -13,9 +13,8 @@ bl_info={
# =============================================
# import system
import bpy, bpy_extras
import bpy.utils.previews
import os
import bpy
# import my code (with reload)
if "bpy" in locals():
import importlib
@ -33,6 +32,8 @@ if "bpy" in locals():
importlib.reload(UTILS_virtools_prop)
if "UTILS_safe_eval" in locals():
importlib.reload(UTILS_safe_eval)
if "UTILS_icons_manager" in locals():
importlib.reload(UTILS_icons_manager)
if "BMFILE_export" in locals():
importlib.reload(BMFILE_export)
@ -63,7 +64,7 @@ if "bpy" in locals():
if "PROPS_virtools_material" in locals():
importlib.reload(PROPS_virtools_material)
from . import UTILS_constants, UTILS_functions, UTILS_preferences, UTILS_virtools_prop, UTILS_safe_eval
from . import UTILS_constants, UTILS_functions, UTILS_preferences, UTILS_virtools_prop, UTILS_safe_eval, UTILS_icons_manager
from . import BMFILE_export, BMFILE_import
from . import MODS_3dsmax_align, MODS_flatten_uv, MODS_rail_uv
from . import OBJS_add_components, OBJS_add_floors, OBJS_add_rails, OBJS_group_opers
@ -86,7 +87,7 @@ class BALLANCE_MT_ThreeDViewerMenu(bpy.types.Menu):
layout.operator(MODS_flatten_uv.BALLANCE_OT_flatten_uv.bl_idname)
class BALLANCE_MT_AddFloorMenu(bpy.types.Menu):
"""Add Ballance floor"""
"""Add Ballance Floor"""
bl_idname = "BALLANCE_MT_AddFloorMenu"
bl_label = "Floors"
@ -97,7 +98,7 @@ class BALLANCE_MT_AddFloorMenu(bpy.types.Menu):
for item in UTILS_constants.floor_basicBlockList:
cop = layout.operator(
OBJS_add_floors.BALLANCE_OT_add_floors.bl_idname,
text=item, icon_value = UTILS_constants.icons_floorDict[item])
text=item, icon_value = UTILS_icons_manager.get_floor_icon(item))
cop.floor_type = item
layout.separator()
@ -105,11 +106,11 @@ class BALLANCE_MT_AddFloorMenu(bpy.types.Menu):
for item in UTILS_constants.floor_derivedBlockList:
cop = layout.operator(
OBJS_add_floors.BALLANCE_OT_add_floors.bl_idname,
text=item, icon_value = UTILS_constants.icons_floorDict[item])
text=item, icon_value = UTILS_icons_manager.get_floor_icon(item))
cop.floor_type = item
class BALLANCE_MT_AddRailMenu(bpy.types.Menu):
"""Add Ballance rail"""
"""Add Ballance Rail"""
bl_idname = "BALLANCE_MT_AddRailMenu"
bl_label = "Rails"
@ -118,6 +119,37 @@ class BALLANCE_MT_AddRailMenu(bpy.types.Menu):
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
@ -135,11 +167,14 @@ classes = (
BALLANCE_MT_ThreeDViewerMenu,
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_tunnels,
OBJS_add_floors.BALLANCE_OT_add_floors,
BALLANCE_MT_AddFloorMenu,
BALLANCE_MT_AddRailMenu,
BALLANCE_MT_AddElementsMenu,
NAMES_rename_system.BALLANCE_OT_rename_by_group,
NAMES_rename_system.BALLANCE_OT_convert_name,
@ -149,14 +184,15 @@ classes = (
UTILS_virtools_prop.BALLANCE_PG_virtools_group,
PROPS_virtools_group.BALLANCE_OT_add_virtools_group,
PROPS_virtools_group.BALLANCE_OT_rm_virtools_group,
PROPS_virtools_group.BALLANCE_OT_clear_virtools_group,
PROPS_virtools_group.BALLANCE_UL_virtools_group,
PROPS_virtools_group.BALLANCE_PT_virtools_group,
PROPS_virtools_material.BALLANCE_OT_apply_virtools_material,
PROPS_virtools_material.BALLANCE_OT_parse_virtools_material,
PROPS_virtools_material.BALLANCE_OT_preset_virtools_material,
PROPS_virtools_material.BALLANCE_PT_virtools_material,
OBJS_group_opers.BALLANCE_OT_select_virtools_group,
OBJS_group_opers.BALLANCE_OT_filter_virtools_group,
OBJS_group_opers.BALLANCE_OT_ctx_set_group,
OBJS_group_opers.BALLANCE_OT_ctx_unset_group,
OBJS_group_opers.BALLANCE_OT_ctx_clear_group,
@ -176,9 +212,10 @@ def menu_func_ballance_add(self, context):
layout.label(text="Ballance")
layout.menu(BALLANCE_MT_AddFloorMenu.bl_idname, icon='MESH_CUBE')
layout.menu(BALLANCE_MT_AddRailMenu.bl_idname, icon='MESH_CIRCLE')
layout.operator_menu_enum(
OBJS_add_components.BALLANCE_OT_add_components.bl_idname,
"elements_type", icon='MESH_ICOSPHERE', text="Elements")
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):
layout = self.layout
layout.separator()
@ -192,8 +229,7 @@ def menu_func_ballance_select(self, context):
layout = self.layout
layout.separator()
layout.label(text="Ballance")
layout.operator(OBJS_group_opers.BALLANCE_OT_select_virtools_group.bl_idname, icon='SELECT_SET')
layout.operator(OBJS_group_opers.BALLANCE_OT_filter_virtools_group.bl_idname, icon='FILTER')
layout.operator(OBJS_group_opers.BALLANCE_OT_select_virtools_group.bl_idname, icon='RESTRICT_SELECT_OFF')
def menu_func_ballance_grouping(self, context):
layout = self.layout
layout.separator()
@ -207,12 +243,7 @@ def menu_func_ballance_grouping(self, context):
def register():
# we need init all icon first
icon_path = os.path.join(os.path.dirname(__file__), "icons")
UTILS_constants.icons_floor = bpy.utils.previews.new()
for key, value in UTILS_constants.floor_blockDict.items():
blockIconName = "Ballance_FloorIcon_" + key
UTILS_constants.icons_floor.load(blockIconName, os.path.join(icon_path, "floor", value["BindingDisplayTexture"]), 'IMAGE')
UTILS_constants.icons_floorDict[key] = UTILS_constants.icons_floor[blockIconName].icon_id
#UTILS_icons_manager.register_icons()
for cls in classes:
bpy.utils.register_class(cls)
@ -253,7 +284,7 @@ def unregister():
bpy.utils.unregister_class(cls)
# 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__":
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