Compare commits
71 Commits
v4.0-alpha
...
v4.2-alpha
Author | SHA1 | Date | |
---|---|---|---|
dd64c0ce04 | |||
84f6baae6a | |||
5afbf679ed | |||
89f5b5990d | |||
b441063061 | |||
b96550ca5c | |||
cc74a2ca8b | |||
f9fe4af1fe | |||
4a96906002 | |||
c448216496 | |||
f10c273067 | |||
04aa879c22 | |||
4ffe29654b | |||
1d7ac76d0e | |||
77315ffbea | |||
0862ecd269 | |||
f4d3e48be2 | |||
6ae8899912 | |||
76f1cdc3c7 | |||
8105b110f2 | |||
acb87b3844 | |||
94d5c934c6 | |||
3372c7a4b7 | |||
4181096a9e | |||
89a5e6367b | |||
cb893b770a | |||
2f08455518 | |||
729e12ed7b | |||
fe47861bd0 | |||
c894d88c54 | |||
c8d59ef5f4 | |||
f5c50ae079 | |||
b1199f6a21 | |||
47a8d81ecd | |||
24ac07a3b3 | |||
b647d7256f | |||
421f01b3db | |||
f215d3487f | |||
1661f82a07 | |||
512e7e868b | |||
427bad4f6b | |||
209d212287 | |||
2271b0a621 | |||
6940428b88 | |||
aa602a7bb8 | |||
8588f097a2 | |||
5d8ffb7e48 | |||
270fddff52 | |||
084e7fbe61 | |||
190be6ec61 | |||
36e925101e | |||
84e7e8380f | |||
c58af8ce48 | |||
07298fd21c | |||
3a1a0fb0f6 | |||
3396947115 | |||
6cf2ab895d | |||
b039dd8b43 | |||
8bad1a487c | |||
0f18559d3f | |||
5a5053440c | |||
02a1222210 | |||
f8c344f65e | |||
0bec108dcb | |||
997839a187 | |||
da71d5560c | |||
02082bf99e | |||
c82c094519 | |||
8ec101e1f1 | |||
8499c25b67 | |||
200ac40648 |
6
.github/workflows/main.yml
vendored
@ -2,17 +2,17 @@ name: Publish docs via GitHub Pages
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- ng
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Deploy docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout ng
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ng
|
||||
ref: master
|
||||
|
||||
- name: Install MkDocs
|
||||
run: |
|
||||
|
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
## ===== Personal =====
|
||||
# Disable distribution build folder
|
||||
redist/
|
@ -4,11 +4,4 @@
|
||||
|
||||
BBP NG, abbr **B**allance **B**lender **P**lugin **N**ext **G**eneration.
|
||||
|
||||
## Brief Introduction
|
||||
|
||||
The next generation of original Ballance Blender Helper. This plugin is fully rewritten.
|
||||
This plugin still work in progress. The development will be pushed into `ng` branch in main repository. For legacy plugin user, please visit `master` branch directly.
|
||||
|
||||
## Develop Help
|
||||
|
||||
Use `pip install fake-bpy-module-latest==20230627` to install the type hint library for Blender. Because fake-bpy-module do not release official 3.6 package, we need install it by choosing the most closest version of Blender 3.6 release. That what I found.
|
||||
For an introduction to this plugin, installing it, compiling it, reporting bugs, etc., see the GitHub Page of this project: https://yyc12345.github.io/BallanceBlenderHelper
|
||||
|
@ -4,7 +4,4 @@
|
||||
|
||||
BBP NG,又名**B**allance **B**lender **P**lugin **N**ext **G**eneration(下一代Ballance Blender插件)。
|
||||
|
||||
## 简介
|
||||
|
||||
下一代的Ballance Blender插件。此插件完全重写了上一代插件。
|
||||
此插件仍然在开发过程中。开发内容会被推送到主仓库的`ng`分支中。对于旧插件的用户,请直接访问`master`分支。
|
||||
有关此插件的介绍,安装,编译,汇报错误等,请参阅本项目的GitHub Page页面:https://yyc12345.github.io/BallanceBlenderHelper
|
||||
|
19
bbp_ng/.gitignore
vendored
@ -1,15 +1,22 @@
|
||||
# My Ban
|
||||
PyBMap/*.dll
|
||||
PyBMap/*.so
|
||||
PyBMap/*.dylib
|
||||
PyBMap/*.bin
|
||||
PyBMap/*.pdb
|
||||
## ===== Personal =====
|
||||
# Do not include PyBMap in this repository.
|
||||
# Order build fetch it manually.
|
||||
PyBMap/
|
||||
|
||||
# Disable generated icons and jsons but keep the directory hierarchy.
|
||||
icons/*
|
||||
!icons/.gitkeep
|
||||
jsons/*
|
||||
!jsons/.gitkeep
|
||||
|
||||
# Do not include intermidiate translation template (POT)
|
||||
# NOTE: it seems that Python default gitignore (written following) disable POT file in default.
|
||||
# so, as it wish, I also remove it from git.
|
||||
i18n/*
|
||||
!i18n/blender.pot
|
||||
!i18n/*.po
|
||||
|
||||
## ===== Python =====
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
|
@ -1,7 +1,7 @@
|
||||
import bpy
|
||||
import bpy, mathutils
|
||||
import typing
|
||||
from . import PROP_preferences
|
||||
from . import UTIL_functions, UTIL_bme
|
||||
from . import UTIL_functions, UTIL_translation, UTIL_bme
|
||||
|
||||
#region BME Adder
|
||||
|
||||
@ -9,29 +9,33 @@ _g_EnumHelper_BmeStructType: UTIL_bme.EnumPropHelper = UTIL_bme.EnumPropHelper()
|
||||
|
||||
class BBP_PG_bme_adder_cfgs(bpy.types.PropertyGroup):
|
||||
prop_int: bpy.props.IntProperty(
|
||||
name = 'Single Int', description = 'Single Int',
|
||||
name = 'Integral Value', description = 'The field representing a single integral value.',
|
||||
min = 0, max = 64,
|
||||
soft_min = 0, soft_max = 32,
|
||||
step = 1,
|
||||
default = 1,
|
||||
)
|
||||
translation_context = 'BBP_PG_bme_adder_cfgs/property'
|
||||
) # type: ignore
|
||||
prop_float: bpy.props.FloatProperty(
|
||||
name = 'Single Float', description = 'Single Float',
|
||||
name = 'Float Point Value', description = 'The field representing a single float point value.',
|
||||
min = 0.0, max = 1024.0,
|
||||
soft_min = 0.0, soft_max = 512.0,
|
||||
step = 50, # Step is in UI, in [1, 100] (WARNING: actual value is /100). So we choose 50, mean 0.5
|
||||
default = 5.0,
|
||||
)
|
||||
translation_context = 'BBP_PG_bme_adder_cfgs/property'
|
||||
) # type: ignore
|
||||
prop_bool: bpy.props.BoolProperty(
|
||||
name = 'Single Bool', description = 'Single Bool',
|
||||
default = True
|
||||
)
|
||||
name = 'Boolean Value', description = 'The field representing a single boolean value.',
|
||||
default = True,
|
||||
translation_context = 'BBP_PG_bme_adder_cfgs/property'
|
||||
) # type: ignore
|
||||
|
||||
class BBP_OT_add_bme_struct(bpy.types.Operator):
|
||||
"""Add BME Struct"""
|
||||
"""Add BME structure"""
|
||||
bl_idname = "bbp.add_bme_struct"
|
||||
bl_label = "Add BME Struct"
|
||||
bl_label = "Add BME Structure"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_bme_struct'
|
||||
|
||||
## There is a compromise due to the shitty Blender design.
|
||||
#
|
||||
@ -43,7 +47,7 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
||||
# So these is the solution about generating cache list according to the change of bme struct type.
|
||||
# First, update function will only set a "outdated" flag for operator which is a pre-registered Blender property.
|
||||
# The "outdated" flags is not showen and not saved.
|
||||
# Then call a internal cache list update function at the begin of `invoke` and `draw`.
|
||||
# Then call a internal cache list update function at the begin of `invoke`, `execute` and `draw`.
|
||||
# In this internal cache list updator, check "outdated" flag first, if cache is outdated, update and reset flag.
|
||||
# Otherwise do nothing.
|
||||
#
|
||||
@ -51,11 +55,12 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
||||
|
||||
## Compromise used "outdated" flag.
|
||||
outdated_flag: bpy.props.BoolProperty(
|
||||
name = "Outdated Type",
|
||||
description = "Internal flag.",
|
||||
# TR: Property not showen should not have name and desc.
|
||||
# name = "Outdated Type",
|
||||
# description = "Internal flag.",
|
||||
options = {'HIDDEN', 'SKIP_SAVE'},
|
||||
default = False
|
||||
)
|
||||
) # type: ignore
|
||||
|
||||
## A BME struct cfgs descriptor cache list
|
||||
# Not only the descriptor self, also the cfg associated index in bme_struct_cfgs
|
||||
@ -96,27 +101,29 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
||||
|
||||
|
||||
# init data collection
|
||||
op_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs]
|
||||
op_cfgs_visitor = UTIL_functions.CollectionVisitor(self.bme_struct_cfgs)
|
||||
# clear first
|
||||
self.bme_struct_cfgs.clear()
|
||||
op_cfgs_visitor.clear()
|
||||
# create enough entries specified by gotten cfgs
|
||||
for _ in range(max(counter_int, counter_float, counter_bool)):
|
||||
self.bme_struct_cfgs.add()
|
||||
op_cfgs_visitor.add()
|
||||
|
||||
# assign default value
|
||||
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
|
||||
# show prop differently by cfg type
|
||||
match(cfg.get_type()):
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
||||
self.bme_struct_cfgs[cfg_index].prop_int = cfg.get_default()
|
||||
op_cfgs_visitor[cfg_index].prop_int = cfg.get_default()
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
||||
self.bme_struct_cfgs[cfg_index].prop_float = cfg.get_default()
|
||||
op_cfgs_visitor[cfg_index].prop_float = cfg.get_default()
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
||||
self.bme_struct_cfgs[cfg_index].prop_bool = cfg.get_default()
|
||||
op_cfgs_visitor[cfg_index].prop_bool = cfg.get_default()
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
||||
# face is just 6 bool
|
||||
default_values: tuple[bool, ...] = cfg.get_default()
|
||||
for i in range(6):
|
||||
self.bme_struct_cfgs[cfg_index + i].prop_bool = default_values[i]
|
||||
op_cfgs_visitor[cfg_index + i].prop_bool = default_values[i]
|
||||
|
||||
# reset outdated flag
|
||||
self.outdated_flag = False
|
||||
@ -130,22 +137,49 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
||||
|
||||
bme_struct_type: bpy.props.EnumProperty(
|
||||
name = "Type",
|
||||
description = "BME struct type",
|
||||
description = "The type of BME structure.",
|
||||
items = _g_EnumHelper_BmeStructType.generate_items(),
|
||||
update = bme_struct_type_updated
|
||||
)
|
||||
update = bme_struct_type_updated,
|
||||
translation_context = 'BBP_OT_add_bme_struct/property'
|
||||
) # type: ignore
|
||||
|
||||
bme_struct_cfgs : bpy.props.CollectionProperty(
|
||||
name = "Cfgs",
|
||||
description = "Cfg collection.",
|
||||
name = "Configurations",
|
||||
description = "The collection holding BME structure configurations.",
|
||||
type = BBP_PG_bme_adder_cfgs,
|
||||
)
|
||||
translation_context = 'BBP_OT_add_bme_struct/property'
|
||||
) # type: ignore
|
||||
|
||||
## Extra transform for good "what you see is what you gotten".
|
||||
# Extra transform will be added after moving this object to cursor.
|
||||
extra_translation: bpy.props.FloatVectorProperty(
|
||||
name = "Extra Translation",
|
||||
description = "The extra translation applied to object after moving to cursor.",
|
||||
size = 3,
|
||||
subtype = 'TRANSLATION',
|
||||
step = 50, # same step as the float entry of BBP_PG_bme_adder_cfgs
|
||||
default = (0.0, 0.0, 0.0),
|
||||
translation_context = 'BBP_OT_add_bme_struct/property'
|
||||
) # type: ignore
|
||||
extra_rotation: bpy.props.FloatVectorProperty(
|
||||
name = "Extra Rotation",
|
||||
description = "The extra rotation applied to object after moving to cursor.",
|
||||
size = 3,
|
||||
subtype = 'EULER',
|
||||
step = 100, # We choosen 100, mean 1. Sync with property window.
|
||||
default = (0.0, 0.0, 0.0),
|
||||
translation_context = 'BBP_OT_add_bme_struct/property'
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
def poll(cls, context):
|
||||
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||
|
||||
def invoke(self, context, event):
|
||||
# reset extra transform to identy
|
||||
self.extra_translation = (0.0, 0.0, 0.0)
|
||||
self.extra_rotation = (0.0, 0.0, 0.0)
|
||||
self.extra_scale = (1.0, 1.0, 1.0)
|
||||
# create internal list
|
||||
self.bme_struct_cfg_index_cache = []
|
||||
# trigger default bme struct type updator
|
||||
@ -156,20 +190,26 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
# call internal updator
|
||||
self.__internal_update_bme_struct_type()
|
||||
|
||||
# create cfg visitor
|
||||
op_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs]
|
||||
op_cfgs_visitor = UTIL_functions.CollectionVisitor(self.bme_struct_cfgs)
|
||||
# collect cfgs data
|
||||
cfgs: dict[str, typing.Any] = {}
|
||||
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
|
||||
match(cfg.get_type()):
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
||||
cfgs[cfg.get_field()] = self.bme_struct_cfgs[cfg_index].prop_int
|
||||
cfgs[cfg.get_field()] = op_cfgs_visitor[cfg_index].prop_int
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
||||
cfgs[cfg.get_field()] = self.bme_struct_cfgs[cfg_index].prop_float
|
||||
cfgs[cfg.get_field()] = op_cfgs_visitor[cfg_index].prop_float
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
||||
cfgs[cfg.get_field()] = self.bme_struct_cfgs[cfg_index].prop_bool
|
||||
cfgs[cfg.get_field()] = op_cfgs_visitor[cfg_index].prop_bool
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
||||
# face is just 6 bool tuple
|
||||
cfgs[cfg.get_field()] = tuple(
|
||||
self.bme_struct_cfgs[cfg_index + i].prop_bool for i in range(6)
|
||||
op_cfgs_visitor[cfg_index + i].prop_bool for i in range(6)
|
||||
)
|
||||
|
||||
# call general creator
|
||||
@ -178,8 +218,16 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
||||
cfgs
|
||||
)
|
||||
|
||||
# move to cursor
|
||||
# add into scene and move to cursor
|
||||
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
||||
# add extra transform
|
||||
obj.matrix_world = obj.matrix_world @ mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector(self.extra_translation),
|
||||
mathutils.Euler(self.extra_rotation, 'XYZ'),
|
||||
mathutils.Vector((1.0, 1.0, 1.0)) # no scale
|
||||
)
|
||||
# select created object
|
||||
UTIL_functions.select_certain_objects((obj, ))
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
@ -191,56 +239,78 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
||||
# show type
|
||||
layout.prop(self, 'bme_struct_type')
|
||||
|
||||
# create cfg visitor
|
||||
op_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs]
|
||||
op_cfgs_visitor = UTIL_functions.CollectionVisitor(self.bme_struct_cfgs)
|
||||
# visit cfgs cache list to show cfg
|
||||
layout.label(text="Prototype Configurations", text_ctxt='BBP_OT_add_bme_struct/draw')
|
||||
prototype_cfg_index: int = 0
|
||||
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
|
||||
# create box for cfgs
|
||||
box_layout: bpy.types.UILayout = layout.box()
|
||||
|
||||
# draw title and description first
|
||||
box_layout.label(text = cfg.get_title())
|
||||
box_layout.label(text = cfg.get_desc())
|
||||
# get prototype identifier name and showcase configuration index
|
||||
# then increase the index.
|
||||
prototype_ident: str = _g_EnumHelper_BmeStructType.get_selection(self.bme_struct_type)
|
||||
box_layout.label(text=cfg.get_title(), text_ctxt=UTIL_translation.build_prototype_showcase_cfg_context(prototype_ident, prototype_cfg_index))
|
||||
box_layout.label(text=cfg.get_desc(), text_ctxt=UTIL_translation.build_prototype_showcase_cfg_context(prototype_ident, prototype_cfg_index))
|
||||
prototype_cfg_index += 1
|
||||
|
||||
# show prop differently by cfg type
|
||||
match(cfg.get_type()):
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
||||
box_layout.prop(self.bme_struct_cfgs[cfg_index], 'prop_int', text = '')
|
||||
box_layout.prop(op_cfgs_visitor[cfg_index], 'prop_int', text='')
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
||||
box_layout.prop(self.bme_struct_cfgs[cfg_index], 'prop_float', text = '')
|
||||
box_layout.prop(op_cfgs_visitor[cfg_index], 'prop_float', text='')
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
||||
box_layout.prop(self.bme_struct_cfgs[cfg_index], 'prop_bool', text = '')
|
||||
box_layout.prop(op_cfgs_visitor[cfg_index], 'prop_bool', text='')
|
||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
||||
# face will show a special layout (grid view)
|
||||
grids = box_layout.grid_flow(
|
||||
row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
|
||||
grids.alignment = 'CENTER'
|
||||
grids.separator()
|
||||
grids.prop(self.bme_struct_cfgs[cfg_index + 0], 'prop_bool', text = 'Top') # top
|
||||
grids.prop(self.bme_struct_cfgs[cfg_index + 2], 'prop_bool', text = 'Front') # front
|
||||
grids.prop(self.bme_struct_cfgs[cfg_index + 4], 'prop_bool', text = 'Left') # left
|
||||
grids.label(text = '', icon = 'CUBE') # show a 3d cube as icon
|
||||
grids.prop(self.bme_struct_cfgs[cfg_index + 5], 'prop_bool', text = 'Right') # right
|
||||
grids.prop(self.bme_struct_cfgs[cfg_index + 3], 'prop_bool', text = 'Back') # back
|
||||
grids.prop(self.bme_struct_cfgs[cfg_index + 1], 'prop_bool', text = 'Bottom') # bottom
|
||||
grids.prop(op_cfgs_visitor[cfg_index + 0], 'prop_bool', text='Top', text_ctxt='BBP_OT_add_bme_struct/draw') # top
|
||||
grids.prop(op_cfgs_visitor[cfg_index + 2], 'prop_bool', text='Front', text_ctxt='BBP_OT_add_bme_struct/draw') # front
|
||||
grids.prop(op_cfgs_visitor[cfg_index + 4], 'prop_bool', text='Left', text_ctxt='BBP_OT_add_bme_struct/draw') # left
|
||||
grids.label(text='', icon='CUBE') # show a 3d cube as icon
|
||||
grids.prop(op_cfgs_visitor[cfg_index + 5], 'prop_bool', text='Right', text_ctxt='BBP_OT_add_bme_struct/draw') # right
|
||||
grids.prop(op_cfgs_visitor[cfg_index + 3], 'prop_bool', text='Back', text_ctxt='BBP_OT_add_bme_struct/draw') # back
|
||||
grids.prop(op_cfgs_visitor[cfg_index + 1], 'prop_bool', text='Bottom', text_ctxt='BBP_OT_add_bme_struct/draw') # bottom
|
||||
grids.separator()
|
||||
|
||||
# show extra transform props
|
||||
# forcely order that each one are placed horizontally
|
||||
layout.label(text="Extra Transform", text_ctxt='BBP_OT_add_bme_struct/draw')
|
||||
# translation
|
||||
layout.label(text='Translation', text_ctxt='BBP_OT_add_bme_struct/draw')
|
||||
hbox_layout: bpy.types.UILayout = layout.row()
|
||||
hbox_layout.prop(self, 'extra_translation', text='')
|
||||
# rotation
|
||||
layout.label(text='Rotation', text_ctxt='BBP_OT_add_bme_struct/draw')
|
||||
hbox_layout = layout.row()
|
||||
hbox_layout.prop(self, 'extra_rotation', text='')
|
||||
|
||||
@classmethod
|
||||
def draw_blc_menu(self, layout: bpy.types.UILayout):
|
||||
def draw_blc_menu(cls, layout: bpy.types.UILayout):
|
||||
for ident in _g_EnumHelper_BmeStructType.get_bme_identifiers():
|
||||
# draw operator
|
||||
cop = layout.operator(
|
||||
self.bl_idname,
|
||||
cls.bl_idname,
|
||||
text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident),
|
||||
icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident)
|
||||
icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident),
|
||||
text_ctxt = UTIL_translation.build_prototype_showcase_context(ident)
|
||||
)
|
||||
# and assign its init type value
|
||||
cop.bme_struct_type = _g_EnumHelper_BmeStructType.to_selection(ident)
|
||||
|
||||
#endregion
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_PG_bme_adder_cfgs)
|
||||
bpy.utils.register_class(BBP_OT_add_bme_struct)
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_add_bme_struct)
|
||||
bpy.utils.unregister_class(BBP_PG_bme_adder_cfgs)
|
@ -1,18 +1,19 @@
|
||||
import bpy, mathutils
|
||||
import math, typing
|
||||
from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convension
|
||||
from . import PROP_ballance_element, PROP_virtools_group
|
||||
from . import PROP_ballance_element, PROP_virtools_group, PROP_ballance_map_info
|
||||
|
||||
#region Param Help Classes
|
||||
|
||||
class ComponentSectorParam():
|
||||
component_sector: bpy.props.IntProperty(
|
||||
name = "Sector",
|
||||
description = "Define which sector the object will be grouped in",
|
||||
description = "The sector which this component will be grouped in.",
|
||||
min = 1, max = 999,
|
||||
soft_min = 1, soft_max = 8,
|
||||
default = 1,
|
||||
)
|
||||
translation_context = 'BBP/OP_ADDS_component.ComponentSectorParam/property'
|
||||
) # type: ignore
|
||||
|
||||
def general_get_component_sector(self) -> int:
|
||||
return self.component_sector
|
||||
@ -23,11 +24,12 @@ class ComponentSectorParam():
|
||||
class ComponentCountParam():
|
||||
component_count: bpy.props.IntProperty(
|
||||
name = "Count",
|
||||
description = "The count of components you want to generate",
|
||||
description = "The count of components which you want to generate",
|
||||
min = 1, max = 64,
|
||||
soft_min = 1, soft_max = 32,
|
||||
default = 1,
|
||||
)
|
||||
translation_context = 'BBP/OP_ADDS_component.ComponentCountParam/property'
|
||||
) # type: ignore
|
||||
|
||||
def general_get_component_count(self) -> int:
|
||||
return self.component_count
|
||||
@ -94,7 +96,20 @@ def _check_component_existance(comp_type: PROP_ballance_element.BallanceElementT
|
||||
if expect_name in bpy.data.objects: return expect_name
|
||||
else: return None
|
||||
|
||||
def _general_create_component(
|
||||
class _GeneralComponentCreator():
|
||||
"""
|
||||
The assist class for general component creation function.
|
||||
Because we need select all created component, thus we need collect all created object into a list.
|
||||
This is the reason why we create this class.
|
||||
"""
|
||||
|
||||
## The list storing all created component within this creation.
|
||||
__mObjList: list[bpy.types.Object]
|
||||
|
||||
def __init__(self):
|
||||
self.__mObjList = []
|
||||
|
||||
def create_component(self,
|
||||
comp_type: PROP_ballance_element.BallanceElementType,
|
||||
comp_sector: int,
|
||||
comp_count: int,
|
||||
@ -111,9 +126,11 @@ def _general_create_component(
|
||||
You can pass `lambda _: mathutils.Matrix( xxx )` to get same offset for every items.
|
||||
You can pass `lambda i: mathutils.Matrix( func(i) )` to get index based offset for each items.
|
||||
The offset is the offset to the origin point, not the previous object.
|
||||
@return The created component instance.
|
||||
"""
|
||||
# get element info first
|
||||
ele_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector)
|
||||
|
||||
# create blc element context
|
||||
with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator:
|
||||
# object creation counter
|
||||
@ -126,6 +143,36 @@ def _general_create_component(
|
||||
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
||||
# move with extra offset by calling offset getter
|
||||
obj.matrix_world = obj.matrix_world @ comp_offset(i)
|
||||
# put into created object list
|
||||
self.__mObjList.append(obj)
|
||||
|
||||
# enlarge scene sector field for non-PS (start point) PE (end point) component
|
||||
# read from scene and create var for enlarged sector count
|
||||
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||
enlarged_sector: int
|
||||
# check component type to get enlarged value
|
||||
match(ele_info.mBasicType):
|
||||
case UTIL_naming_convension.BallanceObjectType.COMPONENT:
|
||||
enlarged_sector = comp_sector
|
||||
case UTIL_naming_convension.BallanceObjectType.CHECKPOINT:
|
||||
# checkpoint 1 means that there is sector 2, so we plus 1 for it.
|
||||
enlarged_sector = comp_sector + 1
|
||||
case UTIL_naming_convension.BallanceObjectType.RESETPOINT:
|
||||
enlarged_sector = comp_sector
|
||||
case _:
|
||||
# this component is not a sector based component
|
||||
# so we do not change it (use original value)
|
||||
enlarged_sector = map_info.mSectorCount
|
||||
# enlarge it
|
||||
map_info.mSectorCount = max(map_info.mSectorCount, enlarged_sector)
|
||||
PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info)
|
||||
|
||||
def finish_component(self) -> None:
|
||||
"""
|
||||
Finish up component creation.
|
||||
Just deselect all objects and select all created components.
|
||||
"""
|
||||
UTIL_functions.select_certain_objects(tuple(self.__mObjList))
|
||||
|
||||
#endregion
|
||||
|
||||
@ -147,16 +194,18 @@ _g_EnumHelper_Component: UTIL_functions.EnumPropHelper = UTIL_functions.EnumProp
|
||||
)
|
||||
|
||||
class BBP_OT_add_component(bpy.types.Operator, ComponentSectorParam):
|
||||
"""Add Component"""
|
||||
"""Add ordinary Component"""
|
||||
bl_idname = "bbp.add_component"
|
||||
bl_label = "Add Component"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_component'
|
||||
|
||||
component_type: bpy.props.EnumProperty(
|
||||
name = "Type",
|
||||
description = "This component type",
|
||||
description = "The type of this component.",
|
||||
items = _g_EnumHelper_Component.generate_items(),
|
||||
)
|
||||
translation_context = 'BBP_OT_add_component/property'
|
||||
) # type: ignore
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
@ -175,16 +224,20 @@ class BBP_OT_add_component(bpy.types.Operator, ComponentSectorParam):
|
||||
# check for some special components and show warning
|
||||
elename: str | None = _check_component_existance(_g_EnumHelper_Component.get_selection(self.component_type), self.general_get_component_sector())
|
||||
if elename is not None:
|
||||
layout.label(text = f'Warning: {elename} already exist.')
|
||||
tr_text: str = bpy.app.translations.pgettext_iface(
|
||||
'Warning: {0} already exist.', 'BBP_OT_add_component/draw')
|
||||
layout.label(text=tr_text.format(elename), translate=False)
|
||||
|
||||
def execute(self, context):
|
||||
# call general creator
|
||||
_general_create_component(
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
_g_EnumHelper_Component.get_selection(self.component_type),
|
||||
self.general_get_component_sector(),
|
||||
1, # only create one
|
||||
lambda _: mathutils.Matrix.Identity(4)
|
||||
)
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
@ -193,8 +246,9 @@ class BBP_OT_add_component(bpy.types.Operator, ComponentSectorParam):
|
||||
item_name: str = PROP_ballance_element.get_ballance_element_name(item)
|
||||
|
||||
cop = layout.operator(
|
||||
BBP_OT_add_component.bl_idname, text = item_name,
|
||||
icon_value = UTIL_icons_manager.get_component_icon(item_name)
|
||||
BBP_OT_add_component.bl_idname,
|
||||
text = item_name, text_ctxt = 'BBP_OT_add_component/property',
|
||||
icon_value = UTIL_icons_manager.get_component_icon(item_name),
|
||||
)
|
||||
cop.component_type = _g_EnumHelper_Component.to_selection(item)
|
||||
|
||||
@ -207,6 +261,7 @@ class BBP_OT_add_nong_extra_point(bpy.types.Operator, ComponentSectorParam, Comp
|
||||
bl_idname = "bbp.add_nong_extra_point"
|
||||
bl_label = "Nong Extra Point"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_nong_extra_point'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@ -218,12 +273,14 @@ class BBP_OT_add_nong_extra_point(bpy.types.Operator, ComponentSectorParam, Comp
|
||||
# calc percent first
|
||||
percent: float = 1.0 / self.general_get_component_count()
|
||||
# create elements
|
||||
_general_create_component(
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
PROP_ballance_element.BallanceElementType.P_Extra_Point,
|
||||
self.general_get_component_sector(),
|
||||
self.general_get_component_count(),
|
||||
lambda i: mathutils.Matrix.Rotation(percent * i * math.pi * 2, 4, 'Z')
|
||||
)
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
@ -240,25 +297,28 @@ class BBP_OT_add_nong_ventilator(bpy.types.Operator, ComponentSectorParam, Compo
|
||||
bl_idname = "bbp.add_nong_ventilator"
|
||||
bl_label = "Nong Ventilator"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_nong_ventilator'
|
||||
|
||||
ventilator_count_source: bpy.props.EnumProperty(
|
||||
name = "Ventilator Count Source",
|
||||
items = (
|
||||
items = [
|
||||
('DEFINED', "Predefined", "Pre-defined ventilator count."),
|
||||
('CUSTOM', "Custom", "User specified ventilator count."),
|
||||
),
|
||||
)
|
||||
],
|
||||
translation_context = 'BBP_OT_add_nong_ventilator/property'
|
||||
) # type: ignore
|
||||
|
||||
preset_vetilator_count: bpy.props.EnumProperty(
|
||||
name = "Preset Count",
|
||||
description = "Pick preset ventilator count.",
|
||||
items = (
|
||||
items = [
|
||||
# (token, display name, descriptions, icon, index)
|
||||
('PAPER', 'Paper', 'The ventilator count (1) can push paper ball up.'),
|
||||
('WOOD', 'Wood', 'The ventilator count (6) can push wood ball up.'),
|
||||
('STONE', 'Stone', 'The ventilator count (32) can push stone ball up.'),
|
||||
),
|
||||
)
|
||||
],
|
||||
translation_context = 'BBP_OT_add_nong_ventilator/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@ -266,7 +326,8 @@ class BBP_OT_add_nong_ventilator(bpy.types.Operator, ComponentSectorParam, Compo
|
||||
self.draw_component_sector_params(layout)
|
||||
|
||||
# draw count settings by different source
|
||||
layout.label(text = 'Count')
|
||||
layout.separator()
|
||||
layout.label(text='Count Source', text_ctxt='BBP_OT_add_nong_ventilator/draw')
|
||||
layout.prop(self, 'ventilator_count_source', expand = True)
|
||||
if (self.ventilator_count_source == 'CUSTOM'):
|
||||
self.draw_component_count_params(layout)
|
||||
@ -286,12 +347,14 @@ class BBP_OT_add_nong_ventilator(bpy.types.Operator, ComponentSectorParam, Compo
|
||||
case _: raise UTIL_functions.BBPException('invalid enumprop data')
|
||||
|
||||
# create elements without any move
|
||||
_general_create_component(
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
PROP_ballance_element.BallanceElementType.P_Modul_18,
|
||||
self.general_get_component_sector(),
|
||||
count,
|
||||
lambda _: mathutils.Matrix.Identity(4)
|
||||
)
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
@ -312,6 +375,7 @@ class BBP_OT_add_tilting_block_series(bpy.types.Operator, ComponentSectorParam,
|
||||
bl_idname = "bbp.add_tilting_block_series"
|
||||
bl_label = "Tilting Block Series"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_tilting_block_series'
|
||||
|
||||
component_span: bpy.props.FloatProperty(
|
||||
name = "Span",
|
||||
@ -319,7 +383,8 @@ class BBP_OT_add_tilting_block_series(bpy.types.Operator, ComponentSectorParam,
|
||||
min = 0.0, max = 100.0,
|
||||
soft_min = 0.0, soft_max = 12.0,
|
||||
default = 6.0022,
|
||||
)
|
||||
translation_context = 'BBP_OT_add_tilting_block_series/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@ -332,13 +397,14 @@ class BBP_OT_add_tilting_block_series(bpy.types.Operator, ComponentSectorParam,
|
||||
# get span first
|
||||
span: float = self.component_span
|
||||
# create elements
|
||||
_general_create_component(
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
PROP_ballance_element.BallanceElementType.P_Modul_41,
|
||||
self.general_get_component_sector(),
|
||||
self.general_get_component_count(),
|
||||
lambda i: mathutils.Matrix.Translation(mathutils.Vector((span * i, 0.0, 0.0))) # move with extra delta in x axis
|
||||
)
|
||||
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
@ -350,11 +416,73 @@ class BBP_OT_add_tilting_block_series(bpy.types.Operator, ComponentSectorParam,
|
||||
)
|
||||
)
|
||||
|
||||
class BBP_OT_add_swing_series(bpy.types.Operator, ComponentSectorParam, ComponentCountParam):
|
||||
"""Add Swing Series"""
|
||||
bl_idname = "bbp.add_swing_series"
|
||||
bl_label = "Swing Series"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_swing_series'
|
||||
|
||||
component_span: bpy.props.FloatProperty(
|
||||
name = "Span",
|
||||
description = "The distance between each swing",
|
||||
min = 0.0, max = 100.0,
|
||||
soft_min = 0.0, soft_max = 30.0,
|
||||
default = 15.0,
|
||||
translation_context = 'BBP_OT_add_swing_series/property'
|
||||
) # type: ignore
|
||||
|
||||
staggered_swing: bpy.props.BoolProperty(
|
||||
name = 'Staggered',
|
||||
description = 'Whether place Swing staggered. Staggered Swing accept any ball however Non-Staggered Swing only accept Wood and Paper ball.',
|
||||
default = True,
|
||||
translation_context = 'BBP_OT_add_swing_series/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
self.draw_component_sector_params(layout)
|
||||
self.draw_component_count_params(layout)
|
||||
layout.prop(self, 'component_span')
|
||||
layout.prop(self, 'staggered_swing')
|
||||
|
||||
def execute(self, context):
|
||||
# create objects and move it by delta
|
||||
# get span first
|
||||
span: float = self.component_span
|
||||
staggered: bool = self.staggered_swing
|
||||
# create elements
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
PROP_ballance_element.BallanceElementType.P_Modul_08,
|
||||
self.general_get_component_sector(),
|
||||
self.general_get_component_count(),
|
||||
lambda i: mathutils.Matrix.LocRotScale(
|
||||
# move with extra delta in x axis
|
||||
mathutils.Vector((span * i, 0.0, 0.0)),
|
||||
# and rotate 90 degree for even one if staggered placement.
|
||||
mathutils.Euler((0, 0, math.radians(180) if (staggered and (i % 2 == 0)) else 0)),
|
||||
None
|
||||
)
|
||||
)
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
def draw_blc_menu(layout: bpy.types.UILayout):
|
||||
layout.operator(
|
||||
BBP_OT_add_swing_series.bl_idname,
|
||||
icon_value = UTIL_icons_manager.get_component_icon(
|
||||
PROP_ballance_element.get_ballance_element_name(PROP_ballance_element.BallanceElementType.P_Modul_08)
|
||||
)
|
||||
)
|
||||
|
||||
class BBP_OT_add_ventilator_series(bpy.types.Operator, ComponentSectorParam, ComponentCountParam):
|
||||
"""Add Ventilator Series"""
|
||||
bl_idname = "bbp.add_ventilator_series"
|
||||
bl_label = "Ventilator Series"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_ventilator_series'
|
||||
|
||||
component_translation: bpy.props.FloatVectorProperty(
|
||||
name = "Delta Vector",
|
||||
@ -363,7 +491,8 @@ class BBP_OT_add_ventilator_series(bpy.types.Operator, ComponentSectorParam, Com
|
||||
min = 0.0, max = 100.0,
|
||||
soft_min = 0.0, soft_max = 50.0,
|
||||
default = (0.0, 0.0, 15.0),
|
||||
)
|
||||
translation_context = 'BBP_OT_add_ventilator_series/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@ -376,13 +505,14 @@ class BBP_OT_add_ventilator_series(bpy.types.Operator, ComponentSectorParam, Com
|
||||
# get translation first
|
||||
translation: mathutils.Vector = mathutils.Vector(self.component_translation)
|
||||
# create elements
|
||||
_general_create_component(
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
PROP_ballance_element.BallanceElementType.P_Modul_18,
|
||||
self.general_get_component_sector(),
|
||||
self.general_get_component_count(),
|
||||
lambda i: mathutils.Matrix.Translation(i * translation) # move with extra translation
|
||||
)
|
||||
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
@ -403,6 +533,7 @@ class BBP_OT_add_sector_component_pair(bpy.types.Operator, ComponentSectorParam)
|
||||
bl_idname = "bbp.add_sector_component_pair"
|
||||
bl_label = "Sector Pair"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_sector_component_pair'
|
||||
|
||||
def __get_checkpoint(self) -> tuple[PROP_ballance_element.BallanceElementType, int]:
|
||||
if self.general_get_component_sector() == 1:
|
||||
@ -423,16 +554,20 @@ class BBP_OT_add_sector_component_pair(bpy.types.Operator, ComponentSectorParam)
|
||||
layout = self.layout
|
||||
self.draw_component_sector_params(layout)
|
||||
|
||||
# fetch warning string
|
||||
tr_text: str = bpy.app.translations.pgettext_iface(
|
||||
'Warning: {0} already exist.', 'BBP_OT_add_sector_component_pair/draw')
|
||||
|
||||
# check checkpoint and resetpoint name conflict and show warnings
|
||||
(checkp_ty, checkp_sector) = self.__get_checkpoint()
|
||||
elename: str | None = _check_component_existance(checkp_ty, checkp_sector)
|
||||
if elename is not None:
|
||||
layout.label(text = f'Warning: {elename} already exist.')
|
||||
layout.label(text=tr_text.format(elename), translate=False)
|
||||
|
||||
(resetp_ty, resetp_sector) = self.__get_resetpoint()
|
||||
elename = _check_component_existance(resetp_ty, resetp_sector)
|
||||
if elename is not None:
|
||||
layout.label(text = f'Warning: {elename} already exist.')
|
||||
layout.label(text=tr_text.format(elename), translate=False)
|
||||
|
||||
def execute(self, context):
|
||||
# create checkpoint and resetpoint individually in element context
|
||||
@ -450,20 +585,21 @@ class BBP_OT_add_sector_component_pair(bpy.types.Operator, ComponentSectorParam)
|
||||
|
||||
# add elements
|
||||
# create checkpoint
|
||||
_general_create_component(
|
||||
creator: _GeneralComponentCreator = _GeneralComponentCreator()
|
||||
creator.create_component(
|
||||
checkp_ty,
|
||||
checkp_sector,
|
||||
1, # only create one
|
||||
lambda _: mathutils.Matrix.Identity(4)
|
||||
)
|
||||
# create resetpoint
|
||||
_general_create_component(
|
||||
creator.create_component(
|
||||
resetp_ty,
|
||||
resetp_sector,
|
||||
1, # only create one
|
||||
lambda _: mathutils.Matrix.Translation(mathutils.Vector((0.0, 0.0, resetp_offset))) # apply resetpoint offset
|
||||
)
|
||||
|
||||
creator.finish_component()
|
||||
return {'FINISHED'}
|
||||
|
||||
@staticmethod
|
||||
@ -477,17 +613,19 @@ class BBP_OT_add_sector_component_pair(bpy.types.Operator, ComponentSectorParam)
|
||||
|
||||
#endregion
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_add_component)
|
||||
bpy.utils.register_class(BBP_OT_add_nong_extra_point)
|
||||
bpy.utils.register_class(BBP_OT_add_nong_ventilator)
|
||||
bpy.utils.register_class(BBP_OT_add_tilting_block_series)
|
||||
bpy.utils.register_class(BBP_OT_add_swing_series)
|
||||
bpy.utils.register_class(BBP_OT_add_ventilator_series)
|
||||
bpy.utils.register_class(BBP_OT_add_sector_component_pair)
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_add_sector_component_pair)
|
||||
bpy.utils.unregister_class(BBP_OT_add_ventilator_series)
|
||||
bpy.utils.unregister_class(BBP_OT_add_swing_series)
|
||||
bpy.utils.unregister_class(BBP_OT_add_tilting_block_series)
|
||||
bpy.utils.unregister_class(BBP_OT_add_nong_ventilator)
|
||||
bpy.utils.unregister_class(BBP_OT_add_nong_extra_point)
|
||||
|
@ -1,6 +1,6 @@
|
||||
import bpy, bmesh, mathutils, math
|
||||
import bpy, mathutils, math
|
||||
import typing
|
||||
from . import UTIL_functions, UTIL_naming_convension
|
||||
from . import UTIL_rail_creator
|
||||
|
||||
## Const Value Hint:
|
||||
# Default Rail Radius: 0.35 (in measure)
|
||||
@ -33,6 +33,52 @@ c_SideSpiralRailScrew: float = 3.6
|
||||
|
||||
#region Operator Helpers
|
||||
|
||||
class SharedExtraTransform():
|
||||
"""
|
||||
This class is served for all rail creation which allow user
|
||||
provide extra transform after moving created rail to cursor.
|
||||
For "what you look is what you gotten" experience, this extra transform is essential.
|
||||
"""
|
||||
|
||||
extra_translation: bpy.props.FloatVectorProperty(
|
||||
name = "Extra Translation",
|
||||
description = "The extra translation applied to object after moving to cursor.",
|
||||
size = 3,
|
||||
subtype = 'TRANSLATION',
|
||||
step = 50, # same step as the float entry of BBP_PG_bme_adder_cfgs
|
||||
default = (0.0, 0.0, 0.0),
|
||||
translation_context = 'BBP/OP_ADDS_rail.SharedExtraTransform/property'
|
||||
) # type: ignore
|
||||
extra_rotation: bpy.props.FloatVectorProperty(
|
||||
name = "Extra Rotation",
|
||||
description = "The extra rotation applied to object after moving to cursor.",
|
||||
size = 3,
|
||||
subtype = 'EULER',
|
||||
step = 100, # We choosen 100, mean 1. Sync with property window.
|
||||
default = (0.0, 0.0, 0.0),
|
||||
translation_context = 'BBP/OP_ADDS_rail.SharedExtraTransform/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw_extra_transform_input(self, layout: bpy.types.UILayout) -> None:
|
||||
# show extra transform props
|
||||
# forcely order that each one are placed horizontally
|
||||
layout.label(text="Extra Transform", text_ctxt='BBP/OP_ADDS_rail.SharedExtraTransform/draw')
|
||||
# translation
|
||||
layout.label(text='Translation', text_ctxt='BBP/OP_ADDS_rail.SharedExtraTransform/draw')
|
||||
row = layout.row()
|
||||
row.prop(self, 'extra_translation', text='')
|
||||
# rotation
|
||||
layout.label(text='Rotation', text_ctxt='BBP/OP_ADDS_rail.SharedExtraTransform/draw')
|
||||
row = layout.row()
|
||||
row.prop(self, 'extra_rotation', text='')
|
||||
|
||||
def general_get_extra_transform(self) -> mathutils.Matrix:
|
||||
return mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector(self.extra_translation),
|
||||
mathutils.Euler(self.extra_rotation, 'XYZ'),
|
||||
mathutils.Vector((1.0, 1.0, 1.0)) # no scale
|
||||
)
|
||||
|
||||
class SharedRailSectionInputProperty():
|
||||
"""
|
||||
This class is served for user to pick the transition type of rail.
|
||||
@ -47,6 +93,7 @@ class SharedRailSectionInputProperty():
|
||||
('RAIL', "Rail", ""),
|
||||
],
|
||||
default = 'RAIL',
|
||||
translation_context = 'BBP/OP_ADDS_rail.SharedRailSectionInputProperty/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw_rail_section_input(self, layout: bpy.types.UILayout) -> None:
|
||||
@ -65,16 +112,19 @@ class SharedRailCapInputProperty():
|
||||
rail_start_cap: bpy.props.BoolProperty(
|
||||
name = 'Start Cap',
|
||||
description = 'Whether this rail should have cap at start terminal.',
|
||||
default = False
|
||||
default = False,
|
||||
translation_context = 'BBP/OP_ADDS_rail.SharedRailCapInputProperty/property'
|
||||
) # type: ignore
|
||||
|
||||
rail_end_cap: bpy.props.BoolProperty(
|
||||
name = 'End Cap',
|
||||
description = 'Whether this rail should have cap at end terminal.',
|
||||
default = False
|
||||
default = False,
|
||||
translation_context = 'BBP/OP_ADDS_rail.SharedRailCapInputProperty/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw_rail_cap_input(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.label(text="Cap Options", text_ctxt='BBP/OP_ADDS_rail.SharedRailCapInputProperty/draw')
|
||||
row = layout.row()
|
||||
row.prop(self, "rail_start_cap", toggle = 1)
|
||||
row.prop(self, "rail_end_cap", toggle = 1)
|
||||
@ -95,7 +145,8 @@ class SharedStraightRailInputProperty():
|
||||
default = 5.0,
|
||||
min = 0,
|
||||
step = 50, # same unit as BME Struct
|
||||
unit = 'LENGTH'
|
||||
unit = 'LENGTH',
|
||||
translation_context = 'BBP/OP_ADDS_rail.SharedStraightRailInputProperty/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw_straight_rail_input(self, layout: bpy.types.UILayout) -> None:
|
||||
@ -112,15 +163,39 @@ class SharedScrewRailInputProperty():
|
||||
rail_screw_steps: bpy.props.IntProperty(
|
||||
name = "Steps",
|
||||
description = "The segment count per iteration. More segment, more smooth but lower performance.",
|
||||
default = 16,
|
||||
default = 28,
|
||||
min = 1,
|
||||
translation_context = 'BBP/OP_ADDS_rail.SharedScrewRailInputProperty/property'
|
||||
) # type: ignore
|
||||
|
||||
rail_screw_radius: bpy.props.FloatProperty(
|
||||
name = "Radius",
|
||||
description = "The screw radius. Minus radius will flip the built screw.",
|
||||
description = "The screw radius.",
|
||||
default = 5,
|
||||
unit = 'LENGTH'
|
||||
min = 0,
|
||||
unit = 'LENGTH',
|
||||
translation_context = 'BBP/OP_ADDS_rail.SharedScrewRailInputProperty/property'
|
||||
) # type: ignore
|
||||
|
||||
rail_screw_flip_x: bpy.props.BoolProperty(
|
||||
name = 'Flip X',
|
||||
description = 'Whether flip this rail with X axis',
|
||||
default = False,
|
||||
translation_context = 'BBP/OP_ADDS_rail.SharedScrewRailInputProperty/property'
|
||||
) # type: ignore
|
||||
|
||||
rail_screw_flip_y: bpy.props.BoolProperty(
|
||||
name = 'Flip Y',
|
||||
description = 'Whether flip this rail with Y axis',
|
||||
default = False,
|
||||
translation_context = 'BBP/OP_ADDS_rail.SharedScrewRailInputProperty/property'
|
||||
) # type: ignore
|
||||
|
||||
rail_screw_flip_z: bpy.props.BoolProperty(
|
||||
name = 'Flip Z',
|
||||
description = 'Whether flip this rail with Z axis',
|
||||
default = False,
|
||||
translation_context = 'BBP/OP_ADDS_rail.SharedScrewRailInputProperty/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw_screw_rail_input(self, layout: bpy.types.UILayout) -> None:
|
||||
@ -132,6 +207,21 @@ class SharedScrewRailInputProperty():
|
||||
def general_get_rail_screw_steps(self) -> int:
|
||||
return self.rail_screw_steps
|
||||
|
||||
def draw_screw_rail_flip_input(self, layout: bpy.types.UILayout) -> None:
|
||||
# flip options should placed horizontally
|
||||
layout.label(text="Flip Options", text_ctxt='BBP/OP_ADDS_rail.SharedScrewRailInputProperty/draw')
|
||||
row = layout.row()
|
||||
row.prop(self, "rail_screw_flip_x", toggle = 1)
|
||||
row.prop(self, "rail_screw_flip_y", toggle = 1)
|
||||
row.prop(self, "rail_screw_flip_z", toggle = 1)
|
||||
|
||||
def general_get_rail_screw_flip_x(self) -> bool:
|
||||
return self.rail_screw_flip_x
|
||||
def general_get_rail_screw_flip_y(self) -> bool:
|
||||
return self.rail_screw_flip_y
|
||||
def general_get_rail_screw_flip_z(self) -> bool:
|
||||
return self.rail_screw_flip_z
|
||||
|
||||
#endregion
|
||||
|
||||
#region Operators
|
||||
@ -141,13 +231,15 @@ class BBP_OT_add_rail_section(SharedRailSectionInputProperty, bpy.types.Operator
|
||||
bl_idname = "bbp.add_rail_section"
|
||||
bl_label = "Rail Section"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_rail_section'
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_rail_section(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_rail_section(
|
||||
bm, self.general_get_is_monorail(),
|
||||
c_DefaultRailRadius, c_DefaultRailSpan
|
||||
)
|
||||
),
|
||||
mathutils.Matrix.Identity(4)
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
@ -160,73 +252,82 @@ class BBP_OT_add_transition_section(bpy.types.Operator):
|
||||
bl_idname = "bbp.add_transition_section"
|
||||
bl_label = "Transition Section"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_transition_section'
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_transition_section(bm, c_DefaultRailRadius, c_DefaultRailSpan)
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_transition_section(bm, c_DefaultRailRadius, c_DefaultRailSpan),
|
||||
mathutils.Matrix.Identity(4)
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'No Options Available')
|
||||
layout.label(text='No Options Available', text_ctxt='BBP_OT_add_transition_section/draw')
|
||||
|
||||
class BBP_OT_add_straight_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
||||
class BBP_OT_add_straight_rail(SharedExtraTransform, SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
||||
"""Add Straight Rail"""
|
||||
bl_idname = "bbp.add_straight_rail"
|
||||
bl_label = "Straight Rail"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_straight_rail'
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_straight_rail(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_straight_rail(
|
||||
bm,
|
||||
self.general_get_is_monorail(), c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_length(), 0,
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
|
||||
)
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Straight Rail')
|
||||
layout.label(text='Straight Rail', text_ctxt='BBP_OT_add_straight_rail/draw')
|
||||
self.draw_rail_section_input(layout)
|
||||
self.draw_straight_rail_input(layout)
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
class BBP_OT_add_transition_rail(SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
||||
class BBP_OT_add_transition_rail(SharedExtraTransform, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
||||
"""Add Transition Rail"""
|
||||
bl_idname = "bbp.add_transition_rail"
|
||||
bl_label = "Transition Rail"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_transition_rail'
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_transition_rail(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_transition_rail(
|
||||
bm,
|
||||
c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_length(),
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
|
||||
)
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Transition Rail')
|
||||
layout.label(text='Transition Rail', text_ctxt='BBP_OT_add_transition_rail/draw')
|
||||
self.draw_straight_rail_input(layout)
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
class BBP_OT_add_side_rail(SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
||||
class BBP_OT_add_side_rail(SharedExtraTransform, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
||||
"""Add Side Rail"""
|
||||
bl_idname = "bbp.add_side_rail"
|
||||
bl_label = "Side Rail"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_side_rail'
|
||||
|
||||
side_rail_type: bpy.props.EnumProperty(
|
||||
name = "Side Type",
|
||||
@ -236,34 +337,38 @@ class BBP_OT_add_side_rail(SharedRailCapInputProperty, SharedStraightRailInputPr
|
||||
('STONE', "Stone Specific", "The side rail which also allow stone ball passed."),
|
||||
],
|
||||
default = 'NORMAL',
|
||||
translation_context = 'BBP_OT_add_side_rail/property'
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_straight_rail(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_straight_rail(
|
||||
bm,
|
||||
False, c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_length(),
|
||||
c_NormalSideRailAngle if self.side_rail_type == 'NORMAL' else c_StoneSideRailAngle,
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
|
||||
)
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Side Rail')
|
||||
layout.label(text='Side Rail', text_ctxt='BBP_OT_add_side_rail/draw')
|
||||
layout.prop(self, 'side_rail_type')
|
||||
self.draw_straight_rail_input(layout)
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
class BBP_OT_add_arc_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
||||
class BBP_OT_add_arc_rail(SharedExtraTransform, SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
||||
"""Add Arc Rail"""
|
||||
bl_idname = "bbp.add_arc_rail"
|
||||
bl_label = "Arc Rail"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_arc_rail'
|
||||
|
||||
rail_screw_angle: bpy.props.FloatProperty(
|
||||
name = "Angle",
|
||||
@ -271,41 +376,50 @@ class BBP_OT_add_arc_rail(SharedRailSectionInputProperty, SharedRailCapInputProp
|
||||
default = math.radians(90),
|
||||
min = 0, max = math.radians(360),
|
||||
subtype = 'ANGLE',
|
||||
translation_context = 'BBP_OT_add_arc_rail/property'
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_screw_rail(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_screw_rail(
|
||||
bm,
|
||||
self.general_get_is_monorail(), c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
||||
math.degrees(self.rail_screw_angle), 0, 1, # blender passed value is in radians
|
||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
|
||||
)
|
||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius(),
|
||||
self.general_get_rail_screw_flip_x(), self.general_get_rail_screw_flip_y(), self.general_get_rail_screw_flip_z()
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Arc Rail')
|
||||
layout.label(text='Arc Rail', text_ctxt='BBP_OT_add_arc_rail/draw')
|
||||
self.draw_rail_section_input(layout)
|
||||
self.draw_screw_rail_input(layout)
|
||||
layout.prop(self, "rail_screw_angle")
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_screw_rail_flip_input(layout)
|
||||
layout.separator()
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
class BBP_OT_add_spiral_rail(SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
||||
class BBP_OT_add_spiral_rail(SharedExtraTransform, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
||||
"""Add Spiral Rail"""
|
||||
bl_idname = "bbp.add_spiral_rail"
|
||||
bl_label = "Spiral Rail"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_spiral_rail'
|
||||
|
||||
rail_screw_screw: bpy.props.FloatProperty(
|
||||
name = "Screw",
|
||||
description = "The increased height in each iteration. Minus height also is accepted.",
|
||||
description = "The increased height in each iteration.",
|
||||
default = c_SpiralRailScrew,
|
||||
unit = 'LENGTH'
|
||||
min = 0.0,
|
||||
unit = 'LENGTH',
|
||||
translation_context = 'BBP_OT_add_spiral_rail/property'
|
||||
) # type: ignore
|
||||
|
||||
rail_screw_iterations: bpy.props.IntProperty(
|
||||
@ -313,35 +427,42 @@ class BBP_OT_add_spiral_rail(SharedRailCapInputProperty, SharedScrewRailInputPro
|
||||
description = "Indicate how many layers of this spiral rail should be generated.",
|
||||
default = 1,
|
||||
min = 1,
|
||||
translation_context = 'BBP_OT_add_spiral_rail/property'
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_screw_rail(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_screw_rail(
|
||||
bm,
|
||||
False, c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
||||
360, self.rail_screw_screw, self.rail_screw_iterations,
|
||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
|
||||
)
|
||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius(),
|
||||
self.general_get_rail_screw_flip_x(), self.general_get_rail_screw_flip_y(), self.general_get_rail_screw_flip_z()
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Spiral Rail')
|
||||
layout.label(text='Spiral Rail', text_ctxt='BBP_OT_add_spiral_rail/draw')
|
||||
self.draw_screw_rail_input(layout)
|
||||
layout.prop(self, "rail_screw_screw")
|
||||
layout.prop(self, "rail_screw_iterations")
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_screw_rail_flip_input(layout)
|
||||
layout.separator()
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
class BBP_OT_add_side_spiral_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
||||
class BBP_OT_add_side_spiral_rail(SharedExtraTransform, SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
||||
"""Add Side Spiral Rail"""
|
||||
bl_idname = "bbp.add_side_spiral_rail"
|
||||
bl_label = "Side Spiral Rail"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_side_spiral_rail'
|
||||
|
||||
rail_screw_iterations: bpy.props.IntProperty(
|
||||
name = "Iterations",
|
||||
@ -350,337 +471,38 @@ class BBP_OT_add_side_spiral_rail(SharedRailSectionInputProperty, SharedRailCapI
|
||||
# at least 2 ietrations can create 1 useful side spiral rail.
|
||||
# becuase side spiral rail is edge shared.
|
||||
min = 2,
|
||||
translation_context = 'BBP_OT_add_side_spiral_rail/property'
|
||||
) # type: ignore
|
||||
|
||||
def execute(self, context):
|
||||
_rail_creator_wrapper(
|
||||
lambda bm: _create_screw_rail(
|
||||
UTIL_rail_creator.rail_creator_wrapper(
|
||||
lambda bm: UTIL_rail_creator.create_screw_rail(
|
||||
bm,
|
||||
True, c_DefaultRailRadius, c_DefaultRailSpan,
|
||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
||||
360, c_SideSpiralRailScrew, self.rail_screw_iterations,
|
||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
|
||||
)
|
||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius(),
|
||||
self.general_get_rail_screw_flip_x(), self.general_get_rail_screw_flip_y(), self.general_get_rail_screw_flip_z()
|
||||
),
|
||||
self.general_get_extra_transform()
|
||||
)
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Spiral Rail')
|
||||
layout.label(text='Spiral Rail', text_ctxt='BBP_OT_add_side_spiral_rail/draw')
|
||||
self.draw_screw_rail_input(layout)
|
||||
layout.prop(self, "rail_screw_iterations")
|
||||
layout.separator()
|
||||
layout.label(text = 'Rail Cap')
|
||||
self.draw_screw_rail_flip_input(layout)
|
||||
layout.separator()
|
||||
self.draw_rail_cap_input(layout)
|
||||
layout.separator()
|
||||
self.draw_extra_transform_input(layout)
|
||||
|
||||
#endregion
|
||||
|
||||
#region BMesh Operations Helper
|
||||
|
||||
def _bmesh_extrude(bm: bmesh.types.BMesh, start_edges: list[bmesh.types.BMEdge], direction: mathutils.Vector) -> list[bmesh.types.BMEdge]:
|
||||
# extrude
|
||||
ret: dict[str, typing.Any] = bmesh.ops.extrude_edge_only(
|
||||
bm,
|
||||
edges = start_edges,
|
||||
use_normal_flip = True, # NOTE: flip normal according to test result.
|
||||
use_select_history = False
|
||||
)
|
||||
|
||||
# get end edges
|
||||
ret_geom = ret['geom']
|
||||
del ret
|
||||
end_verts: list[bmesh.types.BMVert] = list(filter(lambda x: isinstance(x, bmesh.types.BMVert), ret_geom))
|
||||
end_edges: list[bmesh.types.BMEdge] = list(filter(lambda x: isinstance(x, bmesh.types.BMEdge) and x.is_boundary, ret_geom))
|
||||
# and move it
|
||||
bmesh.ops.translate(
|
||||
bm,
|
||||
vec = direction, space = mathutils.Matrix.Identity(4),
|
||||
verts = end_verts,
|
||||
use_shapekey = False
|
||||
)
|
||||
|
||||
# return value
|
||||
return end_edges
|
||||
|
||||
def _bmesh_screw(
|
||||
bm: bmesh.types.BMesh,
|
||||
start_verts: list[bmesh.types.BMVert], start_edges: list[bmesh.types.BMEdge],
|
||||
angle: float, steps: int, iterations: int,
|
||||
center: mathutils.Vector, screw_per_iteration: float) -> list[bmesh.types.BMEdge]:
|
||||
"""
|
||||
Hints: Angle is input as degree unit.
|
||||
"""
|
||||
# screw
|
||||
ret: dict[str, typing.Any] = bmesh.ops.spin(
|
||||
bm,
|
||||
geom = start_edges,
|
||||
cent = center,
|
||||
axis = mathutils.Vector((0, 0, 1)), # default to +Z
|
||||
dvec = mathutils.Vector((0, 0, screw_per_iteration / steps)), # conv to step delta
|
||||
angle = math.radians(angle) * iterations,
|
||||
space = mathutils.Matrix.Identity(4),
|
||||
steps = steps * iterations,
|
||||
use_merge = False,
|
||||
use_normal_flip = True, # NOTE: flip nml according to real test result
|
||||
use_duplicate = False
|
||||
)
|
||||
|
||||
# return last segment
|
||||
geom_last = ret['geom_last']
|
||||
del ret
|
||||
return list(filter(lambda x: isinstance(x, bmesh.types.BMEdge), geom_last))
|
||||
|
||||
def _bmesh_smooth_all_edges(bm: bmesh.types.BMesh) -> None:
|
||||
"""
|
||||
Resrt all edges to smooth. Call this before calling edge cap function.
|
||||
"""
|
||||
# reset all edges to smooth
|
||||
edge: bmesh.types.BMEdge
|
||||
for edge in bm.edges:
|
||||
edge.smooth = True
|
||||
|
||||
def _bmesh_cap(bm: bmesh.types.BMesh, edges: list[bmesh.types.BMEdge]) -> None:
|
||||
"""
|
||||
Cap given edges. And mark it as sharp edge.
|
||||
Please reset all edges to smooth one before calling this.
|
||||
"""
|
||||
# fill holes
|
||||
bmesh.ops.triangle_fill(
|
||||
bm,
|
||||
use_beauty = False, use_dissolve = False,
|
||||
edges = edges
|
||||
# no pass to normal.
|
||||
)
|
||||
|
||||
# and only set sharp for cap's edges
|
||||
for edge in edges:
|
||||
edge.smooth = False
|
||||
|
||||
#endregion
|
||||
|
||||
#region Real Rail Creators
|
||||
|
||||
def _rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], None]) -> bpy.types.Object:
|
||||
# create mesh first
|
||||
bm: bmesh.types.BMesh = bmesh.new()
|
||||
|
||||
# call cret fct
|
||||
fct_poly_cret(bm)
|
||||
|
||||
# finish up
|
||||
mesh: bpy.types.Mesh = bpy.data.meshes.new('Rail')
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
|
||||
# setup smooth for mesh
|
||||
mesh.use_auto_smooth = True
|
||||
mesh.auto_smooth_angle = math.radians(50)
|
||||
mesh.shade_smooth()
|
||||
|
||||
# create object and assoc with it
|
||||
# create info first
|
||||
rail_info: UTIL_naming_convension.BallanceObjectInfo = UTIL_naming_convension.BallanceObjectInfo.create_from_others(
|
||||
UTIL_naming_convension.BallanceObjectType.RAIL
|
||||
)
|
||||
# then get object name
|
||||
rail_name: str | None = UTIL_naming_convension.YYCToolchainConvention.set_to_name(rail_info, None)
|
||||
if rail_name is None: raise UTIL_functions.BBPException('impossible null name')
|
||||
# create object by name
|
||||
obj: bpy.types.Object = bpy.data.objects.new(rail_name, mesh)
|
||||
# assign virtools groups
|
||||
UTIL_naming_convension.VirtoolsGroupConvention.set_to_object(obj, rail_info, None)
|
||||
|
||||
# move to cursor
|
||||
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
||||
|
||||
# return rail
|
||||
return obj
|
||||
|
||||
def _create_rail_section(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
matrix: mathutils.Matrix = mathutils.Matrix.Identity(4)) -> None:
|
||||
"""
|
||||
Add a rail section.
|
||||
|
||||
If created is monorail, the original point locate at the center of section.
|
||||
Otherwise, the original point locate at the center point of the line connecting between left rail section and right rail section.
|
||||
The section will be placed in XZ panel.
|
||||
|
||||
If ordered is monorail, `rail_span` param will be ignored.
|
||||
"""
|
||||
if is_monorail:
|
||||
# create monorail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
None,
|
||||
mathutils.Euler((math.radians(90), math.radians(22.5), 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
else:
|
||||
# create rail
|
||||
# create left rail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector((-rail_span / 2, 0, 0)),
|
||||
mathutils.Euler((math.radians(90), 0, 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
# create right rail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector((rail_span / 2, 0, 0)),
|
||||
mathutils.Euler((math.radians(90), 0, 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
|
||||
def _create_transition_section(
|
||||
bm: bmesh.types.BMesh,
|
||||
rail_radius: float, rail_span: float) -> None:
|
||||
"""
|
||||
Create the transition section between rail and monorail.
|
||||
"""
|
||||
# create rail section
|
||||
_create_rail_section(bm, False, rail_radius, rail_span)
|
||||
|
||||
# create monorail
|
||||
# calc sink first
|
||||
monorail_sink: float
|
||||
try:
|
||||
monorail_sink = math.sqrt((rail_radius + 2) ** 2 - (rail_span / 2) ** 2) - 2 - rail_radius
|
||||
except:
|
||||
monorail_sink = -2 # if sqrt(minus number) happended, it mean no triangle relation. the depth should always be -2.
|
||||
# create monorail with calculated sink
|
||||
_create_rail_section(
|
||||
bm, True, rail_radius, rail_span,
|
||||
mathutils.Matrix.Translation((0, 0, monorail_sink))
|
||||
)
|
||||
|
||||
def _create_straight_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
rail_length: float, rail_angle: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool) -> None:
|
||||
"""
|
||||
Add a straight rail.
|
||||
|
||||
The original point is same as `_add_rail_section()`.
|
||||
The start terminal of this straight will be placed in XZ panel.
|
||||
The expand direction is +Y.
|
||||
|
||||
If ordered is monorail, `rail_span` param will be ignored.
|
||||
|
||||
The rail angle is in degree unit and indicate how any angle this rail should rotated by its axis.
|
||||
It usually used to create side rail.
|
||||
"""
|
||||
# create section first
|
||||
_create_rail_section(
|
||||
bm, is_monorail, rail_radius, rail_span,
|
||||
mathutils.Matrix.LocRotScale(
|
||||
None,
|
||||
mathutils.Euler((0, math.radians(rail_angle), 0), 'XYZ'),
|
||||
None
|
||||
)
|
||||
)
|
||||
|
||||
# get start edges
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
# extrude and get end edges
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_extrude(
|
||||
bm, start_edges, mathutils.Vector((0, rail_length, 0))
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
def _create_transition_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
rail_radius: float, rail_span: float,
|
||||
rail_length: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool) -> None:
|
||||
"""
|
||||
Add a transition rail.
|
||||
|
||||
The original point is same as `_add_transition_section()`.
|
||||
The start terminal of this straight will be placed in XZ panel.
|
||||
The expand direction is +Y.
|
||||
"""
|
||||
# create section first
|
||||
_create_transition_section(bm, rail_radius, rail_span)
|
||||
|
||||
# get start edges
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
# extrude and get end edges
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_extrude(
|
||||
bm, start_edges, mathutils.Vector((0, rail_length, 0))
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
def _create_screw_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool,
|
||||
rail_screw_angle: float, rail_screw_screw: float, rail_screw_iterations: int,
|
||||
rail_screw_steps: int, rail_screw_radius: float) -> None:
|
||||
"""
|
||||
Add a screw rail.
|
||||
|
||||
The original point is same as `_add_rail_section()`.
|
||||
The start terminal of this straight will be placed in XZ panel.
|
||||
The expand direction is +Y.
|
||||
|
||||
If ordered is monorail, `rail_span` param will be ignored.
|
||||
|
||||
Angle is input as degree unit.
|
||||
"""
|
||||
# create section first
|
||||
_create_rail_section(bm, is_monorail, rail_radius, rail_span)
|
||||
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_screw(
|
||||
bm,
|
||||
bm.verts[:], start_edges,
|
||||
rail_screw_angle,
|
||||
rail_screw_steps, rail_screw_iterations,
|
||||
mathutils.Vector((rail_screw_radius, 0, 0)),
|
||||
rail_screw_screw
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
#endregion
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_add_rail_section)
|
||||
bpy.utils.register_class(BBP_OT_add_transition_section)
|
||||
|
||||
@ -693,7 +515,7 @@ def register():
|
||||
bpy.utils.register_class(BBP_OT_add_side_spiral_rail)
|
||||
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_add_side_spiral_rail)
|
||||
bpy.utils.unregister_class(BBP_OT_add_spiral_rail)
|
||||
bpy.utils.unregister_class(BBP_OT_add_arc_rail)
|
||||
|
@ -1,29 +1,25 @@
|
||||
import bpy
|
||||
from . import PROP_preferences, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_icons_manager, UTIL_ioport_shared
|
||||
from . import PROP_preferences, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ioport_shared
|
||||
|
||||
class BBP_OT_export_bmfile(bpy.types.Operator, UTIL_file_browser.ExportBmxFile, UTIL_ioport_shared.ExportParams):
|
||||
"""Save a Ballance Map File (BM File Spec 1.4)"""
|
||||
bl_idname = "bbp.export_bmfile"
|
||||
bl_label = "Export BM (Ballance Map) File"
|
||||
bl_options = {'PRESET'}
|
||||
bl_translation_context = 'BBP_OT_export_bmfile'
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
def poll(cls, context):
|
||||
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||
|
||||
def execute(self, context):
|
||||
UTIL_functions.message_box(
|
||||
('This function not supported yet.', ),
|
||||
'No Implement',
|
||||
UTIL_icons_manager.BlenderPresetIcons.Error.value
|
||||
)
|
||||
self.report({'INFO'}, "BM File Exporting Finished.")
|
||||
self.report({'ERROR'}, 'This feature is not supported yet.')
|
||||
# self.report({'INFO'}, "BM File Exporting Finished.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Export Target')
|
||||
self.draw_export_params(layout.box())
|
||||
self.draw_export_params(context, layout.box())
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_export_bmfile)
|
||||
|
@ -1,64 +1,53 @@
|
||||
import bpy
|
||||
import bpy, mathutils
|
||||
from bpy_extras.wm_utils.progress_report import ProgressReport
|
||||
import tempfile, os, typing, re
|
||||
import tempfile, os, typing
|
||||
from . import PROP_preferences, UTIL_ioport_shared
|
||||
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_icons_manager
|
||||
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture
|
||||
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_naming_convension
|
||||
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_virtools_light
|
||||
from .PyBMap import bmap_wrapper as bmap
|
||||
|
||||
# define global tex save opt blender enum prop helper
|
||||
_g_EnumHelper_CK_TEXTURE_SAVEOPTIONS: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS)
|
||||
|
||||
class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtoolsFile, UTIL_ioport_shared.ExportParams, UTIL_ioport_shared.VirtoolsParams):
|
||||
class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtoolsFile, UTIL_ioport_shared.ExportParams, UTIL_ioport_shared.VirtoolsParams, UTIL_ioport_shared.BallanceParams):
|
||||
"""Export Virtools File"""
|
||||
bl_idname = "bbp.export_virtools"
|
||||
bl_label = "Export Virtools File"
|
||||
bl_options = {'PRESET'}
|
||||
|
||||
texture_save_opt: bpy.props.EnumProperty(
|
||||
name = "Global Texture Save Options",
|
||||
description = "Decide how texture saved if texture is specified as Use Global as its Save Options.",
|
||||
items = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.generate_items(),
|
||||
default = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.to_selection(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL)
|
||||
)
|
||||
|
||||
use_compress: bpy.props.BoolProperty(
|
||||
name="Use Compress",
|
||||
default = True,
|
||||
)
|
||||
|
||||
compress_level: bpy.props.IntProperty(
|
||||
name = "Compress Level",
|
||||
description = "The ZLib compress level used by Virtools Engine when saving composition.",
|
||||
min = 1, max = 9,
|
||||
default = 5,
|
||||
)
|
||||
bl_translation_context = 'BBP_OT_export_virtools'
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
def poll(cls, context):
|
||||
return (
|
||||
PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||
and bmap.is_bmap_available())
|
||||
|
||||
def execute(self, context):
|
||||
# check selecting first
|
||||
objls: tuple[bpy.types.Object] | None = self.general_get_export_objects()
|
||||
objls: tuple[bpy.types.Object] | None = self.general_get_export_objects(context)
|
||||
if objls is None:
|
||||
UTIL_functions.message_box(
|
||||
('No selected target!', ),
|
||||
'Lost Parameters',
|
||||
UTIL_icons_manager.BlenderPresetIcons.Error.value
|
||||
)
|
||||
self.report({'ERROR'}, 'No selected target!')
|
||||
return {'CANCELLED'}
|
||||
|
||||
# check texture save option to avoid real stupid user.
|
||||
texture_save_opt = self.general_get_texture_save_opt()
|
||||
if texture_save_opt == UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_USEGLOBAL:
|
||||
self.report({'ERROR'}, 'You can not specify "Use Global" as global texture save option!')
|
||||
return {'CANCELLED'}
|
||||
|
||||
# check whether encoding list is empty to avoid real stupid user.
|
||||
encodings = self.general_get_vt_encodings(context)
|
||||
if len(encodings) == 0:
|
||||
self.report({'ERROR'}, 'You must specify at least one encoding for file saving (e.g. cp1252, gbk)!')
|
||||
return {'CANCELLED'}
|
||||
|
||||
# start exporting
|
||||
with UTIL_ioport_shared.ExportEditModeBackup() as editmode_guard:
|
||||
_export_virtools(
|
||||
self.general_get_filename(),
|
||||
self.general_get_vt_encodings(),
|
||||
_g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.get_selection(self.texture_save_opt),
|
||||
self.use_compress,
|
||||
self.compress_level,
|
||||
encodings,
|
||||
texture_save_opt,
|
||||
self.general_get_use_compress(),
|
||||
self.general_get_compress_level(),
|
||||
self.general_get_successive_sector(),
|
||||
self.general_get_successive_sector_count(),
|
||||
objls
|
||||
)
|
||||
|
||||
@ -67,24 +56,12 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Export Target')
|
||||
self.draw_export_params(layout.box())
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = 'Virtools Params')
|
||||
box = layout.box()
|
||||
self.draw_virtools_params(box)
|
||||
|
||||
box.separator()
|
||||
box.label(text = 'Global Texture Save Option')
|
||||
box.prop(self, 'texture_save_opt', text = '')
|
||||
|
||||
box.separator()
|
||||
box.prop(self, 'use_compress')
|
||||
if self.use_compress:
|
||||
box.prop(self, 'compress_level')
|
||||
self.draw_export_params(context, layout)
|
||||
self.draw_virtools_params(context, layout, False)
|
||||
self.draw_ballance_params(layout, False)
|
||||
|
||||
_TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject]
|
||||
_TLightPair = tuple[bpy.types.Object, bpy.types.Light, bmap.BMTargetLight]
|
||||
_TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh]
|
||||
_TMaterialPair = tuple[bpy.types.Material, bmap.BMMaterial]
|
||||
_TTexturePair = tuple[bpy.types.Image, bmap.BMTexture]
|
||||
@ -95,12 +72,16 @@ def _export_virtools(
|
||||
texture_save_opt_: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS,
|
||||
use_compress_: bool,
|
||||
compress_level_: int,
|
||||
successive_sector_: bool,
|
||||
successive_sector_count_: int,
|
||||
export_objects: tuple[bpy.types.Object, ...]
|
||||
) -> None:
|
||||
|
||||
# create temp folder
|
||||
with tempfile.TemporaryDirectory() as vt_temp_folder:
|
||||
print(f'Virtools Engine Temp: {vt_temp_folder}')
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt(
|
||||
'Virtools Engine Temporary Directory: {0}', 'BBP_OT_export_virtools/execute')
|
||||
print(tr_text.format(vt_temp_folder))
|
||||
|
||||
# create virtools reader context
|
||||
with bmap.BMFileWriter(
|
||||
@ -110,11 +91,15 @@ def _export_virtools(
|
||||
|
||||
# prepare progress reporter
|
||||
with ProgressReport(wm = bpy.context.window_manager) as progress:
|
||||
# prepare 3dobject
|
||||
obj3d_crets: tuple[_TObj3dPair, ...] = _prepare_virtools_3dobjects(
|
||||
writer, progress, export_objects)
|
||||
# export group and 3dobject by prepared 3dobject
|
||||
_export_virtools_groups(writer, progress, obj3d_crets)
|
||||
# prepare 3dobject and light
|
||||
obj3d_crets: tuple[_TObj3dPair, ...]
|
||||
light_crets: tuple[_TLightPair, ...]
|
||||
(obj3d_crets, light_crets) = _prepare_virtools_3dobjects(writer, progress, export_objects)
|
||||
# export group according to prepared 3dobject
|
||||
_export_virtools_groups(writer, progress, successive_sector_, successive_sector_count_, obj3d_crets)
|
||||
# export prepared light
|
||||
_export_virtools_light(writer, progress, light_crets)
|
||||
# export prepared 3dobject
|
||||
mesh_crets: tuple[_TMeshPair, ...] = _export_virtools_3dobjects(
|
||||
writer, progress, obj3d_crets)
|
||||
# export mesh
|
||||
@ -133,18 +118,31 @@ def _export_virtools(
|
||||
def _prepare_virtools_3dobjects(
|
||||
writer: bmap.BMFileWriter,
|
||||
progress: ProgressReport,
|
||||
export_objects: tuple[bpy.types.Object]
|
||||
) -> tuple[_TObj3dPair, ...]:
|
||||
export_objects: tuple[bpy.types.Object, ...]
|
||||
) -> tuple[tuple[_TObj3dPair, ...], tuple[_TLightPair, ...]]:
|
||||
# this function only create equvalent entries in virtools engine and do not export anything
|
||||
# because _export_virtools_3dobjects() and _export_virtools_groups() are need use the return value of this function
|
||||
#
|
||||
# at the same time, due to the difference of light object between virtools and blender,
|
||||
# we also need extract exported lights and create equvalent entries in virtools for them.
|
||||
|
||||
# create 3dobject hashset and result
|
||||
obj3d_crets: list[_TObj3dPair] = []
|
||||
obj3d_cret_set: set[bpy.types.Object] = set()
|
||||
# create light hashset and result
|
||||
light_crets: list[_TLightPair] = []
|
||||
light_cret_set: set[bpy.types.Object] = set()
|
||||
# start saving
|
||||
progress.enter_substeps(len(export_objects), "Creating 3dObjects")
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Creating 3dObjects and Lights', 'BBP_OT_export_virtools/execute')
|
||||
progress.enter_substeps(len(export_objects), tr_text)
|
||||
|
||||
# iterate exported object list
|
||||
for obj3d in export_objects:
|
||||
# only accept mesh object and light object
|
||||
# all of other objects will be discard.
|
||||
match(obj3d.type):
|
||||
case 'MESH':
|
||||
# mesh object
|
||||
if obj3d not in obj3d_cret_set:
|
||||
# add into set
|
||||
obj3d_cret_set.add(obj3d)
|
||||
@ -152,26 +150,52 @@ def _prepare_virtools_3dobjects(
|
||||
vtobj3d: bmap.BM3dObject = writer.create_3dobject()
|
||||
# add into result list
|
||||
obj3d_crets.append((obj3d, vtobj3d))
|
||||
case 'LIGHT':
|
||||
# light object
|
||||
if obj3d not in light_cret_set:
|
||||
# add into set
|
||||
light_cret_set.add(obj3d)
|
||||
# create virtools instance
|
||||
vtlight: bmap.BMTargetLight = writer.create_target_light()
|
||||
# add into result list
|
||||
light_crets.append((obj3d, typing.cast(bpy.types.Light, obj3d.data), vtlight))
|
||||
|
||||
# step progress no matter whether create new one
|
||||
progress.step()
|
||||
|
||||
# leave progress and return
|
||||
progress.leave_substeps()
|
||||
return tuple(obj3d_crets)
|
||||
return (tuple(obj3d_crets), tuple(light_crets))
|
||||
|
||||
def _export_virtools_groups(
|
||||
writer: bmap.BMFileWriter,
|
||||
progress: ProgressReport,
|
||||
successive_sector: bool,
|
||||
successive_sector_count: int,
|
||||
obj3d_crets: tuple[_TObj3dPair, ...]
|
||||
) -> None:
|
||||
# create virtools group
|
||||
group_cret_map: dict[str, bmap.BMGroup] = {}
|
||||
# start saving
|
||||
progress.enter_substeps(len(obj3d_crets), "Saving Groups")
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Saving Groups', 'BBP_OT_export_virtools/execute')
|
||||
progress.enter_substeps(len(obj3d_crets), tr_text)
|
||||
|
||||
# create group exporting helper
|
||||
group_cret_guard: VirtoolsGroupCreationGuard = VirtoolsGroupCreationGuard(writer)
|
||||
# create sector group first if user ordered
|
||||
# This step is designed for ensure that the created sector group is successive.
|
||||
#
|
||||
# Due to the design of Ballance, Ballance rely on checking the existance of Sector_XX to get how many sectors this map have.
|
||||
# Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash
|
||||
# or be ended at a accident sector.
|
||||
#
|
||||
# So we create all needed sector group in here to make sure exported virtools file can be read by Ballancde correctly.
|
||||
if successive_sector:
|
||||
for i in range(successive_sector_count):
|
||||
gp_name: str = UTIL_naming_convension.build_name_from_sector_index(i + 1)
|
||||
vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None)
|
||||
if vtgroup is None:
|
||||
vtgroup = writer.create_group()
|
||||
vtgroup.set_name(gp_name)
|
||||
group_cret_map[gp_name] = vtgroup
|
||||
|
||||
for obj3d, vtobj3d in obj3d_crets:
|
||||
# open group visitor
|
||||
@ -180,8 +204,8 @@ def _export_virtools_groups(
|
||||
# get group or create new group from guard
|
||||
vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None)
|
||||
if vtgroup is None:
|
||||
# note: no need to set name, guard has set it.
|
||||
vtgroup = group_cret_guard.create_group(gp_name)
|
||||
vtgroup = writer.create_group()
|
||||
vtgroup.set_name(gp_name)
|
||||
group_cret_map[gp_name] = vtgroup
|
||||
|
||||
# group this object
|
||||
@ -193,6 +217,51 @@ def _export_virtools_groups(
|
||||
# leave progress and return
|
||||
progress.leave_substeps()
|
||||
|
||||
def _export_virtools_light(
|
||||
writer: bmap.BMFileWriter,
|
||||
progress: ProgressReport,
|
||||
light_crets: tuple[_TLightPair, ...]
|
||||
) -> None:
|
||||
# start saving
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Saving Lights', 'BBP_OT_export_virtools/execute')
|
||||
progress.enter_substeps(len(light_crets), tr_text)
|
||||
|
||||
for obj3d, light, vtlight in light_crets:
|
||||
# set name
|
||||
vtlight.set_name(obj3d.name)
|
||||
|
||||
# setup 3d entity parts
|
||||
# set world matrix
|
||||
vtmat: UTIL_virtools_types.VxMatrix = UTIL_virtools_types.VxMatrix()
|
||||
bldmat: mathutils.Matrix = UTIL_virtools_types.bldmatrix_restore_light_obj(obj3d.matrix_world)
|
||||
UTIL_virtools_types.vxmatrix_from_blender(vtmat, bldmat)
|
||||
UTIL_virtools_types.vxmatrix_conv_co(vtmat)
|
||||
vtlight.set_world_matrix(vtmat)
|
||||
# set visibility
|
||||
vtlight.set_visibility(not obj3d.hide_get())
|
||||
|
||||
# setup light data
|
||||
rawlight: PROP_virtools_light.RawVirtoolsLight = PROP_virtools_light.get_raw_virtools_light(light)
|
||||
|
||||
vtlight.set_type(rawlight.mType)
|
||||
vtlight.set_color(rawlight.mColor)
|
||||
|
||||
vtlight.set_constant_attenuation(rawlight.mConstantAttenuation)
|
||||
vtlight.set_linear_attenuation(rawlight.mLinearAttenuation)
|
||||
vtlight.set_quadratic_attenuation(rawlight.mQuadraticAttenuation)
|
||||
|
||||
vtlight.set_range(rawlight.mRange)
|
||||
|
||||
vtlight.set_hot_spot(rawlight.mHotSpot)
|
||||
vtlight.set_falloff(rawlight.mFalloff)
|
||||
vtlight.set_falloff_shape(rawlight.mFalloffShape)
|
||||
|
||||
# step
|
||||
progress.step()
|
||||
|
||||
# leave progress and return
|
||||
progress.leave_substeps()
|
||||
|
||||
def _export_virtools_3dobjects(
|
||||
writer: bmap.BMFileWriter,
|
||||
progress: ProgressReport,
|
||||
@ -202,14 +271,15 @@ def _export_virtools_3dobjects(
|
||||
mesh_crets: list[_TMeshPair] = []
|
||||
mesh_cret_map: dict[bpy.types.Mesh, bmap.BMMesh] = {}
|
||||
# start saving
|
||||
progress.enter_substeps(len(obj3d_crets), "Saving 3dObjects")
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Saving 3dObjects', 'BBP_OT_export_virtools/execute')
|
||||
progress.enter_substeps(len(obj3d_crets), tr_text)
|
||||
|
||||
for obj3d, vtobj3d in obj3d_crets:
|
||||
# set name
|
||||
vtobj3d.set_name(obj3d.name)
|
||||
|
||||
# check mesh
|
||||
mesh: bpy.types.Mesh | None = obj3d.data
|
||||
mesh: bpy.types.Mesh | None = typing.cast(bpy.types.Mesh | None, obj3d.data)
|
||||
if mesh is not None:
|
||||
# get existing vt mesh or create new one
|
||||
vtmesh: bmap.BMMesh | None = mesh_cret_map.get(mesh, None)
|
||||
@ -248,7 +318,8 @@ def _export_virtools_meshes(
|
||||
material_crets: list[_TMaterialPair] = []
|
||||
material_cret_map: dict[bpy.types.Material, bmap.BMMaterial] = {}
|
||||
# start saving
|
||||
progress.enter_substeps(len(mesh_crets), "Saving Meshes")
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Saving Meshes', 'BBP_OT_export_virtools/execute')
|
||||
progress.enter_substeps(len(mesh_crets), tr_text)
|
||||
|
||||
# iterate meshes
|
||||
for obj3d, mesh, vtmesh in mesh_crets:
|
||||
@ -340,7 +411,7 @@ def _export_virtools_meshes(
|
||||
)
|
||||
|
||||
# parse to vtmesh
|
||||
mesh_trans.parse(writer, vtmesh)
|
||||
mesh_trans.parse(vtmesh)
|
||||
|
||||
# end of mesh trans
|
||||
# end of mesh visitor
|
||||
@ -362,7 +433,8 @@ def _export_virtools_materials(
|
||||
texture_crets: list[_TTexturePair] = []
|
||||
texture_cret_map: dict[bpy.types.Image, bmap.BMTexture] = {}
|
||||
# start saving
|
||||
progress.enter_substeps(len(material_crets), "Saving Materials")
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Saving Materials', 'BBP_OT_export_virtools/execute')
|
||||
progress.enter_substeps(len(material_crets), tr_text)
|
||||
|
||||
for mtl, vtmaterial in material_crets:
|
||||
# set name
|
||||
@ -427,7 +499,8 @@ def _export_virtools_textures(
|
||||
texture_crets: tuple[_TTexturePair, ...]
|
||||
) -> None:
|
||||
# start saving
|
||||
progress.enter_substeps(len(texture_crets), "Saving Textures")
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Saving Textures', 'BBP_OT_export_virtools/execute')
|
||||
progress.enter_substeps(len(texture_crets), tr_text)
|
||||
|
||||
for tex, vttexture in texture_crets:
|
||||
# set name
|
||||
@ -466,78 +539,12 @@ def _save_virtools_document(
|
||||
compress_level: int
|
||||
) -> None:
|
||||
|
||||
progress.enter_substeps(1, "Saving Document")
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Saving Document', 'BBP_OT_export_virtools/execute')
|
||||
progress.enter_substeps(1, tr_text)
|
||||
writer.save(file_name, texture_save_opt, use_compress, compress_level)
|
||||
progress.step()
|
||||
progress.leave_substeps()
|
||||
|
||||
class VirtoolsGroupCreationGuard():
|
||||
"""
|
||||
This class is designed for ensure that the created sector group is successive.
|
||||
|
||||
Due to the design of Ballance, Ballance rely on checking the existance of Sector_XX to get how many sectors this map have.
|
||||
Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash
|
||||
or be ended at a accident sector.
|
||||
|
||||
This class hook the operation of Virtools group creation and check all Sector group creation.
|
||||
Create essential group to make Sector_XX group successive.
|
||||
Thus all group creation in this module should be passed by this class.
|
||||
"""
|
||||
cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
|
||||
|
||||
@staticmethod
|
||||
def __get_group_index(group_name: str) -> int | None:
|
||||
"""
|
||||
Return the sector index of group name if it is. Otherwise None.
|
||||
"""
|
||||
regex_result = VirtoolsGroupCreationGuard.cRegexGroupSector.match(group_name)
|
||||
if regex_result is not None:
|
||||
return int(regex_result.group(1))
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def __get_group_name(group_index: int) -> str:
|
||||
"""
|
||||
Output Sector group name by given sector index
|
||||
"""
|
||||
if group_index == 9:
|
||||
return 'Sector_9'
|
||||
else:
|
||||
return f'Sector_{group_index:0>2d}'
|
||||
|
||||
__mWriter: bmap.BMFileWriter
|
||||
__mSectors: list[bmap.BMGroup]
|
||||
|
||||
def __init__(self, assoc_writer: bmap.BMFileWriter):
|
||||
self.__mWriter = assoc_writer
|
||||
self.__mSectors = []
|
||||
|
||||
def create_group(self, group_name: str) -> bmap.BMGroup:
|
||||
"""
|
||||
The hooked group creation function.
|
||||
|
||||
Please note the passed group name argument is just for name checking.
|
||||
This function will set group name for you, not like BMFileWriter.create_group() operated.
|
||||
"""
|
||||
# check whether it is sector group
|
||||
# note: the return sector index is 1 based, not 0
|
||||
sector_idx: int | None = VirtoolsGroupCreationGuard.__get_group_index(group_name)
|
||||
# if it is regular group, return normal creation
|
||||
if sector_idx is None:
|
||||
gp: bmap.BMGroup = self.__mWriter.create_group()
|
||||
gp.set_name(group_name)
|
||||
return gp
|
||||
|
||||
# get from sector cahce list
|
||||
# enlarge sector cache list if it is not fulfilled given sector index
|
||||
while sector_idx > len(self.__mSectors):
|
||||
gp: bmap.BMGroup = self.__mWriter.create_group()
|
||||
self.__mSectors.append(gp)
|
||||
gp.set_name(self.__get_group_name(len(self.__mSectors)))
|
||||
# return ordered sector from sector caches
|
||||
return self.__mSectors[sector_idx - 1]
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_export_virtools)
|
||||
|
||||
|
@ -1,28 +1,24 @@
|
||||
import bpy
|
||||
from . import PROP_preferences, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_icons_manager, UTIL_ioport_shared
|
||||
from . import PROP_preferences, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ioport_shared
|
||||
|
||||
class BBP_OT_import_bmfile(bpy.types.Operator, UTIL_file_browser.ImportBmxFile, UTIL_ioport_shared.ImportParams):
|
||||
"""Load a Ballance Map File (BM File Spec 1.4)"""
|
||||
bl_idname = "bbp.import_bmfile"
|
||||
bl_label = "Import BM (Ballance Map) File"
|
||||
bl_options = {'PRESET', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_import_bmfile'
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
def poll(cls, context):
|
||||
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||
|
||||
def execute(self, context):
|
||||
UTIL_functions.message_box(
|
||||
('This function not supported yet.', ),
|
||||
'No Implement',
|
||||
UTIL_icons_manager.BlenderPresetIcons.Error.value
|
||||
)
|
||||
self.report({'INFO'}, "BM File Importing Finished.")
|
||||
self.report({'ERROR'}, 'This feature is not supported yet.')
|
||||
# self.report({'INFO'}, "BM File Importing Finished.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Conflict Options')
|
||||
self.draw_import_params(layout.box())
|
||||
|
||||
def register() -> None:
|
||||
|
@ -1,27 +1,34 @@
|
||||
import bpy
|
||||
import bpy, mathutils
|
||||
from bpy_extras.wm_utils.progress_report import ProgressReport
|
||||
import tempfile, os, typing
|
||||
from . import PROP_preferences, UTIL_ioport_shared
|
||||
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture
|
||||
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture
|
||||
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_naming_convension
|
||||
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_virtools_light, PROP_ballance_map_info
|
||||
from .PyBMap import bmap_wrapper as bmap
|
||||
|
||||
class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams):
|
||||
class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams, UTIL_ioport_shared.BallanceParams):
|
||||
"""Import Virtools File"""
|
||||
bl_idname = "bbp.import_virtools"
|
||||
bl_label = "Import Virtools File"
|
||||
bl_options = {'PRESET', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_import_virtools'
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
def poll(cls, context):
|
||||
return (
|
||||
PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||
and bmap.is_bmap_available())
|
||||
|
||||
def execute(self, context):
|
||||
# check whether encoding list is empty to avoid real stupid user.
|
||||
encodings = self.general_get_vt_encodings(context)
|
||||
if len(encodings) == 0:
|
||||
self.report({'ERROR'}, 'You must specify at least one encoding for file loading (e.g. cp1252, gbk)!')
|
||||
return {'CANCELLED'}
|
||||
|
||||
_import_virtools(
|
||||
self.general_get_filename(),
|
||||
self.general_get_vt_encodings(),
|
||||
encodings,
|
||||
self.general_get_conflict_resolver()
|
||||
)
|
||||
self.report({'INFO'}, "Virtools File Importing Finished.")
|
||||
@ -29,16 +36,16 @@ class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtool
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'Conflict Options')
|
||||
self.draw_import_params(layout.box())
|
||||
layout.separator()
|
||||
layout.label(text = 'Virtools Params')
|
||||
self.draw_virtools_params(layout.box())
|
||||
self.draw_import_params(layout)
|
||||
self.draw_virtools_params(context, layout, True)
|
||||
self.draw_ballance_params(layout, True)
|
||||
|
||||
def _import_virtools(file_name_: str, encodings_: tuple[str], resolver: UTIL_ioport_shared.ConflictResolver) -> None:
|
||||
# create temp folder
|
||||
with tempfile.TemporaryDirectory() as vt_temp_folder:
|
||||
print(f'Virtools Engine Temp: {vt_temp_folder}')
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt(
|
||||
'Virtools Engine Temporary Directory: {0}', 'BBP_OT_import_virtools/execute')
|
||||
print(tr_text.format(vt_temp_folder))
|
||||
|
||||
# create virtools reader context
|
||||
with bmap.BMFileReader(
|
||||
@ -61,6 +68,8 @@ def _import_virtools(file_name_: str, encodings_: tuple[str], resolver: UTIL_iop
|
||||
# import 3dobjects
|
||||
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object] = _import_virtools_3dobjects(
|
||||
reader, progress, resolver, mesh_cret_map)
|
||||
# import light
|
||||
_import_virtools_lights(reader, progress, resolver)
|
||||
# import groups
|
||||
_import_virtools_groups(reader, progress, obj3d_cret_map)
|
||||
|
||||
@ -71,11 +80,15 @@ def _import_virtools_textures(
|
||||
) -> dict[bmap.BMTexture, bpy.types.Image]:
|
||||
# create map
|
||||
texture_cret_map: dict[bmap.BMTexture, bpy.types.Image] = {}
|
||||
progress.enter_substeps(reader.get_texture_count(), "Loading Textures")
|
||||
# notify steps
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Loading Textures', 'BBP_OT_import_virtools/execute')
|
||||
progress.enter_substeps(reader.get_texture_count(), tr_text)
|
||||
|
||||
# create another temp folder for raw data virtools texture importing
|
||||
with tempfile.TemporaryDirectory() as rawdata_temp:
|
||||
print(f'Texture Raw Data Temp: {rawdata_temp}')
|
||||
tr_text = bpy.app.translations.pgettext_rpt(
|
||||
'Texture Raw Data Temporary Directory: {0}', 'BBP_OT_import_virtools/execute')
|
||||
print(tr_text.format(rawdata_temp))
|
||||
|
||||
for vttexture in reader.get_textures():
|
||||
tex_cret: typing.Callable[[], bpy.types.Image]
|
||||
@ -137,7 +150,9 @@ def _import_virtools_materials(
|
||||
) -> dict[bmap.BMMaterial, bpy.types.Material]:
|
||||
# create map and prepare progress
|
||||
material_cret_map: dict[bmap.BMMaterial, bpy.types.Material] = {}
|
||||
progress.enter_substeps(reader.get_material_count(), "Loading Materials")
|
||||
# notify steps
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Loading Materials', 'BBP_OT_import_virtools/execute')
|
||||
progress.enter_substeps(reader.get_material_count(), tr_text)
|
||||
|
||||
for vtmaterial in reader.get_materials():
|
||||
# create mtl
|
||||
@ -202,7 +217,9 @@ def _import_virtools_meshes(
|
||||
) -> dict[bmap.BMMesh, bpy.types.Mesh]:
|
||||
# create map and prepare progress
|
||||
mesh_cret_map: dict[bmap.BMMesh, bpy.types.Mesh] = {}
|
||||
progress.enter_substeps(reader.get_material_count(), "Loading Meshes")
|
||||
# notify steps
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Loading Meshes', 'BBP_OT_import_virtools/execute')
|
||||
progress.enter_substeps(reader.get_mesh_count(), tr_text)
|
||||
|
||||
for vtmesh in reader.get_meshs():
|
||||
# create mesh
|
||||
@ -298,11 +315,9 @@ def _import_virtools_3dobjects(
|
||||
) -> dict[bmap.BM3dObject, bpy.types.Object]:
|
||||
# create map and prepare progress
|
||||
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object] = {}
|
||||
progress.enter_substeps(reader.get_material_count(), "Loading 3dObjects")
|
||||
|
||||
# get some essential blender data
|
||||
blender_view_layer = bpy.context.view_layer
|
||||
blender_collection = blender_view_layer.active_layer_collection.collection
|
||||
# notify steps
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Loading 3dObjects', 'BBP_OT_import_virtools/execute')
|
||||
progress.enter_substeps(reader.get_3dobject_count(), tr_text)
|
||||
|
||||
for vt3dobj in reader.get_3dobjects():
|
||||
# get virtools binding mesh data first
|
||||
@ -316,8 +331,8 @@ def _import_virtools_3dobjects(
|
||||
|
||||
# setup if necessary
|
||||
if init_obj3d:
|
||||
# link to collection
|
||||
blender_collection.objects.link(obj3d)
|
||||
# add into scene
|
||||
UTIL_functions.add_into_scene(obj3d)
|
||||
|
||||
# set world matrix
|
||||
vtmat: UTIL_virtools_types.VxMatrix = vt3dobj.get_world_matrix()
|
||||
@ -340,26 +355,91 @@ def _import_virtools_3dobjects(
|
||||
progress.leave_substeps()
|
||||
return obj3d_cret_map
|
||||
|
||||
def _import_virtools_lights(
|
||||
reader: bmap.BMFileReader,
|
||||
progress: ProgressReport,
|
||||
resolver: UTIL_ioport_shared.ConflictResolver
|
||||
) -> None:
|
||||
# prepare progress
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Loading Lights', 'BBP_OT_import_virtools/execute')
|
||||
progress.enter_substeps(reader.get_target_light_count(), tr_text)
|
||||
|
||||
# please note light is slightly different between virtools and blender.
|
||||
# in virtools, light is the sub class of 3d entity.
|
||||
# it means that virtools use class inheritance to implement light.
|
||||
# however, in blender, light is the data property of object.
|
||||
# comparing with normal mesh object, it just replace the data property of object to a light.
|
||||
# so in blender, light is implemented as a data struct attached to object.
|
||||
# thus we can reuse light data for multiple objects but virtools can not.
|
||||
# in virtools, every light are individual objects.
|
||||
for vtlight in reader.get_target_lights():
|
||||
# create light data block and 3d object together
|
||||
(light_3dobj, light, init_light) = resolver.create_light(
|
||||
UTIL_virtools_types.virtools_name_regulator(vtlight.get_name())
|
||||
)
|
||||
|
||||
if init_light:
|
||||
# setup light data block
|
||||
rawlight: PROP_virtools_light.RawVirtoolsLight = PROP_virtools_light.RawVirtoolsLight()
|
||||
rawlight.mType = vtlight.get_type()
|
||||
rawlight.mColor = vtlight.get_color()
|
||||
|
||||
rawlight.mConstantAttenuation = vtlight.get_constant_attenuation()
|
||||
rawlight.mLinearAttenuation = vtlight.get_linear_attenuation()
|
||||
rawlight.mQuadraticAttenuation = vtlight.get_quadratic_attenuation()
|
||||
|
||||
rawlight.mRange = vtlight.get_range()
|
||||
|
||||
rawlight.mHotSpot = vtlight.get_hot_spot()
|
||||
rawlight.mFalloff = vtlight.get_falloff()
|
||||
rawlight.mFalloffShape = vtlight.get_falloff_shape()
|
||||
|
||||
PROP_virtools_light.set_raw_virtools_light(light, rawlight)
|
||||
PROP_virtools_light.apply_to_blender_light(light)
|
||||
|
||||
# setup light associated 3d object
|
||||
# add into scene
|
||||
UTIL_functions.add_into_scene(light_3dobj)
|
||||
# set world matrix
|
||||
vtmat: UTIL_virtools_types.VxMatrix = vtlight.get_world_matrix()
|
||||
UTIL_virtools_types.vxmatrix_conv_co(vtmat)
|
||||
bldmat: mathutils.Matrix = UTIL_virtools_types.vxmatrix_to_blender(vtmat)
|
||||
light_3dobj.matrix_world = UTIL_virtools_types.bldmatrix_patch_light_obj(bldmat)
|
||||
# set visibility
|
||||
light_3dobj.hide_set(not vtlight.get_visibility())
|
||||
|
||||
# leave progress
|
||||
progress.leave_substeps()
|
||||
|
||||
def _import_virtools_groups(
|
||||
reader: bmap.BMFileReader,
|
||||
progress: ProgressReport,
|
||||
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object]
|
||||
) -> dict[bmap.BM3dObject, bpy.types.Object]:
|
||||
) -> None:
|
||||
# we need iterate all groups to construct a reversed map
|
||||
# to indicate which groups should this 3dobject be grouped into.
|
||||
reverse_map: dict[bmap.BM3dObject, set[str]] = {}
|
||||
# sector counter to record the maximum sector we have processed.
|
||||
sector_count: int = 1
|
||||
|
||||
# prepare progress
|
||||
progress.enter_substeps(reader.get_material_count(), "Loading Groups")
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Loading Groups', 'BBP_OT_import_virtools/execute')
|
||||
progress.enter_substeps(reader.get_group_count(), tr_text)
|
||||
|
||||
for vtgroup in reader.get_groups():
|
||||
# if this group do not have name, skip it
|
||||
group_name: str | None = vtgroup.get_name()
|
||||
if group_name is None: continue
|
||||
|
||||
# try extracting sector info
|
||||
potential_sector_count: int | None = UTIL_naming_convension.extract_sector_from_name(group_name)
|
||||
if potential_sector_count is not None:
|
||||
sector_count = max(sector_count, potential_sector_count)
|
||||
|
||||
# creating map
|
||||
for item in vtgroup.get_objects():
|
||||
# get or create set
|
||||
objgroups: set[str] = reverse_map.get(item, None)
|
||||
objgroups: set[str] | None = reverse_map.get(item, None)
|
||||
if objgroups is None:
|
||||
objgroups = set()
|
||||
reverse_map[item] = objgroups
|
||||
@ -370,11 +450,17 @@ def _import_virtools_groups(
|
||||
# step
|
||||
progress.step()
|
||||
|
||||
# assign to ballance map info according to gotten sector count
|
||||
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||
map_info.mSectorCount = max(map_info.mSectorCount, sector_count)
|
||||
PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info)
|
||||
|
||||
# leave progress
|
||||
progress.leave_substeps()
|
||||
|
||||
# now we can assign 3dobject group data by reverse map
|
||||
progress.enter_substeps(reader.get_material_count(), "Applying Groups")
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Applying Groups', 'BBP_OT_import_virtools/execute')
|
||||
progress.enter_substeps(len(reverse_map), tr_text)
|
||||
for mapk, mapv in reverse_map.items():
|
||||
# check object
|
||||
assoc_obj = obj3d_cret_map.get(mapk, None)
|
||||
@ -385,6 +471,10 @@ def _import_virtools_groups(
|
||||
gpoper.clear_groups()
|
||||
gpoper.add_groups(mapv)
|
||||
|
||||
# step
|
||||
progress.step()
|
||||
|
||||
# leave progress
|
||||
progress.leave_substeps()
|
||||
|
||||
|
||||
|
42
bbp_ng/OP_MTL_fix_material.py
Normal file
@ -0,0 +1,42 @@
|
||||
import bpy
|
||||
from . import UTIL_functions
|
||||
from . import PROP_virtools_material, PROP_preferences
|
||||
|
||||
class BBP_OT_fix_all_material(bpy.types.Operator):
|
||||
"""Fix All Materials by Its Referred Ballance Texture Name."""
|
||||
bl_idname = "bbp.fix_all_material"
|
||||
bl_label = "Fix All Materials"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_fix_all_material'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
# only enable this when plugin have a valid ballance texture folder
|
||||
# and we are in object mode
|
||||
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder() and UTIL_functions.is_in_object_mode()
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_confirm(self, event)
|
||||
|
||||
def execute(self, context):
|
||||
# do work and count
|
||||
counter_all: int = 0
|
||||
counter_suc: int = 0
|
||||
for mtl in bpy.data.materials:
|
||||
counter_all += 1
|
||||
if PROP_virtools_material.fix_material(mtl):
|
||||
PROP_virtools_material.apply_to_blender_material(mtl)
|
||||
counter_suc += 1
|
||||
|
||||
# report and return
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt(
|
||||
'Fix {0}/{1} materials.', 'BBP_OT_fix_all_material/draw')
|
||||
self.report({'INFO'}, tr_text.format(counter_suc, counter_all))
|
||||
return {'FINISHED'}
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_fix_all_material)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_fix_all_material)
|
@ -43,25 +43,30 @@ class BBP_PG_legacy_align_history(bpy.types.PropertyGroup):
|
||||
align_x: bpy.props.BoolProperty(
|
||||
name = "X Position",
|
||||
default = False,
|
||||
)
|
||||
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||
) # type: ignore
|
||||
align_y: bpy.props.BoolProperty(
|
||||
name = "Y Position",
|
||||
default = False,
|
||||
)
|
||||
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||
) # type: ignore
|
||||
align_z: bpy.props.BoolProperty(
|
||||
name = "Z Position",
|
||||
default = False,
|
||||
)
|
||||
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||
) # type: ignore
|
||||
current_align_mode: bpy.props.EnumProperty(
|
||||
name = "Current Object (Active Object)",
|
||||
items = _g_EnumHelper_AlignMode.generate_items(),
|
||||
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
||||
)
|
||||
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||
) # type: ignore
|
||||
target_align_mode: bpy.props.EnumProperty(
|
||||
name = "Target Objects (Other Objects)",
|
||||
name = "Target Objects (Selected Objects)",
|
||||
items = _g_EnumHelper_AlignMode.generate_items(),
|
||||
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
||||
)
|
||||
translation_context = 'BBP_PG_legacy_align_history/property'
|
||||
) # type: ignore
|
||||
|
||||
#endregion
|
||||
|
||||
@ -70,6 +75,7 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
||||
bl_idname = "bbp.legacy_align"
|
||||
bl_label = "3ds Max Align"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_legacy_align'
|
||||
|
||||
# the updator for apply flag value
|
||||
def apply_flag_updated(self, context):
|
||||
@ -84,11 +90,13 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
||||
|
||||
# check whether add new entry
|
||||
# if no selected axis, this alignment is invalid
|
||||
entry: BBP_PG_legacy_align_history = self.align_history[-1]
|
||||
histories: UTIL_functions.CollectionVisitor[BBP_PG_legacy_align_history]
|
||||
histories = UTIL_functions.CollectionVisitor(self.align_history)
|
||||
entry: BBP_PG_legacy_align_history = histories[-1]
|
||||
if entry.align_x == True or entry.align_y == True or entry.align_z == True:
|
||||
# valid one
|
||||
# add a new entry in history
|
||||
self.align_history.add()
|
||||
histories.add()
|
||||
else:
|
||||
# invalid one
|
||||
# reset all data to default
|
||||
@ -104,41 +112,55 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
||||
return None
|
||||
|
||||
apply_flag: bpy.props.BoolProperty(
|
||||
name = "Apply Flag",
|
||||
description = "Internal flag.",
|
||||
# TR: Property not showen should not have name and desc.
|
||||
# name = "Apply Flag",
|
||||
# description = "Internal flag.",
|
||||
options = {'HIDDEN', 'SKIP_SAVE'},
|
||||
default = True, # default True value to make it as a "light" button, not a grey one.
|
||||
update = apply_flag_updated,
|
||||
)
|
||||
update = apply_flag_updated
|
||||
) # type: ignore
|
||||
recursive_hinder: bpy.props.BoolProperty(
|
||||
name = "Recursive Hinder",
|
||||
description = "An internal flag to prevent the loop calling to apply_flags's updator.",
|
||||
# TR: Property not showen should not have name and desc.
|
||||
# name = "Recursive Hinder",
|
||||
# description = "An internal flag to prevent the loop calling to apply_flags's updator.",
|
||||
options = {'HIDDEN', 'SKIP_SAVE'},
|
||||
default = False,
|
||||
)
|
||||
default = False
|
||||
) # type: ignore
|
||||
align_history : bpy.props.CollectionProperty(
|
||||
name = "Historys",
|
||||
description = "Align history.",
|
||||
type = BBP_PG_legacy_align_history,
|
||||
)
|
||||
# TR: Property not showen should not have name and desc.
|
||||
# name = "Historys",
|
||||
# description = "Align history.",
|
||||
type = BBP_PG_legacy_align_history
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
def poll(cls, context):
|
||||
return _check_align_requirement()
|
||||
|
||||
def invoke(self, context, event):
|
||||
histories: UTIL_functions.CollectionVisitor[BBP_PG_legacy_align_history]
|
||||
histories = UTIL_functions.CollectionVisitor(self.align_history)
|
||||
# clear history and add 1 entry for following functions
|
||||
self.align_history.clear()
|
||||
self.align_history.add()
|
||||
histories.clear()
|
||||
histories.add()
|
||||
# run execute() function
|
||||
return self.execute(context)
|
||||
|
||||
def execute(self, context):
|
||||
# get processed objects
|
||||
(current_obj, target_objs) = _prepare_objects()
|
||||
# YYC MARK:
|
||||
# This statement is VERY IMPORTANT.
|
||||
# If this statement is not presented, Blender will return identity matrix
|
||||
# when getting world matrix from Object since the second execution of this function.
|
||||
# It seems that Blender fail to read restored value from a new execution.
|
||||
# Additionally, this statement only can be placed in there.
|
||||
# If you place it at the end of this function, it doesn't work.
|
||||
context.view_layer.update()
|
||||
# iterate history to align objects
|
||||
entry: BBP_PG_legacy_align_history
|
||||
for entry in self.align_history:
|
||||
histories: UTIL_functions.CollectionVisitor[BBP_PG_legacy_align_history]
|
||||
histories = UTIL_functions.CollectionVisitor(self.align_history)
|
||||
for entry in histories:
|
||||
_align_objects(
|
||||
current_obj, target_objs,
|
||||
entry.align_x, entry.align_y, entry.align_z,
|
||||
@ -149,13 +171,15 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
||||
|
||||
def draw(self, context):
|
||||
# get last entry in history to show
|
||||
entry: BBP_PG_legacy_align_history = self.align_history[-1]
|
||||
histories: UTIL_functions.CollectionVisitor[BBP_PG_legacy_align_history]
|
||||
histories = UTIL_functions.CollectionVisitor(self.align_history)
|
||||
entry: BBP_PG_legacy_align_history = histories[-1]
|
||||
|
||||
layout = self.layout
|
||||
col = layout.column()
|
||||
|
||||
# show axis
|
||||
col.label(text="Align Axis (Multi-selection)")
|
||||
col.label(text="Align Axis (Multi-selection)", text_ctxt='BBP_OT_legacy_align/draw')
|
||||
row = col.row()
|
||||
row.prop(entry, "align_x", toggle = 1)
|
||||
row.prop(entry, "align_y", toggle = 1)
|
||||
@ -163,9 +187,9 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
||||
|
||||
# show mode
|
||||
col.separator()
|
||||
col.label(text = 'Current Object (Active Object)')
|
||||
col.label(text='Current Object (Active Object)', text_ctxt='BBP_OT_legacy_align/draw')
|
||||
col.prop(entry, "current_align_mode", expand = True)
|
||||
col.label(text = 'Target Objects (Selected Objects)')
|
||||
col.label(text='Target Objects (Selected Objects)', text_ctxt='BBP_OT_legacy_align/draw')
|
||||
col.prop(entry, "target_align_mode", expand = True)
|
||||
|
||||
# show apply button
|
||||
@ -174,16 +198,21 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
||||
# only allow Apply when there is a selected axis
|
||||
conditional_disable_area.enabled = entry.align_x == True or entry.align_y == True or entry.align_z == True
|
||||
# show apply and counter
|
||||
conditional_disable_area.prop(self, 'apply_flag', text = 'Apply', icon = 'CHECKMARK', toggle = 1)
|
||||
conditional_disable_area.label(text = f'Total {len(self.align_history) - 1} applied alignments')
|
||||
conditional_disable_area.prop(self, 'apply_flag', toggle = 1, text='Apply', icon='CHECKMARK', text_ctxt='BBP_OT_legacy_align/draw')
|
||||
tr_text: str = bpy.app.translations.pgettext_iface(
|
||||
'Total {0} applied alignments', 'BBP_OT_legacy_align/draw')
|
||||
conditional_disable_area.label(text=tr_text.format(len(histories) - 1), translate=False)
|
||||
|
||||
#region Core Functions
|
||||
|
||||
def _check_align_requirement() -> bool:
|
||||
# if we are not in object mode, do not do legacy align
|
||||
if not UTIL_functions.is_in_object_mode():
|
||||
return False
|
||||
|
||||
# check current obj
|
||||
if bpy.context.active_object is None:
|
||||
return False
|
||||
|
||||
# check target obj with filter of current obj
|
||||
length = len(bpy.context.selected_objects)
|
||||
if bpy.context.active_object in bpy.context.selected_objects:
|
||||
@ -211,63 +240,59 @@ def _align_objects(
|
||||
return
|
||||
|
||||
# calc current object data
|
||||
current_obj_bbox: tuple[mathutils.Vector] = tuple(current_obj.matrix_world @ mathutils.Vector(corner) for corner in current_obj.bound_box)
|
||||
current_obj_ref: mathutils.Vector = _get_object_ref_point(current_obj, current_obj_bbox, current_mode)
|
||||
current_obj_ref: mathutils.Vector = _get_object_ref_point(current_obj, current_mode)
|
||||
|
||||
# process each target obj
|
||||
for target_obj in target_objs:
|
||||
# calc target object data
|
||||
target_obj_bbox: tuple[mathutils.Vector] = tuple(target_obj.matrix_world @ mathutils.Vector(corner) for corner in target_obj.bound_box)
|
||||
target_obj_ref: mathutils.Vector = _get_object_ref_point(target_obj, target_obj_bbox, target_mode)
|
||||
# do align
|
||||
if align_x:
|
||||
target_obj.location.x += current_obj_ref.x - target_obj_ref.x
|
||||
if align_y:
|
||||
target_obj.location.y += current_obj_ref.y - target_obj_ref.y
|
||||
if align_z:
|
||||
target_obj.location.z += current_obj_ref.z - target_obj_ref.z
|
||||
target_obj_ref: mathutils.Vector = _get_object_ref_point(target_obj, target_mode)
|
||||
# build translation transform
|
||||
target_obj_translation: mathutils.Vector = current_obj_ref - target_obj_ref
|
||||
if not align_x: target_obj_translation.x = 0
|
||||
if not align_y: target_obj_translation.y = 0
|
||||
if not align_z: target_obj_translation.z = 0
|
||||
# target_obj.location += target_obj_translation
|
||||
target_obj_translation_matrix: mathutils.Matrix = mathutils.Matrix.Translation(target_obj_translation)
|
||||
# apply translation transform to left side (add into original matrix)
|
||||
target_obj.matrix_world = target_obj_translation_matrix @ target_obj.matrix_world
|
||||
|
||||
def _get_object_ref_point(obj: bpy.types.Object, corners: tuple[mathutils.Vector], mode: AlignMode) -> mathutils.Vector:
|
||||
bpy.context.scene.update_tag
|
||||
|
||||
def _get_object_ref_point(obj: bpy.types.Object, mode: AlignMode) -> mathutils.Vector:
|
||||
ref_pos: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
||||
|
||||
# calc bounding box data
|
||||
corners: tuple[mathutils.Vector] = tuple(obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box)
|
||||
bbox_min_corner: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
||||
bbox_min_corner.x = min((vec.x for vec in corners))
|
||||
bbox_min_corner.y = min((vec.y for vec in corners))
|
||||
bbox_min_corner.z = min((vec.z for vec in corners))
|
||||
bbox_max_corner: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
||||
bbox_max_corner.x = max((vec.x for vec in corners))
|
||||
bbox_max_corner.y = max((vec.y for vec in corners))
|
||||
bbox_max_corner.z = max((vec.z for vec in corners))
|
||||
|
||||
# return value by given align mode
|
||||
match(mode):
|
||||
case AlignMode.Min:
|
||||
ref_pos.x = min((vec.x for vec in corners))
|
||||
ref_pos.y = min((vec.y for vec in corners))
|
||||
ref_pos.z = min((vec.z for vec in corners))
|
||||
ref_pos = bbox_min_corner
|
||||
case AlignMode.Max:
|
||||
ref_pos.x = max((vec.x for vec in corners))
|
||||
ref_pos.y = max((vec.y for vec in corners))
|
||||
ref_pos.z = max((vec.z for vec in corners))
|
||||
ref_pos = bbox_max_corner
|
||||
case AlignMode.BBoxCenter:
|
||||
max_vec_cache: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
||||
min_vec_cache: mathutils.Vector = mathutils.Vector((0, 0, 0))
|
||||
|
||||
min_vec_cache.x = min((vec.x for vec in corners))
|
||||
min_vec_cache.y = min((vec.y for vec in corners))
|
||||
min_vec_cache.z = min((vec.z for vec in corners))
|
||||
max_vec_cache.x = max((vec.x for vec in corners))
|
||||
max_vec_cache.y = max((vec.y for vec in corners))
|
||||
max_vec_cache.z = max((vec.z for vec in corners))
|
||||
|
||||
ref_pos.x = (max_vec_cache.x + min_vec_cache.x) / 2
|
||||
ref_pos.y = (max_vec_cache.y + min_vec_cache.y) / 2
|
||||
ref_pos.z = (max_vec_cache.z + min_vec_cache.z) / 2
|
||||
ref_pos = (bbox_max_corner + bbox_min_corner) / 2
|
||||
case AlignMode.AxisCenter:
|
||||
ref_pos.x = obj.location.x
|
||||
ref_pos.y = obj.location.y
|
||||
ref_pos.z = obj.location.z
|
||||
ref_pos = obj.matrix_world.translation
|
||||
case _:
|
||||
raise UTIL_functions.BBPException('inpossible align mode.')
|
||||
raise UTIL_functions.BBPException('impossible align mode.')
|
||||
|
||||
return ref_pos
|
||||
|
||||
#endregion
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_PG_legacy_align_history)
|
||||
bpy.utils.register_class(BBP_OT_legacy_align)
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_legacy_align)
|
||||
bpy.utils.unregister_class(BBP_PG_legacy_align_history)
|
||||
|
@ -7,6 +7,7 @@ class BBP_OT_regulate_objects_name(bpy.types.Operator):
|
||||
bl_idname = "bbp.regulate_objects_name"
|
||||
bl_label = "Regulate Objects Name"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_regulate_objects_name'
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
@ -24,6 +25,7 @@ class BBP_OT_auto_grouping(bpy.types.Operator):
|
||||
bl_idname = "bbp.auto_grouping"
|
||||
bl_label = "Auto Grouping"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_auto_grouping'
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
@ -41,6 +43,7 @@ class BBP_OT_convert_to_imengyu(bpy.types.Operator):
|
||||
bl_idname = "bbp.convert_to_imengyu"
|
||||
bl_label = "Convert to Imengyu"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_convert_to_imengyu'
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
@ -79,22 +82,26 @@ def _rename_core(
|
||||
reporter.leave_object(obj)
|
||||
|
||||
# report data
|
||||
tr_text_title: str = bpy.app.translations.pgettext_rpt('Rename System Report', 'BBP/OP_OBJECT_naming_convention._rename_core()')
|
||||
tr_text_1: str = bpy.app.translations.pgettext_rpt('View console to get more detail', 'BBP/OP_OBJECT_naming_convention._rename_core()')
|
||||
tr_text_2: str = bpy.app.translations.pgettext_rpt('All: {0}', 'BBP/OP_OBJECT_naming_convention._rename_core()')
|
||||
tr_text_3: str = bpy.app.translations.pgettext_rpt('Failed: {0}', 'BBP/OP_OBJECT_naming_convention._rename_core()')
|
||||
UTIL_functions.message_box(
|
||||
(
|
||||
'View console to get more detail',
|
||||
f'All: {reporter.get_all_objs_count()}',
|
||||
f'Failed: {reporter.get_failed_objs_count()}'
|
||||
tr_text_1,
|
||||
tr_text_2.format(reporter.get_all_objs_count()),
|
||||
tr_text_3.format(reporter.get_failed_objs_count())
|
||||
),
|
||||
'Rename System Report',
|
||||
tr_text_title,
|
||||
UTIL_icons_manager.BlenderPresetIcons.Info.value
|
||||
)
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_regulate_objects_name)
|
||||
bpy.utils.register_class(BBP_OT_auto_grouping)
|
||||
bpy.utils.register_class(BBP_OT_convert_to_imengyu)
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_convert_to_imengyu)
|
||||
bpy.utils.unregister_class(BBP_OT_auto_grouping)
|
||||
bpy.utils.unregister_class(BBP_OT_regulate_objects_name)
|
||||
|
46
bbp_ng/OP_OBJECT_snoop_group_then_to_mesh.py
Normal file
@ -0,0 +1,46 @@
|
||||
import bpy
|
||||
import typing
|
||||
from . import PROP_virtools_group
|
||||
|
||||
class BBP_OT_snoop_group_then_to_mesh(bpy.types.Operator):
|
||||
"""Convert selected objects into mesh objects and try to copy the Virtools Group infos of their associated curve bevel object if they have. """
|
||||
bl_idname = "bbp.snoop_group_then_to_mesh"
|
||||
bl_label = "Snoop Group then to Mesh"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_snoop_group_then_to_mesh'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) != 0
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.selected_objects:
|
||||
# skip all non-curve object
|
||||
if obj.type != 'CURVE': continue
|
||||
|
||||
# fetch curve data block
|
||||
curve: bpy.types.Curve = typing.cast(bpy.types.Curve, obj.data)
|
||||
|
||||
# if bevel mode is not object, skip
|
||||
if curve.bevel_mode != 'OBJECT': continue
|
||||
# if bevel object is None, skip
|
||||
bevel_obj: bpy.types.Object | None = curve.bevel_object
|
||||
if bevel_obj is None: continue
|
||||
|
||||
# copy bevel object group info into current object
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as this_gp:
|
||||
this_gp.clear_groups()
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(bevel_obj) as bevel_gp:
|
||||
this_gp.add_groups(bevel_gp.iterate_groups())
|
||||
|
||||
# convert all selected object to mesh
|
||||
# no matter the success of copying virtools group infos and whether selected object is curve
|
||||
bpy.ops.object.convert(target = 'MESH')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_snoop_group_then_to_mesh)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_snoop_group_then_to_mesh)
|
@ -32,13 +32,19 @@ class BBP_OT_select_object_by_virtools_group(bpy.types.Operator, PROP_virtools_g
|
||||
bl_idname = "bbp.select_object_by_virtools_group"
|
||||
bl_label = "Select by Virtools Group"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_select_object_by_virtools_group'
|
||||
|
||||
selection_mode: bpy.props.EnumProperty(
|
||||
name = "Mode",
|
||||
description = "Selection mode",
|
||||
items = _g_EnumHelper_SelectMode.generate_items(),
|
||||
default = _g_EnumHelper_SelectMode.to_selection(SelectMode.Intersect)
|
||||
)
|
||||
default = _g_EnumHelper_SelectMode.to_selection(SelectMode.Intersect),
|
||||
translation_context = 'BBP_OT_select_object_by_virtools_group/property'
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return UTIL_functions.is_in_object_mode()
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
@ -46,6 +52,7 @@ class BBP_OT_select_object_by_virtools_group(bpy.types.Operator, PROP_virtools_g
|
||||
|
||||
def execute(self, context):
|
||||
_select_object_by_virtools_group(
|
||||
context,
|
||||
self.general_get_group_name(),
|
||||
_g_EnumHelper_SelectMode.get_selection(self.selection_mode)
|
||||
)
|
||||
@ -53,25 +60,25 @@ class BBP_OT_select_object_by_virtools_group(bpy.types.Operator, PROP_virtools_g
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text='Selection Mode')
|
||||
layout.label(text='Selection Mode', text_ctxt='BBP_OT_select_object_by_virtools_group/draw')
|
||||
sublayout = layout.column() # make selection expand vertically, not horizontal.
|
||||
sublayout.prop(self, 'selection_mode', expand = True)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text='Group Parameters')
|
||||
layout.label(text='Group Parameters', text_ctxt='BBP_OT_select_object_by_virtools_group/draw')
|
||||
self.draw_group_name_input(layout)
|
||||
|
||||
def _select_object_by_virtools_group(group_name: str, mode: SelectMode) -> None:
|
||||
def _select_object_by_virtools_group(context: bpy.types.Context, group_name: str, mode: SelectMode) -> None:
|
||||
match(mode):
|
||||
case SelectMode.Set:
|
||||
# iterate all objects and directly set
|
||||
for obj in bpy.context.scene.objects:
|
||||
for obj in context.scene.objects:
|
||||
# check group and decide whether select this obj
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
obj.select_set(gp.contain_group(group_name))
|
||||
case SelectMode.Extend:
|
||||
# also iterate all objects
|
||||
for obj in bpy.context.scene.objects:
|
||||
for obj in context.scene.objects:
|
||||
# but only increase selection, for selected object, skip check
|
||||
if obj.select_get(): continue
|
||||
# if not selected, check whether add it.
|
||||
@ -82,7 +89,7 @@ def _select_object_by_virtools_group(group_name: str, mode: SelectMode) -> None:
|
||||
# subtract only involving selected item. so we get selected objest first
|
||||
# and copy it (because we need modify it)
|
||||
# and iterate it to reduce useless operations
|
||||
selected = bpy.context.selected_objects[:]
|
||||
selected = context.selected_objects[:]
|
||||
for obj in selected:
|
||||
# remove matched only
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
@ -90,16 +97,16 @@ def _select_object_by_virtools_group(group_name: str, mode: SelectMode) -> None:
|
||||
obj.select_set(False)
|
||||
case SelectMode.Difference:
|
||||
# construct a selected obj set for convenient operations
|
||||
selected_set = set(bpy.context.selected_objects)
|
||||
selected_set = set(context.selected_objects)
|
||||
# iterate all objects
|
||||
for obj in bpy.context.scene.objects:
|
||||
for obj in context.scene.objects:
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
# use xor to select
|
||||
# in_selected XOR in_group
|
||||
obj.select_set((obj in selected_set) ^ gp.contain_group(group_name))
|
||||
case SelectMode.Intersect:
|
||||
# like subtract, only iterate selected obj
|
||||
selected = bpy.context.selected_objects[:]
|
||||
selected = context.selected_objects[:]
|
||||
for obj in selected:
|
||||
# but remove not matched
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
@ -117,10 +124,11 @@ class BBP_OT_add_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.
|
||||
bl_idname = "bbp.add_objects_virtools_group"
|
||||
bl_label = "Grouping Objects"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_objects_virtools_group'
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return len(bpy.context.selected_objects) != 0
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) != 0
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
@ -128,9 +136,10 @@ class BBP_OT_add_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.
|
||||
|
||||
def execute(self, context):
|
||||
group_name: str = self.general_get_group_name()
|
||||
for obj in bpy.context.selected_objects:
|
||||
for obj in context.selected_objects:
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
gp.add_group(group_name)
|
||||
self.report({'INFO'}, "Grouping objects successfully.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
@ -141,10 +150,11 @@ class BBP_OT_rm_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.S
|
||||
bl_idname = "bbp.rm_objects_virtools_group"
|
||||
bl_label = "Ungrouping Objects"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_rm_objects_virtools_group'
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return len(bpy.context.selected_objects) != 0
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) != 0
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
@ -152,9 +162,10 @@ class BBP_OT_rm_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.S
|
||||
|
||||
def execute(self, context):
|
||||
group_name: str = self.general_get_group_name()
|
||||
for obj in bpy.context.selected_objects:
|
||||
for obj in context.selected_objects:
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
gp.remove_group(group_name)
|
||||
self.report({'INFO'}, "Ungrouping objects successfully.")
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
@ -165,10 +176,11 @@ class BBP_OT_clear_objects_virtools_group(bpy.types.Operator):
|
||||
bl_idname = "bbp.clear_objects_virtools_group"
|
||||
bl_label = "Clear All Groups"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_clear_objects_virtools_group'
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return len(bpy.context.selected_objects) != 0
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects) != 0
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
@ -176,21 +188,22 @@ class BBP_OT_clear_objects_virtools_group(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
# iterate object
|
||||
for obj in bpy.context.selected_objects:
|
||||
for obj in context.selected_objects:
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
gp.clear_groups()
|
||||
self.report({'INFO'}, "Clear objects groups successfully.")
|
||||
return {'FINISHED'}
|
||||
|
||||
#endregion
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_OT_select_object_by_virtools_group)
|
||||
|
||||
bpy.utils.register_class(BBP_OT_add_objects_virtools_group)
|
||||
bpy.utils.register_class(BBP_OT_rm_objects_virtools_group)
|
||||
bpy.utils.register_class(BBP_OT_clear_objects_virtools_group)
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
bpy.utils.unregister_class(BBP_OT_clear_objects_virtools_group)
|
||||
bpy.utils.unregister_class(BBP_OT_rm_objects_virtools_group)
|
||||
bpy.utils.unregister_class(BBP_OT_add_objects_virtools_group)
|
||||
|
@ -16,6 +16,23 @@ class FlattenMethod(enum.IntEnum):
|
||||
# Not only V axis, but also U axis' continuity will been make sure.
|
||||
Wood = enum.auto()
|
||||
|
||||
class NeighborType(enum.IntEnum):
|
||||
"""
|
||||
NeighborType is used by special flatten uv to describe the direction of neighbor.
|
||||
|
||||
Normally we find neighbor by +V, +U direction (in UV world), these neighbors are "forward" neighbors and marked as Forward.
|
||||
But if we try finding neighbor by -V, -U direction, we call these neighbors are "backward" neighbors,
|
||||
and marked as VerticalBackward or HorizontalBackward by its direction.
|
||||
|
||||
The UV of Backward neighbor need to be processed specially so we need distinguish them with Forward neighbors.
|
||||
"""
|
||||
# +V, +U direction neighbor.
|
||||
Forward = enum.auto()
|
||||
# -V direction neighbor.
|
||||
VerticalBackward = enum.auto()
|
||||
# -U direction neighbor.
|
||||
HorizontalBackward = enum.auto()
|
||||
|
||||
class FlattenParam():
|
||||
mReferenceEdge: int
|
||||
mUseRefPoint: bool
|
||||
@ -61,6 +78,7 @@ class BBP_OT_flatten_uv(bpy.types.Operator):
|
||||
bl_idname = "bbp.flatten_uv"
|
||||
bl_label = "Flatten UV"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_flatten_uv'
|
||||
|
||||
reference_edge: bpy.props.IntProperty(
|
||||
name = "Reference Edge",
|
||||
@ -68,6 +86,7 @@ class BBP_OT_flatten_uv(bpy.types.Operator):
|
||||
min = 0,
|
||||
soft_min = 0, soft_max = 3,
|
||||
default = 0,
|
||||
translation_context = 'BBP_OT_flatten_uv/property'
|
||||
) # type: ignore
|
||||
|
||||
flatten_method: bpy.props.EnumProperty(
|
||||
@ -77,7 +96,8 @@ class BBP_OT_flatten_uv(bpy.types.Operator):
|
||||
('FLOOR', "Floor", "Floor specific flatten UV."),
|
||||
('WOOD', "Wood", "Wood specific flatten UV."),
|
||||
],
|
||||
default = 'RAW'
|
||||
default = 'RAW',
|
||||
translation_context = 'BBP_OT_flatten_uv/property'
|
||||
) # type: ignore
|
||||
|
||||
scale_mode: bpy.props.EnumProperty(
|
||||
@ -86,7 +106,8 @@ class BBP_OT_flatten_uv(bpy.types.Operator):
|
||||
('NUM', "Scale Size", "Scale UV with specific number."),
|
||||
('REF', "Ref. Point", "Scale UV with Reference Point feature."),
|
||||
],
|
||||
default = 'NUM'
|
||||
default = 'NUM',
|
||||
translation_context = 'BBP_OT_flatten_uv/property'
|
||||
) # type: ignore
|
||||
|
||||
scale_number: bpy.props.FloatProperty(
|
||||
@ -97,6 +118,7 @@ class BBP_OT_flatten_uv(bpy.types.Operator):
|
||||
default = 5.0,
|
||||
step = 10,
|
||||
precision = 1,
|
||||
translation_context = 'BBP_OT_flatten_uv/property'
|
||||
) # type: ignore
|
||||
|
||||
reference_point: bpy.props.IntProperty(
|
||||
@ -105,6 +127,7 @@ class BBP_OT_flatten_uv(bpy.types.Operator):
|
||||
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,
|
||||
translation_context = 'BBP_OT_flatten_uv/property'
|
||||
) # type: ignore
|
||||
|
||||
reference_uv: bpy.props.FloatProperty(
|
||||
@ -114,11 +137,12 @@ class BBP_OT_flatten_uv(bpy.types.Operator):
|
||||
default = 0.5,
|
||||
step = 10,
|
||||
precision = 2,
|
||||
translation_context = 'BBP_OT_flatten_uv/property'
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = bpy.context.active_object
|
||||
obj = context.active_object
|
||||
if obj is None:
|
||||
return False
|
||||
if obj.type != 'MESH':
|
||||
@ -145,26 +169,28 @@ class BBP_OT_flatten_uv(bpy.types.Operator):
|
||||
return {'CANCELLED'}
|
||||
|
||||
# do flatten uv and report
|
||||
failed: int = _flatten_uv_wrapper(bpy.context.active_object.data, flatten_param_)
|
||||
failed: int = _flatten_uv_wrapper(context.active_object.data, flatten_param_)
|
||||
if failed != 0:
|
||||
print(f'[Flatten UV] {failed} faces are not be processed correctly because process failed.')
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt(
|
||||
'[Flatten UV] {0} faces are not be processed correctly because process failed.', 'BBP_OT_flatten_uv/execute')
|
||||
print(tr_text.format(failed))
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.emboss = 'NORMAL'
|
||||
layout.label(text = "Flatten Method")
|
||||
layout.label(text="Flatten Method", text_ctxt='BBP_OT_flatten_uv/draw')
|
||||
sublayout = layout.row()
|
||||
sublayout.prop(self, "flatten_method", expand = True)
|
||||
layout.prop(self, "reference_edge")
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Scale Mode")
|
||||
layout.label(text="Scale Mode", text_ctxt='BBP_OT_flatten_uv/draw')
|
||||
sublayout = layout.row()
|
||||
sublayout.prop(self, "scale_mode", expand = True)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Scale Config")
|
||||
layout.label(text="Scale Configuration", text_ctxt='BBP_OT_flatten_uv/draw')
|
||||
if self.scale_mode == 'NUM':
|
||||
layout.prop(self, "scale_number")
|
||||
else:
|
||||
@ -283,9 +309,26 @@ def _specific_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerIte
|
||||
|
||||
# prepare a function to check whether face is valid
|
||||
def face_validator(f: bmesh.types.BMFace) -> bool:
|
||||
# specify using external failed counter
|
||||
nonlocal failed
|
||||
# a valid face must be
|
||||
# selected, not processed, and should be rectangle
|
||||
return f.select and (not f.tag) and (len(f.loops) == 4)
|
||||
# we check selection first
|
||||
# then check tag. if tag == True, it mean this face has been processed.
|
||||
if not f.select or f.tag: return False
|
||||
# now this face can be processed, we need check whether it is rectangle
|
||||
if len(f.loops) == 4:
|
||||
# yes it is rectangle
|
||||
return True
|
||||
else:
|
||||
# no, it is not rectangle
|
||||
# we need mark its tag as True to prevent any possible recursive checking
|
||||
# because it definately can not be processed in future.
|
||||
f.tag = True
|
||||
# then we report this face failed
|
||||
failed = failed + 1
|
||||
# return false
|
||||
return False
|
||||
# prepare face getter which will be used when stack is empty
|
||||
face_getter: typing.Iterator[bmesh.types.BMFace] = filter(
|
||||
lambda f: face_validator(f),
|
||||
@ -321,7 +364,7 @@ def _specific_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerIte
|
||||
return neighbor_f
|
||||
# prepare face stack.
|
||||
# NOTE: all face inserted into this stack should be marked as processed first.
|
||||
face_stack: collections.deque[tuple[bmesh.types.BMFace, mathutils.Vector]] = collections.deque()
|
||||
face_stack: collections.deque[tuple[bmesh.types.BMFace, mathutils.Vector, NeighborType]] = collections.deque()
|
||||
# start process faces
|
||||
while True:
|
||||
# if no item in face stack, pick one from face getter and mark it as processed
|
||||
@ -330,14 +373,13 @@ def _specific_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerIte
|
||||
try:
|
||||
f = next(face_getter)
|
||||
f.tag = True
|
||||
face_stack.append((f, mathutils.Vector((0, 0))))
|
||||
face_stack.append((f, mathutils.Vector((0, 0)), NeighborType.Forward))
|
||||
except StopIteration:
|
||||
break
|
||||
|
||||
# pick one face from stack and process it
|
||||
(face, face_offset) = face_stack.pop()
|
||||
(face, face_offset, face_backward) = face_stack.pop()
|
||||
_flatten_face_uv(face, uv_layer, flatten_param, face_offset)
|
||||
print(face_offset)
|
||||
|
||||
# get 4 point uv because we need use them later
|
||||
# NOTE: 4 uv point following this order
|
||||
@ -363,9 +405,9 @@ def _specific_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerIte
|
||||
uv2 = _get_face_vertex_uv(face, uv_layer, ind2)
|
||||
uv3 = _get_face_vertex_uv(face, uv_layer, ind3)
|
||||
|
||||
# insert horizontal neighbor if we are wood flatten uv
|
||||
# correct rectangle shape when in wood mode
|
||||
if flatten_param.mFlattenMethod == FlattenMethod.Wood:
|
||||
# first, make its uv geometry to rectangle from a trapezium.
|
||||
# make its uv geometry to rectangle from a trapezium.
|
||||
# get the average U factor from its right edge.
|
||||
# and make top + bottom uv edge be parallel with U axis by using left edge V factor.
|
||||
average_u = (uv2[0] + uv3[0]) / 2
|
||||
@ -374,21 +416,66 @@ def _specific_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerIte
|
||||
_set_face_vertex_uv(face, uv_layer, ind2, uv2)
|
||||
_set_face_vertex_uv(face, uv_layer, ind3, uv3)
|
||||
|
||||
# then, try getting its right neighbor
|
||||
# do backward correction
|
||||
# in backward mode, we can not know how many space backward one will occupied,
|
||||
# thus we can not pass it by offset because we don't know the offset,
|
||||
# so we only can patch it after computing its real size.
|
||||
if face_backward != NeighborType.Forward:
|
||||
if face_backward == NeighborType.VerticalBackward:
|
||||
# in vertical backward patch,
|
||||
# minus self height for all uv.
|
||||
self_height: float = uv1[1] - uv0[1]
|
||||
uv0 = (uv0[0], uv0[1] - self_height)
|
||||
uv1 = (uv1[0], uv1[1] - self_height)
|
||||
uv2 = (uv2[0], uv2[1] - self_height)
|
||||
uv3 = (uv3[0], uv3[1] - self_height)
|
||||
if face_backward == NeighborType.HorizontalBackward:
|
||||
# in horizontal backward patch, minus self width for all uv.
|
||||
# because we have process rectangle shape issue before this,
|
||||
# so we can pick uv2 or uv3 to get width directly.
|
||||
self_width: float = uv3[0] - uv0[0]
|
||||
uv0 = (uv0[0] - self_width, uv0[1])
|
||||
uv1 = (uv1[0] - self_width, uv1[1])
|
||||
uv2 = (uv2[0] - self_width, uv2[1])
|
||||
uv3 = (uv3[0] - self_width, uv3[1])
|
||||
# set modified uv to geometry
|
||||
_set_face_vertex_uv(face, uv_layer, ind0, uv0)
|
||||
_set_face_vertex_uv(face, uv_layer, ind1, uv1)
|
||||
_set_face_vertex_uv(face, uv_layer, ind2, uv2)
|
||||
_set_face_vertex_uv(face, uv_layer, ind3, uv3)
|
||||
|
||||
# insert horizontal neighbor only in wood mode.
|
||||
if flatten_param.mFlattenMethod == FlattenMethod.Wood:
|
||||
# insert right neighbor (forward)
|
||||
r_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind2, ind0)
|
||||
if r_face is not None:
|
||||
# mark it as processed
|
||||
r_face.tag = True
|
||||
# insert face with extra horizontal offset.
|
||||
face_stack.append((r_face, mathutils.Vector((uv3[0], uv3[1]))))
|
||||
face_stack.append((r_face, mathutils.Vector((uv3[0], uv3[1])), NeighborType.Forward))
|
||||
# insert left neighbor (backward)
|
||||
# swap the index param of neighbor getter
|
||||
l_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind0, ind2)
|
||||
if l_face is not None:
|
||||
l_face.tag = True
|
||||
# pass origin pos, and order backward correction
|
||||
face_stack.append((l_face, mathutils.Vector((uv0[0], uv0[1])), NeighborType.HorizontalBackward))
|
||||
|
||||
# insert vertical neighbor
|
||||
# insert top neighbor (forward)
|
||||
t_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind1, ind3)
|
||||
if t_face is not None:
|
||||
# mark it as processed
|
||||
t_face.tag = True
|
||||
# insert face with extra vertical offset.
|
||||
face_stack.append((t_face, mathutils.Vector((uv1[0], uv1[1]))))
|
||||
face_stack.append((t_face, mathutils.Vector((uv1[0], uv1[1])), NeighborType.Forward))
|
||||
# insert bottom neighbor (backward)
|
||||
# swap the index param of neighbor getter
|
||||
b_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind3, ind1)
|
||||
if b_face is not None:
|
||||
b_face.tag = True
|
||||
# pass origin pos, and order backward correction
|
||||
face_stack.append((b_face, mathutils.Vector((uv0[0], uv0[1])), NeighborType.VerticalBackward))
|
||||
|
||||
return failed
|
||||
|
||||
@ -420,10 +507,13 @@ def _flatten_face_uv(face: bmesh.types.BMFace, uv_layer: bmesh.types.BMLayerItem
|
||||
vec1.normalize()
|
||||
|
||||
# get z axis
|
||||
new_z_axis: mathutils.Vector = new_y_axis.cross(vec1)
|
||||
new_z_axis: mathutils.Vector = vec1.cross(new_y_axis)
|
||||
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
|
||||
# if z is a zero vector, use face normal instead
|
||||
# please note we need use inverted face normal.
|
||||
if not any(round(v, 7) for v in new_z_axis):
|
||||
new_z_axis = typing.cast(mathutils.Vector, face.normal).normalized()
|
||||
new_z_axis.negate()
|
||||
|
||||
# get x axis
|
||||
new_x_axis: mathutils.Vector = new_y_axis.cross(new_z_axis)
|
||||
|
@ -6,12 +6,13 @@ from . import UTIL_virtools_types, UTIL_icons_manager, UTIL_functions
|
||||
class BBP_OT_rail_uv(bpy.types.Operator):
|
||||
"""Create UV for Rail as Ballance Showen (TT_ReflectionMapping)"""
|
||||
bl_idname = "bbp.rail_uv"
|
||||
bl_label = "Create Rail UV"
|
||||
bl_label = "Rail UV"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_rail_uv'
|
||||
|
||||
@classmethod
|
||||
def poll(self, context):
|
||||
return _check_rail_target()
|
||||
def poll(cls, context):
|
||||
return _check_rail_target(context)
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm: bpy.types.WindowManager = context.window_manager
|
||||
@ -19,27 +20,31 @@ class BBP_OT_rail_uv(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
# check material
|
||||
mtl: bpy.types.Material = PROP_ptrprop_resolver.get_rail_uv_material()
|
||||
ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene)
|
||||
mtl: bpy.types.Material = ptrprops.get_rail_uv_material()
|
||||
if mtl is None:
|
||||
UTIL_functions.message_box(
|
||||
("No specific material", ),
|
||||
"Lost Parameter",
|
||||
UTIL_icons_manager.BlenderPresetIcons.Error.value
|
||||
)
|
||||
self.report({'ERROR'}, "Specified material is empty.")
|
||||
return {'CANCELLED'}
|
||||
|
||||
# apply rail uv
|
||||
_create_rail_uv(_get_rail_target(), mtl)
|
||||
(has_invalid_objs, meshes) = _get_rail_target(context)
|
||||
_create_rail_uv(meshes, mtl)
|
||||
|
||||
# show warning if there is invalid objects
|
||||
if has_invalid_objs:
|
||||
self.report({'WARNING'}, 'Some objects are invalid for this operation. See Console for more details.')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
PROP_ptrprop_resolver.draw_rail_uv_material(layout)
|
||||
ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene)
|
||||
ptrprops.draw_rail_uv_material(layout)
|
||||
|
||||
#region Real Worker Functions
|
||||
|
||||
def _check_rail_target() -> bool:
|
||||
for obj in bpy.context.selected_objects:
|
||||
def _check_rail_target(context: bpy.types.Context) -> bool:
|
||||
for obj in context.selected_objects:
|
||||
if obj.type != 'MESH':
|
||||
continue
|
||||
if obj.mode != 'OBJECT':
|
||||
@ -49,11 +54,11 @@ def _check_rail_target() -> bool:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_rail_target() -> typing.Iterable[bpy.types.Mesh]:
|
||||
def _get_rail_target(context: bpy.types.Context) -> tuple[bool, typing.Iterable[bpy.types.Mesh]]:
|
||||
# collect objects
|
||||
meshes: list[bpy.types.Mesh] = []
|
||||
error_objname: list[str] = []
|
||||
for obj in bpy.context.selected_objects:
|
||||
for obj in context.selected_objects:
|
||||
if obj.type != 'MESH':
|
||||
error_objname.append(obj.name)
|
||||
continue
|
||||
@ -64,28 +69,25 @@ def _get_rail_target() -> typing.Iterable[bpy.types.Mesh]:
|
||||
error_objname.append(obj.name)
|
||||
continue
|
||||
|
||||
meshes.append(obj.data)
|
||||
meshes.append(typing.cast(bpy.types.Mesh, obj.data))
|
||||
|
||||
# display warning window if necessary
|
||||
if len(error_objname) != 0:
|
||||
# show dialog
|
||||
UTIL_functions.message_box(
|
||||
("Some objects is not processed, see Console for more infos.", ),
|
||||
"Object Type Warning",
|
||||
UTIL_icons_manager.BlenderPresetIcons.Warning.value
|
||||
)
|
||||
|
||||
has_invalid_objs = len(error_objname) != 0
|
||||
if has_invalid_objs:
|
||||
# output to console
|
||||
print('')
|
||||
print('=====')
|
||||
print('Following objects are not processed by Rail UV because they do not meet the requirements of Rail UV.')
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt('Rail UV Report', 'BBP_OT_rail_uv/execute')
|
||||
print(f'========== {tr_text} ==========')
|
||||
print(bpy.app.translations.pgettext_rpt(
|
||||
'Following objects are not processed by Rail UV because they do not meet the requirements of Rail UV.',
|
||||
'BBP_OT_rail_uv/execute'
|
||||
))
|
||||
for objname in error_objname:
|
||||
print(objname)
|
||||
print('=====')
|
||||
print('')
|
||||
|
||||
# return valid
|
||||
return meshes
|
||||
return (has_invalid_objs, meshes)
|
||||
|
||||
def _tt_reflection_mapping_compute(
|
||||
point_: UTIL_virtools_types.ConstVxVector3,
|
||||
|
@ -96,16 +96,18 @@ def is_ballance_element(name: str) -> bool:
|
||||
class BBP_PG_ballance_element(bpy.types.PropertyGroup):
|
||||
element_id: bpy.props.IntProperty(
|
||||
name = "Element Id",
|
||||
default = 0
|
||||
)
|
||||
default = 0,
|
||||
translation_context = 'BBP_PG_ballance_element/property'
|
||||
) # type: ignore
|
||||
|
||||
mesh_ptr: bpy.props.PointerProperty(
|
||||
name = "Mesh",
|
||||
type = bpy.types.Mesh
|
||||
)
|
||||
type = bpy.types.Mesh,
|
||||
translation_context = 'BBP_PG_ballance_element/property'
|
||||
) # type: ignore
|
||||
|
||||
def get_ballance_elements(scene: bpy.types.Scene) -> bpy.types.CollectionProperty:
|
||||
return scene.ballance_elements
|
||||
def get_ballance_elements(scene: bpy.types.Scene) -> UTIL_functions.CollectionVisitor[BBP_PG_ballance_element]:
|
||||
return UTIL_functions.CollectionVisitor(scene.ballance_elements)
|
||||
|
||||
#endregion
|
||||
|
||||
@ -224,7 +226,7 @@ class BallanceElementsHelper():
|
||||
This class should only have 1 instance at the same time. This class support `with` syntax to achieve this.
|
||||
This class frequently used in importing stage to create element placeholder.
|
||||
"""
|
||||
__mSingletonMutex: typing.ClassVar[bool] = False
|
||||
__mSingletonMutex: typing.ClassVar[UTIL_functions.TinyMutex[bpy.types.Scene]] = UTIL_functions.TinyMutex()
|
||||
__mIsValid: bool
|
||||
__mAssocScene: bpy.types.Scene
|
||||
__mElementMap: dict[BallanceElementType, bpy.types.Mesh]
|
||||
@ -232,14 +234,11 @@ class BallanceElementsHelper():
|
||||
def __init__(self, assoc: bpy.types.Scene):
|
||||
self.__mElementMap = {}
|
||||
self.__mAssocScene = assoc
|
||||
self.__mIsValid = False
|
||||
|
||||
# check singleton
|
||||
if BallanceElementsHelper.__mSingletonMutex:
|
||||
self.__mIsValid = False
|
||||
raise UTIL_functions.BBPException('BallanceElementsHelper is mutex.')
|
||||
|
||||
BallanceElementsHelper.__mSingletonMutex.lock(self.__mAssocScene)
|
||||
# set validation and read ballance elements property
|
||||
BallanceElementsHelper.__mSingletonMutex = True
|
||||
self.__mIsValid = True
|
||||
self.__read_from_ballance_element()
|
||||
|
||||
@ -257,7 +256,7 @@ class BallanceElementsHelper():
|
||||
# write to ballance elements property and reset validation
|
||||
self.__write_to_ballance_elements()
|
||||
self.__mIsValid = False
|
||||
BallanceElementsHelper.__mSingletonMutex = False
|
||||
BallanceElementsHelper.__mSingletonMutex.unlock(self.__mAssocScene)
|
||||
|
||||
def get_element(self, element_type: BallanceElementType) -> bpy.types.Mesh:
|
||||
if not self.is_valid():
|
||||
@ -276,8 +275,16 @@ class BallanceElementsHelper():
|
||||
self.__mElementMap[element_type] = new_mesh
|
||||
return new_mesh
|
||||
|
||||
def reset_elements(self) -> None:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('calling invalid BallanceElementsHelper')
|
||||
|
||||
# reload all items
|
||||
for elety, elemesh in self.__mElementMap.items():
|
||||
_load_element(elemesh, elety)
|
||||
|
||||
def __write_to_ballance_elements(self) -> None:
|
||||
elements: bpy.types.CollectionProperty = get_ballance_elements(self.__mAssocScene)
|
||||
elements = get_ballance_elements(self.__mAssocScene)
|
||||
elements.clear()
|
||||
|
||||
for elety, elemesh in self.__mElementMap.items():
|
||||
@ -286,10 +293,9 @@ class BallanceElementsHelper():
|
||||
item.mesh_ptr = elemesh
|
||||
|
||||
def __read_from_ballance_element(self) -> None:
|
||||
elements: bpy.types.CollectionProperty = get_ballance_elements(self.__mAssocScene)
|
||||
elements = get_ballance_elements(self.__mAssocScene)
|
||||
self.__mElementMap.clear()
|
||||
|
||||
item: BBP_PG_ballance_element
|
||||
for item in elements:
|
||||
# check requirements
|
||||
if item.mesh_ptr is None: continue
|
||||
@ -299,30 +305,6 @@ class BallanceElementsHelper():
|
||||
# add into map
|
||||
self.__mElementMap[element_type] = item.mesh_ptr
|
||||
|
||||
def reset_ballance_elements(scene: bpy.types.Scene) -> None:
|
||||
invalid_idx: list[int] = []
|
||||
elements: bpy.types.CollectionProperty = get_ballance_elements(scene)
|
||||
|
||||
# re-load all elements
|
||||
index: int = 0
|
||||
item: BBP_PG_ballance_element
|
||||
for item in elements:
|
||||
elety: BallanceElementType | None = get_ballance_element_type_from_id(item.element_id)
|
||||
|
||||
# load or record invalid entry
|
||||
if elety is None or item.mesh_ptr is None:
|
||||
invalid_idx.append(index)
|
||||
else:
|
||||
_load_element(item.mesh_ptr, elety)
|
||||
|
||||
# inc counter
|
||||
index += 1
|
||||
|
||||
# remove invalid one with reversed order
|
||||
invalid_idx.reverse()
|
||||
for idx in invalid_idx:
|
||||
elements.remove(idx)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Ballance Elements Representation
|
||||
@ -342,19 +324,17 @@ class BBP_OT_reset_ballance_elements(bpy.types.Operator):
|
||||
bl_idname = "bbp.reset_ballance_elements"
|
||||
bl_label = "Reset Ballance Elements"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_reset_ballance_elements'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
reset_ballance_elements(context.scene)
|
||||
with BallanceElementsHelper(context.scene) as helper:
|
||||
helper.reset_elements()
|
||||
# show a window to let user know, not silence
|
||||
UTIL_functions.message_box(
|
||||
('Reset OK.', ),
|
||||
"Reset Result",
|
||||
UTIL_icons_manager.BlenderPresetIcons.Info.value
|
||||
)
|
||||
self.report({'INFO'}, 'Reset Ballance elements successfully.')
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_PT_ballance_elements(bpy.types.Panel):
|
||||
@ -364,6 +344,7 @@ class BBP_PT_ballance_elements(bpy.types.Panel):
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "scene"
|
||||
bl_translation_context = 'BBP_PT_ballance_elements'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@ -393,7 +374,7 @@ class BBP_PT_ballance_elements(bpy.types.Panel):
|
||||
|
||||
#endregion
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
# register all classes
|
||||
bpy.utils.register_class(BBP_PG_ballance_element)
|
||||
bpy.utils.register_class(BBP_UL_ballance_elements)
|
||||
@ -404,7 +385,7 @@ def register():
|
||||
bpy.types.Scene.ballance_elements = bpy.props.CollectionProperty(type = BBP_PG_ballance_element)
|
||||
bpy.types.Scene.active_ballance_elements = bpy.props.IntProperty()
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
# del from scene metadata
|
||||
del bpy.types.Scene.active_ballance_elements
|
||||
del bpy.types.Scene.ballance_elements
|
||||
|
83
bbp_ng/PROP_ballance_map_info.py
Normal file
@ -0,0 +1,83 @@
|
||||
import bpy
|
||||
import typing
|
||||
from . import UTIL_functions
|
||||
|
||||
class RawBallanceMapInfo():
|
||||
cSectorCount: typing.ClassVar[int] = 1
|
||||
|
||||
mSectorCount: int
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.mSectorCount = kwargs.get("mSectorCount", RawBallanceMapInfo.cSectorCount)
|
||||
|
||||
def regulate(self):
|
||||
self.mSectorCount = UTIL_functions.clamp_int(self.mSectorCount, 1, 999)
|
||||
|
||||
#region Prop Decl & Getter Setter
|
||||
|
||||
class BBP_PG_ballance_map_info(bpy.types.PropertyGroup):
|
||||
sector_count: bpy.props.IntProperty(
|
||||
name = "Sector",
|
||||
description = "The sector count of this Ballance map which is used in exporting map and may be changed when importing map.",
|
||||
default = 1,
|
||||
max = 999, min = 1,
|
||||
soft_max = 8, soft_min = 1,
|
||||
step = 1,
|
||||
translation_context = 'BBP_PG_ballance_map_info/property'
|
||||
) # type: ignore
|
||||
|
||||
def get_ballance_map_info(scene: bpy.types.Scene) -> BBP_PG_ballance_map_info:
|
||||
return scene.ballance_map_info
|
||||
|
||||
def get_raw_ballance_map_info(scene: bpy.types.Scene) -> RawBallanceMapInfo:
|
||||
props: BBP_PG_ballance_map_info = get_ballance_map_info(scene)
|
||||
rawdata: RawBallanceMapInfo = RawBallanceMapInfo()
|
||||
|
||||
rawdata.mSectorCount = props.sector_count
|
||||
|
||||
rawdata.regulate()
|
||||
return rawdata
|
||||
|
||||
def set_raw_ballance_map_info(scene: bpy.types.Scene, rawdata: RawBallanceMapInfo) -> None:
|
||||
props: BBP_PG_ballance_map_info = get_ballance_map_info(scene)
|
||||
|
||||
props.sector_count = rawdata.mSectorCount
|
||||
|
||||
#endregion
|
||||
|
||||
class BBP_PT_ballance_map_info(bpy.types.Panel):
|
||||
"""Show Ballance Map Infos."""
|
||||
bl_label = "Ballance Map"
|
||||
bl_idname = "BBP_PT_ballance_map_info"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "scene"
|
||||
bl_translation_context = 'BBP_PT_ballance_map_info'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene is not None
|
||||
|
||||
def draw(self, context):
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
target: bpy.types.Scene = context.scene
|
||||
props: BBP_PG_ballance_map_info = get_ballance_map_info(target)
|
||||
|
||||
# show map sector count numberbox
|
||||
layout.prop(props, 'sector_count')
|
||||
|
||||
def register() -> None:
|
||||
# register
|
||||
bpy.utils.register_class(BBP_PG_ballance_map_info)
|
||||
bpy.utils.register_class(BBP_PT_ballance_map_info)
|
||||
|
||||
# add into scene metadata
|
||||
bpy.types.Scene.ballance_map_info = bpy.props.PointerProperty(type = BBP_PG_ballance_map_info)
|
||||
|
||||
def unregister() -> None:
|
||||
# del from scene metadata
|
||||
del bpy.types.Scene.ballance_map_info
|
||||
|
||||
# unregister
|
||||
bpy.utils.unregister_class(BBP_PG_ballance_map_info)
|
||||
bpy.utils.unregister_class(BBP_PT_ballance_map_info)
|
@ -63,6 +63,11 @@ _g_BMEMaterialPresets: dict[str, _BMEMaterialPreset] = {
|
||||
'Ball_Wood.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.TraforWoodStone).mData
|
||||
),
|
||||
|
||||
'Rail': _BMEMaterialPreset(
|
||||
'Rail_Environment.bmp',
|
||||
PROP_virtools_material.get_virtools_material_preset(PROP_virtools_material.MaterialPresetType.Rail).mData
|
||||
),
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -72,16 +77,18 @@ _g_BMEMaterialPresets: dict[str, _BMEMaterialPreset] = {
|
||||
class BBP_PG_bme_material(bpy.types.PropertyGroup):
|
||||
bme_material_name: bpy.props.StringProperty(
|
||||
name = "Name",
|
||||
default = ""
|
||||
)
|
||||
default = "",
|
||||
translation_context = 'BBP_PG_bme_material/property'
|
||||
) # type: ignore
|
||||
|
||||
material_ptr: bpy.props.PointerProperty(
|
||||
name = "Material",
|
||||
type = bpy.types.Material
|
||||
)
|
||||
type = bpy.types.Material,
|
||||
translation_context = 'BBP_PG_bme_material/property'
|
||||
) # type: ignore
|
||||
|
||||
def get_bme_materials(scene: bpy.types.Scene) -> bpy.types.CollectionProperty:
|
||||
return scene.bme_materials
|
||||
def get_bme_materials(scene: bpy.types.Scene) -> UTIL_functions.CollectionVisitor[BBP_PG_bme_material]:
|
||||
return UTIL_functions.CollectionVisitor(scene.bme_materials)
|
||||
|
||||
#endregion
|
||||
|
||||
@ -121,7 +128,7 @@ class BMEMaterialsHelper():
|
||||
This class should only have 1 instance at the same time. This class support `with` syntax to achieve this.
|
||||
This class frequently used in creating BME meshes.
|
||||
"""
|
||||
__mSingletonMutex: typing.ClassVar[bool] = False
|
||||
__mSingletonMutex: typing.ClassVar[UTIL_functions.TinyMutex[bpy.types.Scene]] = UTIL_functions.TinyMutex()
|
||||
__mIsValid: bool
|
||||
__mAssocScene: bpy.types.Scene
|
||||
__mMaterialMap: dict[str, bpy.types.Material]
|
||||
@ -129,14 +136,11 @@ class BMEMaterialsHelper():
|
||||
def __init__(self, assoc: bpy.types.Scene):
|
||||
self.__mMaterialMap = {}
|
||||
self.__mAssocScene = assoc
|
||||
self.__mIsValid = False
|
||||
|
||||
# check singleton
|
||||
if BMEMaterialsHelper.__mSingletonMutex:
|
||||
self.__mIsValid = False
|
||||
raise UTIL_functions.BBPException('BMEMaterialsHelper is mutex.')
|
||||
|
||||
BMEMaterialsHelper.__mSingletonMutex.lock(self.__mAssocScene)
|
||||
# set validation and read ballance elements property
|
||||
BMEMaterialsHelper.__mSingletonMutex = True
|
||||
self.__mIsValid = True
|
||||
self.__read_from_bme_materials()
|
||||
|
||||
@ -154,7 +158,7 @@ class BMEMaterialsHelper():
|
||||
# write to ballance elements property and reset validation
|
||||
self.__write_to_bme_materials()
|
||||
self.__mIsValid = False
|
||||
BMEMaterialsHelper.__mSingletonMutex = False
|
||||
BMEMaterialsHelper.__mSingletonMutex.unlock(self.__mAssocScene)
|
||||
|
||||
def get_material(self, preset_name: str) -> bpy.types.Material:
|
||||
if not self.is_valid():
|
||||
@ -173,8 +177,16 @@ class BMEMaterialsHelper():
|
||||
self.__mMaterialMap[preset_name] = new_mtl
|
||||
return new_mtl
|
||||
|
||||
def reset_materials(self) -> None:
|
||||
if not self.is_valid():
|
||||
raise UTIL_functions.BBPException('calling invalid BMEMaterialsHelper')
|
||||
|
||||
# load all items
|
||||
for preset_name, mtl in self.__mMaterialMap.items():
|
||||
_load_bme_material_preset(mtl, preset_name)
|
||||
|
||||
def __write_to_bme_materials(self) -> None:
|
||||
mtls: bpy.types.CollectionProperty = get_bme_materials(self.__mAssocScene)
|
||||
mtls = get_bme_materials(self.__mAssocScene)
|
||||
mtls.clear()
|
||||
|
||||
for preset_name, mtl in self.__mMaterialMap.items():
|
||||
@ -183,38 +195,15 @@ class BMEMaterialsHelper():
|
||||
item.material_ptr = mtl
|
||||
|
||||
def __read_from_bme_materials(self) -> None:
|
||||
mtls: bpy.types.CollectionProperty = get_bme_materials(self.__mAssocScene)
|
||||
mtls = get_bme_materials(self.__mAssocScene)
|
||||
self.__mMaterialMap.clear()
|
||||
|
||||
item: BBP_PG_bme_material
|
||||
for item in mtls:
|
||||
# check requirements
|
||||
if item.material_ptr is None: continue
|
||||
# add into map
|
||||
self.__mMaterialMap[item.bme_material_name] = item.material_ptr
|
||||
|
||||
def reset_bme_materials(scene: bpy.types.Scene) -> None:
|
||||
invalid_idx: list[int] = []
|
||||
mtls: bpy.types.CollectionProperty = get_bme_materials(scene)
|
||||
|
||||
# re-load all elements
|
||||
index: int = 0
|
||||
item: BBP_PG_bme_material
|
||||
for item in mtls:
|
||||
# load or record invalid entry
|
||||
if item.material_ptr is None:
|
||||
invalid_idx.append(index)
|
||||
else:
|
||||
_load_bme_material_preset(item.material_ptr, item.bme_material_name)
|
||||
|
||||
# inc counter
|
||||
index += 1
|
||||
|
||||
# remove invalid one with reversed order
|
||||
invalid_idx.reverse()
|
||||
for idx in invalid_idx:
|
||||
mtls.remove(idx)
|
||||
|
||||
#endregion
|
||||
|
||||
#region BME Materials Representation
|
||||
@ -232,19 +221,17 @@ class BBP_OT_reset_bme_materials(bpy.types.Operator):
|
||||
bl_idname = "bbp.reset_bme_materials"
|
||||
bl_label = "Reset BME Materials"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_reset_bme_materials'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene is not None
|
||||
|
||||
def execute(self, context):
|
||||
reset_bme_materials(context.scene)
|
||||
with BMEMaterialsHelper(context.scene) as helper:
|
||||
helper.reset_materials()
|
||||
# show a window to let user know, not silence
|
||||
UTIL_functions.message_box(
|
||||
('Reset OK.', ),
|
||||
"Reset Result",
|
||||
UTIL_icons_manager.BlenderPresetIcons.Info.value
|
||||
)
|
||||
self.report({'INFO'}, 'Reset BME materials successfully.')
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_PT_bme_materials(bpy.types.Panel):
|
||||
@ -254,6 +241,7 @@ class BBP_PT_bme_materials(bpy.types.Panel):
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "scene"
|
||||
bl_translation_context = 'BBP_PT_bme_materials'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@ -283,7 +271,7 @@ class BBP_PT_bme_materials(bpy.types.Panel):
|
||||
|
||||
#endregion
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
# register all classes
|
||||
bpy.utils.register_class(BBP_PG_bme_material)
|
||||
bpy.utils.register_class(BBP_UL_bme_materials)
|
||||
@ -294,7 +282,7 @@ def register():
|
||||
bpy.types.Scene.bme_materials = bpy.props.CollectionProperty(type = BBP_PG_bme_material)
|
||||
bpy.types.Scene.active_bme_materials = bpy.props.IntProperty()
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
# del from scene metadata
|
||||
del bpy.types.Scene.active_bme_materials
|
||||
del bpy.types.Scene.bme_materials
|
||||
|
@ -22,24 +22,26 @@ class BBPPreferences(bpy.types.AddonPreferences):
|
||||
ballance_texture_folder: bpy.props.StringProperty(
|
||||
name = "Ballance Texture Folder",
|
||||
description = "The path to folder which will be used by this plugin to get external Ballance texture.",
|
||||
subtype='DIR_PATH',
|
||||
subtype = 'DIR_PATH',
|
||||
default = RawPreferences.cBallanceTextureFolder,
|
||||
)
|
||||
translation_context = 'BBPPreferences/property'
|
||||
) # type: ignore
|
||||
|
||||
no_component_collection: bpy.props.StringProperty(
|
||||
name = "No Component Collection",
|
||||
description = "(Import) The object which stored in this collectiion will not be saved as component. (Export) All forced no component objects will be stored in this collection",
|
||||
description = "When importing, it is the name of collection where objects store will not be saved as component. When exporting, all forced no component objects will be stored in this name represented collection",
|
||||
default = RawPreferences.cNoComponentCollection,
|
||||
)
|
||||
translation_context = 'BBPPreferences/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
|
||||
row = layout.row()
|
||||
col = row.column()
|
||||
col.label(text = "Ballance Texture Folder")
|
||||
col.label(text="Ballance Texture Folder", text_ctxt='BBPPreferences/draw')
|
||||
col.prop(self, "ballance_texture_folder", text = "")
|
||||
col.label(text = "No Component Collection")
|
||||
col.label(text="No Component Collection", text_ctxt='BBPPreferences/draw')
|
||||
col.prop(self, "no_component_collection", text = "")
|
||||
|
||||
def get_preferences() -> BBPPreferences:
|
||||
|
@ -1,51 +1,261 @@
|
||||
import bpy
|
||||
from bpy.types import Context, Event
|
||||
from . import UTIL_functions, UTIL_virtools_types
|
||||
|
||||
## Intent
|
||||
# Operator is not allowed to register Pointer Properties.
|
||||
# The solution is register pointer properties in Scene and reference it when drawing operator window.
|
||||
# This module contains all pointer properties used by other operators.
|
||||
|
||||
#region Blender Type Defines
|
||||
|
||||
class BBP_PG_bmap_encoding(bpy.types.PropertyGroup):
|
||||
encoding: bpy.props.StringProperty(
|
||||
name = "Encoding",
|
||||
description = "The name of BMap used encoding.",
|
||||
default = "",
|
||||
translation_context = 'BBP_PG_bmap_encoding/property'
|
||||
) # type: ignore
|
||||
|
||||
class BBP_UL_bmap_encoding(bpy.types.UIList):
|
||||
def draw_item(self, context, layout: bpy.types.UILayout, data, item: BBP_PG_bmap_encoding, icon, active_data, active_propname):
|
||||
layout.prop(item, 'encoding', emboss = False, text = '', icon = 'FONT_DATA')
|
||||
|
||||
class BBP_PG_ptrprop_resolver(bpy.types.PropertyGroup):
|
||||
rail_uv_material: bpy.props.PointerProperty(
|
||||
name = "Material",
|
||||
description = "The material used for rail",
|
||||
type = bpy.types.Material,
|
||||
)
|
||||
translation_context = 'BBP_PG_ptrprop_resolver/property'
|
||||
) # type: ignore
|
||||
|
||||
export_collection: bpy.props.PointerProperty(
|
||||
type = bpy.types.Collection,
|
||||
name = "Collection",
|
||||
description = "The collection exported. Nested collections allowed."
|
||||
)
|
||||
description = "The collection exported. Nested collections allowed.",
|
||||
type = bpy.types.Collection,
|
||||
translation_context = 'BBP_PG_ptrprop_resolver/property'
|
||||
) # type: ignore
|
||||
|
||||
export_object: bpy.props.PointerProperty(
|
||||
type = bpy.types.Object,
|
||||
name = "Object",
|
||||
description = "The object exported"
|
||||
description = "The object exported",
|
||||
type = bpy.types.Object,
|
||||
translation_context = 'BBP_PG_ptrprop_resolver/property'
|
||||
) # type: ignore
|
||||
|
||||
# TR: Properties not showen should not have name and desc.
|
||||
ioport_encodings: bpy.props.CollectionProperty(type = BBP_PG_bmap_encoding) # type: ignore
|
||||
active_ioport_encodings: bpy.props.IntProperty() # type: ignore
|
||||
|
||||
#endregion
|
||||
|
||||
def get_ptrprop_resolver(scene: bpy.types.Scene) -> BBP_PG_ptrprop_resolver:
|
||||
return scene.bbp_ptrprop_resolver
|
||||
|
||||
def get_ioport_encodings(scene: bpy.types.Scene) -> UTIL_functions.CollectionVisitor[BBP_PG_bmap_encoding]:
|
||||
return UTIL_functions.CollectionVisitor(get_ptrprop_resolver(scene).ioport_encodings)
|
||||
def get_active_ioport_encoding(scene: bpy.types.Scene) -> int:
|
||||
return get_ptrprop_resolver(scene).active_ioport_encodings
|
||||
def set_active_ioport_encoding(scene: bpy.types.Scene, val: int) -> None:
|
||||
get_ptrprop_resolver(scene).active_ioport_encodings = val
|
||||
|
||||
#region Blender Operator Defines for Encodings
|
||||
|
||||
class BBP_OT_add_ioport_encodings(bpy.types.Operator):
|
||||
"""Add item at the tail of encodings list used by BMap for Virtools file read and write."""
|
||||
bl_idname = "bbp.add_ioport_encodings"
|
||||
bl_label = "Add in Encodings List"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_ioport_encodings'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
return True
|
||||
|
||||
def execute(self, context):
|
||||
encodings = get_ioport_encodings(context.scene)
|
||||
encodings.add()
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_OT_rm_ioport_encodings(bpy.types.Operator):
|
||||
"""Remove selected item in encodings list used by BMap for Virtools file read and write."""
|
||||
bl_idname = "bbp.rm_ioport_encodings"
|
||||
bl_label = "Remove from Encodings List"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_rm_ioport_encodings'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
encodings = get_ioport_encodings(context.scene)
|
||||
index = get_active_ioport_encoding(context.scene)
|
||||
return index >= 0 and index < len(encodings)
|
||||
|
||||
def execute(self, context):
|
||||
# delete selected item
|
||||
encodings = get_ioport_encodings(context.scene)
|
||||
index = get_active_ioport_encoding(context.scene)
|
||||
encodings.remove(index)
|
||||
# try to correct selected item
|
||||
if index >= len(encodings): index = len(encodings) - 1
|
||||
if index < 0: index = 0
|
||||
set_active_ioport_encoding(context.scene, index)
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_OT_up_ioport_encodings(bpy.types.Operator):
|
||||
"""Move selected item up in encodings list used by BMap for Virtools file read and write."""
|
||||
bl_idname = "bbp.up_ioport_encodings"
|
||||
bl_label = "Move Up in Encodings List"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_up_ioport_encodings'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
encodings = get_ioport_encodings(context.scene)
|
||||
index = get_active_ioport_encoding(context.scene)
|
||||
return index >= 1 and index < len(encodings)
|
||||
|
||||
def execute(self, context):
|
||||
encodings = get_ioport_encodings(context.scene)
|
||||
index = get_active_ioport_encoding(context.scene)
|
||||
encodings.move(index, index - 1)
|
||||
set_active_ioport_encoding(context.scene, index - 1)
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_OT_down_ioport_encodings(bpy.types.Operator):
|
||||
"""Move selected item down in encodings list used by BMap for Virtools file read and write."""
|
||||
bl_idname = "bbp.down_ioport_encodings"
|
||||
bl_label = "Move Down in Encodings List"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_down_ioport_encodings'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
encodings = get_ioport_encodings(context.scene)
|
||||
index = get_active_ioport_encoding(context.scene)
|
||||
return index >= 0 and index < len(encodings) - 1
|
||||
|
||||
def execute(self, context):
|
||||
encodings = get_ioport_encodings(context.scene)
|
||||
index = get_active_ioport_encoding(context.scene)
|
||||
encodings.move(index, index + 1)
|
||||
set_active_ioport_encoding(context.scene, index + 1)
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_OT_clear_ioport_encodings(bpy.types.Operator):
|
||||
"""Clear the encodings list used by BMap for Virtools file read and write."""
|
||||
bl_idname = "bbp.clear_ioport_encodings"
|
||||
bl_label = "Clear Encodings List"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_clear_ioport_encodings'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context: bpy.types.Context) -> bool:
|
||||
return True
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_confirm(self, event)
|
||||
|
||||
def execute(self, context):
|
||||
encodings = get_ioport_encodings(context.scene)
|
||||
encodings.clear()
|
||||
set_active_ioport_encoding(context.scene, 0)
|
||||
return {'FINISHED'}
|
||||
|
||||
#endregion
|
||||
|
||||
class PropsVisitor():
|
||||
"""
|
||||
When outside code want to fetch or draw properties defined in ptrprop_resolver,
|
||||
they should create the instance of this class with given associated scene instance first.
|
||||
Then use this class provided member function to draw or fetch these properties.
|
||||
The function located in this module should not be called directly!
|
||||
"""
|
||||
|
||||
__mAssocScene: bpy.types.Scene
|
||||
|
||||
def __init__(self, assoc_scene: bpy.types.Scene):
|
||||
self.__mAssocScene = assoc_scene
|
||||
|
||||
def get_rail_uv_material(self) -> bpy.types.Material:
|
||||
return get_ptrprop_resolver(self.__mAssocScene).rail_uv_material
|
||||
def draw_rail_uv_material(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(get_ptrprop_resolver(self.__mAssocScene), 'rail_uv_material')
|
||||
|
||||
def get_export_collection(self) -> bpy.types.Collection:
|
||||
return get_ptrprop_resolver(self.__mAssocScene).export_collection
|
||||
def draw_export_collection(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(get_ptrprop_resolver(self.__mAssocScene), 'export_collection')
|
||||
|
||||
def get_export_object(self) -> bpy.types.Object:
|
||||
return get_ptrprop_resolver(self.__mAssocScene).export_object
|
||||
def draw_export_object(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(get_ptrprop_resolver(self.__mAssocScene), 'export_object')
|
||||
|
||||
def get_ioport_encodings(self) -> tuple[str, ...]:
|
||||
encodings = get_ioport_encodings(self.__mAssocScene)
|
||||
return tuple(i.encoding for i in encodings)
|
||||
def draw_ioport_encodings(self, layout: bpy.types.UILayout) -> None:
|
||||
target = get_ptrprop_resolver(self.__mAssocScene)
|
||||
row = layout.row()
|
||||
|
||||
# draw main list
|
||||
row.template_list(
|
||||
"BBP_UL_bmap_encoding", "",
|
||||
target, "ioport_encodings",
|
||||
target, "active_ioport_encodings",
|
||||
rows = 6, maxrows = 6,
|
||||
sort_reverse = False, sort_lock = True # disable sort feature because the order od this encoding list is crucial
|
||||
)
|
||||
|
||||
def get_ptrprop_resolver() -> BBP_PG_ptrprop_resolver:
|
||||
return bpy.context.scene.bbp_ptrprop_resolver
|
||||
# draw sidebar
|
||||
col = row.column(align=True)
|
||||
col.operator(BBP_OT_add_ioport_encodings.bl_idname, icon='ADD', text='')
|
||||
col.operator(BBP_OT_rm_ioport_encodings.bl_idname, icon='REMOVE', text='')
|
||||
col.separator()
|
||||
col.operator(BBP_OT_up_ioport_encodings.bl_idname, icon='TRIA_UP', text='')
|
||||
col.operator(BBP_OT_down_ioport_encodings.bl_idname, icon='TRIA_DOWN', text='')
|
||||
col.separator()
|
||||
col.operator(BBP_OT_clear_ioport_encodings.bl_idname, icon='TRASH', text='')
|
||||
|
||||
def get_rail_uv_material() -> bpy.types.Material:
|
||||
return get_ptrprop_resolver().rail_uv_material
|
||||
def draw_rail_uv_material(layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(get_ptrprop_resolver(), 'rail_uv_material')
|
||||
@bpy.app.handlers.persistent
|
||||
def _ioport_encodings_initializer(file_path: str):
|
||||
# if we can fetch property, and it is empty after loading file
|
||||
# we fill it with default value
|
||||
encodings = get_ioport_encodings(bpy.context.scene)
|
||||
if len(encodings) == 0:
|
||||
for default_enc in UTIL_virtools_types.g_PyBMapDefaultEncodings:
|
||||
item = encodings.add()
|
||||
item.encoding = default_enc
|
||||
|
||||
def get_export_collection() -> bpy.types.Collection:
|
||||
return get_ptrprop_resolver().export_collection
|
||||
def draw_export_collection(layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(get_ptrprop_resolver(), 'export_collection')
|
||||
|
||||
def get_export_object() -> bpy.types.Object:
|
||||
return get_ptrprop_resolver().export_object
|
||||
def draw_export_object(layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(get_ptrprop_resolver(), 'export_object')
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_PG_bmap_encoding)
|
||||
bpy.utils.register_class(BBP_UL_bmap_encoding)
|
||||
bpy.utils.register_class(BBP_PG_ptrprop_resolver)
|
||||
|
||||
# register ioport encodings default value
|
||||
bpy.app.handlers.load_post.append(_ioport_encodings_initializer)
|
||||
|
||||
bpy.utils.register_class(BBP_OT_add_ioport_encodings)
|
||||
bpy.utils.register_class(BBP_OT_rm_ioport_encodings)
|
||||
bpy.utils.register_class(BBP_OT_up_ioport_encodings)
|
||||
bpy.utils.register_class(BBP_OT_down_ioport_encodings)
|
||||
bpy.utils.register_class(BBP_OT_clear_ioport_encodings)
|
||||
|
||||
bpy.types.Scene.bbp_ptrprop_resolver = bpy.props.PointerProperty(type = BBP_PG_ptrprop_resolver)
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
del bpy.types.Scene.bbp_ptrprop_resolver
|
||||
|
||||
bpy.utils.unregister_class(BBP_OT_clear_ioport_encodings)
|
||||
bpy.utils.unregister_class(BBP_OT_down_ioport_encodings)
|
||||
bpy.utils.unregister_class(BBP_OT_up_ioport_encodings)
|
||||
bpy.utils.unregister_class(BBP_OT_rm_ioport_encodings)
|
||||
bpy.utils.unregister_class(BBP_OT_add_ioport_encodings)
|
||||
|
||||
# unregister ioport encodings default value
|
||||
bpy.app.handlers.load_post.remove(_ioport_encodings_initializer)
|
||||
|
||||
bpy.utils.unregister_class(BBP_PG_ptrprop_resolver)
|
||||
bpy.utils.unregister_class(BBP_UL_bmap_encoding)
|
||||
bpy.utils.unregister_class(BBP_PG_bmap_encoding)
|
||||
|
@ -7,11 +7,12 @@ from . import UTIL_functions, UTIL_icons_manager
|
||||
class BBP_PG_virtools_group(bpy.types.PropertyGroup):
|
||||
group_name: bpy.props.StringProperty(
|
||||
name = "Group Name",
|
||||
default = ""
|
||||
)
|
||||
default = "",
|
||||
translation_context = 'BBP_PG_virtools_group/property'
|
||||
) # type: ignore
|
||||
|
||||
def get_virtools_groups(obj: bpy.types.Object) -> bpy.types.CollectionProperty:
|
||||
return obj.virtools_groups
|
||||
def get_virtools_groups(obj: bpy.types.Object) -> UTIL_functions.CollectionVisitor[BBP_PG_virtools_group]:
|
||||
return UTIL_functions.CollectionVisitor(obj.virtools_groups)
|
||||
|
||||
def get_active_virtools_groups(obj: bpy.types.Object) -> int:
|
||||
return obj.active_virtools_groups
|
||||
@ -26,7 +27,7 @@ class VirtoolsGroupsHelper():
|
||||
All Virtools group operations should be done by this class.
|
||||
Do NOT manipulate object's Virtools group properties directly.
|
||||
"""
|
||||
__mSingletonMutex: typing.ClassVar[bool] = False
|
||||
__mSingletonMutex: typing.ClassVar[UTIL_functions.TinyMutex[bpy.types.Object]] = UTIL_functions.TinyMutex()
|
||||
__mIsValid: bool
|
||||
__mNoChange: bool ##< A bool indicate whether any change happended during lifetime. If no change, skip the writing when exiting.
|
||||
__mAssocObj: bpy.types.Object
|
||||
@ -36,14 +37,11 @@ class VirtoolsGroupsHelper():
|
||||
self.__mGroupsSet = set()
|
||||
self.__mAssocObj = assoc
|
||||
self.__mNoChange = True
|
||||
self.__mIsValid = False
|
||||
|
||||
# check singleton
|
||||
if VirtoolsGroupsHelper.__mSingletonMutex:
|
||||
self.__mIsValid = False
|
||||
raise UTIL_functions.BBPException('VirtoolsGroupsHelper is mutex.')
|
||||
|
||||
VirtoolsGroupsHelper.__mSingletonMutex.lock(self.__mAssocObj)
|
||||
# set validation and read ballance elements property
|
||||
VirtoolsGroupsHelper.__mSingletonMutex = True
|
||||
self.__mIsValid = True
|
||||
self.__read_from_virtools_groups()
|
||||
|
||||
@ -63,7 +61,7 @@ class VirtoolsGroupsHelper():
|
||||
if not self.__mNoChange:
|
||||
self.__write_to_virtools_groups()
|
||||
self.__mIsValid = False
|
||||
VirtoolsGroupsHelper.__mSingletonMutex = False
|
||||
VirtoolsGroupsHelper.__mSingletonMutex.unlock(self.__mAssocObj)
|
||||
|
||||
def __check_valid(self) -> None:
|
||||
if not self.is_valid():
|
||||
@ -127,7 +125,7 @@ class VirtoolsGroupsHelper():
|
||||
return len(self.__mGroupsSet)
|
||||
|
||||
def __write_to_virtools_groups(self) -> None:
|
||||
groups: bpy.types.CollectionProperty = get_virtools_groups(self.__mAssocObj)
|
||||
groups = get_virtools_groups(self.__mAssocObj)
|
||||
sel: int = get_active_virtools_groups(self.__mAssocObj)
|
||||
groups.clear()
|
||||
|
||||
@ -143,10 +141,9 @@ class VirtoolsGroupsHelper():
|
||||
set_active_virtools_groups(self.__mAssocObj, sel)
|
||||
|
||||
def __read_from_virtools_groups(self) -> None:
|
||||
groups: bpy.types.CollectionProperty = get_virtools_groups(self.__mAssocObj)
|
||||
groups = get_virtools_groups(self.__mAssocObj)
|
||||
self.__mGroupsSet.clear()
|
||||
|
||||
item: BBP_PG_virtools_group
|
||||
for item in groups:
|
||||
self.__mGroupsSet.add(item.group_name)
|
||||
|
||||
@ -255,19 +252,22 @@ class SharedGroupNameInputProperties():
|
||||
('DEFINED', "Predefined", "Pre-defined group name."),
|
||||
('CUSTOM', "Custom", "User specified group name."),
|
||||
),
|
||||
)
|
||||
translation_context = 'BBP/PROP_virtools_grourp.SharedGroupNameInputProperties/property'
|
||||
) # type: ignore
|
||||
|
||||
preset_group_name: bpy.props.EnumProperty(
|
||||
name = "Group Name",
|
||||
description = "Pick vanilla Ballance group name.",
|
||||
items = _g_EnumHelper_Group.generate_items(),
|
||||
)
|
||||
translation_context = 'BBP/PROP_virtools_grourp.SharedGroupNameInputProperties/property'
|
||||
) # type: ignore
|
||||
|
||||
custom_group_name: bpy.props.StringProperty(
|
||||
name = "Custom Group Name",
|
||||
description = "Input your custom group name.",
|
||||
default = "",
|
||||
)
|
||||
translation_context = 'BBP/PROP_virtools_grourp.SharedGroupNameInputProperties/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw_group_name_input(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.prop(self, 'group_name_source', expand = True)
|
||||
@ -295,9 +295,10 @@ class BBP_OT_add_virtools_group(bpy.types.Operator, SharedGroupNameInputProperti
|
||||
bl_idname = "bbp.add_virtools_groups"
|
||||
bl_label = "Add to Virtools Groups"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_add_virtools_group'
|
||||
|
||||
@classmethod
|
||||
def poll(self, context: bpy.types.Context):
|
||||
def poll(cls, context: bpy.types.Context):
|
||||
return context.object is not None
|
||||
|
||||
def invoke(self, context, event):
|
||||
@ -306,7 +307,8 @@ class BBP_OT_add_virtools_group(bpy.types.Operator, SharedGroupNameInputProperti
|
||||
|
||||
def execute(self, context):
|
||||
# add group
|
||||
with VirtoolsGroupsHelper(context.object) as hlp:
|
||||
obj = typing.cast(bpy.types.Object, context.object)
|
||||
with VirtoolsGroupsHelper(obj) as hlp:
|
||||
hlp.add_group(self.general_get_group_name())
|
||||
return {'FINISHED'}
|
||||
|
||||
@ -318,13 +320,14 @@ class BBP_OT_rm_virtools_group(bpy.types.Operator):
|
||||
bl_idname = "bbp.rm_virtools_groups"
|
||||
bl_label = "Remove from Virtools Groups"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_rm_virtools_group'
|
||||
|
||||
## This class is slightly unique.
|
||||
# Because we need get user selected group name first.
|
||||
# Then pass it to helper.
|
||||
|
||||
@classmethod
|
||||
def poll(self, context: bpy.types.Context):
|
||||
def poll(cls, context: bpy.types.Context):
|
||||
if context.object is None:
|
||||
return False
|
||||
|
||||
@ -335,8 +338,9 @@ class BBP_OT_rm_virtools_group(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
# get selected group name first
|
||||
obj = context.object
|
||||
item: BBP_PG_virtools_group = get_virtools_groups(obj)[get_active_virtools_groups(obj)]
|
||||
obj = typing.cast(bpy.types.Object, context.object)
|
||||
groups = get_virtools_groups(obj)
|
||||
item = groups[get_active_virtools_groups(obj)]
|
||||
gname: str = item.group_name
|
||||
# then delete it
|
||||
with VirtoolsGroupsHelper(obj) as hlp:
|
||||
@ -349,9 +353,10 @@ class BBP_OT_clear_virtools_groups(bpy.types.Operator):
|
||||
bl_idname = "bbp.clear_virtools_groups"
|
||||
bl_label = "Clear Virtools Groups"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_clear_virtools_groups'
|
||||
|
||||
@classmethod
|
||||
def poll(self, context: bpy.types.Context):
|
||||
def poll(cls, context: bpy.types.Context):
|
||||
return context.object is not None
|
||||
|
||||
def invoke(self, context, event):
|
||||
@ -359,7 +364,8 @@ class BBP_OT_clear_virtools_groups(bpy.types.Operator):
|
||||
return wm.invoke_confirm(self, event)
|
||||
|
||||
def execute(self, context):
|
||||
with VirtoolsGroupsHelper(context.object) as hlp:
|
||||
obj = typing.cast(bpy.types.Object, context.object)
|
||||
with VirtoolsGroupsHelper(obj) as hlp:
|
||||
hlp.clear_groups()
|
||||
return {'FINISHED'}
|
||||
|
||||
@ -370,6 +376,7 @@ class BBP_PT_virtools_groups(bpy.types.Panel):
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "object"
|
||||
bl_translation_context = 'BBP_PT_virtools_groups'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@ -377,8 +384,15 @@ class BBP_PT_virtools_groups(bpy.types.Panel):
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
target = bpy.context.active_object
|
||||
target = typing.cast(bpy.types.Object, context.active_object)
|
||||
|
||||
# notify on non-mesh object
|
||||
if target.type != 'MESH':
|
||||
layout.label(
|
||||
text='Virtools Group is invalid on non-mesh object!', icon='ERROR',
|
||||
text_ctxt='BBP_PT_virtools_groups/draw')
|
||||
|
||||
# draw main body
|
||||
row = layout.row()
|
||||
row.template_list(
|
||||
"BBP_UL_virtools_groups", "",
|
||||
@ -396,7 +410,7 @@ class BBP_PT_virtools_groups(bpy.types.Panel):
|
||||
|
||||
#endregion
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
# register all classes
|
||||
bpy.utils.register_class(BBP_PG_virtools_group)
|
||||
bpy.utils.register_class(BBP_UL_virtools_groups)
|
||||
@ -405,14 +419,14 @@ def register():
|
||||
bpy.utils.register_class(BBP_OT_clear_virtools_groups)
|
||||
bpy.utils.register_class(BBP_PT_virtools_groups)
|
||||
|
||||
# add into scene metadata
|
||||
# add into object metadata
|
||||
bpy.types.Object.virtools_groups = bpy.props.CollectionProperty(type = BBP_PG_virtools_group)
|
||||
bpy.types.Object.active_virtools_groups = bpy.props.IntProperty()
|
||||
|
||||
def unregister():
|
||||
# del from scene metadata
|
||||
del bpy.types.Scene.active_virtools_groups
|
||||
del bpy.types.Scene.virtools_groups
|
||||
def unregister() -> None:
|
||||
# del from object metadata
|
||||
del bpy.types.Object.active_virtools_groups
|
||||
del bpy.types.Object.virtools_groups
|
||||
|
||||
bpy.utils.unregister_class(BBP_PT_virtools_groups)
|
||||
bpy.utils.unregister_class(BBP_OT_clear_virtools_groups)
|
||||
|
334
bbp_ng/PROP_virtools_light.py
Normal file
@ -0,0 +1,334 @@
|
||||
import bpy, mathutils
|
||||
from bpy.types import Context
|
||||
import typing, math
|
||||
from . import UTIL_functions, UTIL_virtools_types
|
||||
|
||||
# Raw Data
|
||||
|
||||
class RawVirtoolsLight():
|
||||
# Class member
|
||||
|
||||
mType: UTIL_virtools_types.VXLIGHT_TYPE
|
||||
mColor: UTIL_virtools_types.VxColor
|
||||
|
||||
mConstantAttenuation: float
|
||||
mLinearAttenuation: float
|
||||
mQuadraticAttenuation: float
|
||||
|
||||
mRange: float
|
||||
|
||||
mHotSpot: float
|
||||
mFalloff: float
|
||||
mFalloffShape: float
|
||||
|
||||
# Class member default value
|
||||
|
||||
cDefaultType: typing.ClassVar[UTIL_virtools_types.VXLIGHT_TYPE] = UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTPOINT
|
||||
cDefaultColor: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(1.0, 1.0, 1.0, 1.0)
|
||||
|
||||
cDefaultConstantAttenuation: typing.ClassVar[float] = 1.0
|
||||
cDefaultLinearAttenuation: typing.ClassVar[float] = 0.0
|
||||
cDefaultQuadraticAttenuation: typing.ClassVar[float] = 0.0
|
||||
|
||||
cDefaultRange: typing.ClassVar[float] = 100.0
|
||||
|
||||
cDefaultHotSpot: typing.ClassVar[float] = math.radians(40)
|
||||
cDefaultFalloff: typing.ClassVar[float] = math.radians(45)
|
||||
cDefaultFalloffShape: typing.ClassVar[float] = 1.0
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
# assign default value for each component
|
||||
self.mType = kwargs.get('mType', RawVirtoolsLight.cDefaultType)
|
||||
self.mColor = kwargs.get('mColor', RawVirtoolsLight.cDefaultColor).clone()
|
||||
|
||||
self.mConstantAttenuation = kwargs.get('mConstantAttenuation', RawVirtoolsLight.cDefaultConstantAttenuation)
|
||||
self.mLinearAttenuation = kwargs.get('mLinearAttenuation', RawVirtoolsLight.cDefaultLinearAttenuation)
|
||||
self.mQuadraticAttenuation = kwargs.get('mQuadraticAttenuation', RawVirtoolsLight.cDefaultQuadraticAttenuation)
|
||||
|
||||
self.mRange = kwargs.get('mRange', RawVirtoolsLight.cDefaultRange)
|
||||
|
||||
self.mHotSpot = kwargs.get('mHotSpot', RawVirtoolsLight.cDefaultHotSpot)
|
||||
self.mFalloff = kwargs.get('mFalloff', RawVirtoolsLight.cDefaultFalloff)
|
||||
self.mFalloffShape = kwargs.get('mFalloffShape', RawVirtoolsLight.cDefaultFalloffShape)
|
||||
|
||||
def regulate(self) -> None:
|
||||
# regulate color and reset its alpha value
|
||||
self.mColor.regulate()
|
||||
self.mColor.a = 1.0
|
||||
# regulate range
|
||||
self.mRange = UTIL_functions.clamp_float(self.mRange, 0.0, 200.0)
|
||||
|
||||
# regulate attenuation
|
||||
self.mConstantAttenuation = UTIL_functions.clamp_float(self.mConstantAttenuation, 0.0, 10.0)
|
||||
self.mLinearAttenuation = UTIL_functions.clamp_float(self.mLinearAttenuation, 0.0, 10.0)
|
||||
self.mQuadraticAttenuation = UTIL_functions.clamp_float(self.mQuadraticAttenuation, 0.0, 10.0)
|
||||
|
||||
# regulate spot cone
|
||||
self.mHotSpot = UTIL_functions.clamp_float(self.mHotSpot, 0.0, math.radians(180))
|
||||
self.mFalloff = UTIL_functions.clamp_float(self.mFalloff, 0.0, math.radians(180))
|
||||
self.mFalloffShape = UTIL_functions.clamp_float(self.mFalloffShape, 0.0, 10.0)
|
||||
# regulate spot cone size order
|
||||
if self.mFalloff < self.mHotSpot:
|
||||
self.mFalloff = self.mHotSpot
|
||||
|
||||
# Blender Property Group
|
||||
|
||||
_g_Helper_VXLIGHT_TYPE: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXLIGHT_TYPE)
|
||||
|
||||
class BBP_PG_virtools_light(bpy.types.PropertyGroup):
|
||||
light_type: bpy.props.EnumProperty(
|
||||
name = "Type",
|
||||
description = "The type of this light",
|
||||
items = _g_Helper_VXLIGHT_TYPE.generate_items(),
|
||||
default = _g_Helper_VXLIGHT_TYPE.to_selection(RawVirtoolsLight.cDefaultType),
|
||||
translation_context = 'BBP_PG_virtools_light/property'
|
||||
) # type: ignore
|
||||
|
||||
light_color: bpy.props.FloatVectorProperty(
|
||||
name = "Color",
|
||||
description = "Defines the red, green and blue components of the light.",
|
||||
subtype = 'COLOR',
|
||||
min = 0.0,
|
||||
max = 1.0,
|
||||
size = 3,
|
||||
default = RawVirtoolsLight.cDefaultColor.to_const_rgb(),
|
||||
translation_context = 'BBP_PG_virtools_light/property'
|
||||
) # type: ignore
|
||||
|
||||
constant_attenuation: bpy.props.FloatProperty(
|
||||
name = "Constant Attenuation",
|
||||
description = "Defines the constant attenuation factor.",
|
||||
min = 0.0,
|
||||
max = 10.0,
|
||||
step = 10,
|
||||
default = RawVirtoolsLight.cDefaultConstantAttenuation,
|
||||
translation_context = 'BBP_PG_virtools_light/property'
|
||||
) # type: ignore
|
||||
|
||||
linear_attenuation: bpy.props.FloatProperty(
|
||||
name = "Linear Attenuation",
|
||||
description = "Defines the linear attenuation factor.",
|
||||
min = 0.0,
|
||||
max = 10.0,
|
||||
step = 10,
|
||||
default = RawVirtoolsLight.cDefaultLinearAttenuation,
|
||||
translation_context = 'BBP_PG_virtools_light/property'
|
||||
) # type: ignore
|
||||
|
||||
quadratic_attenuation: bpy.props.FloatProperty(
|
||||
name = "Quadratic Attenuation",
|
||||
description = "Defines the quadratic attenuation factor.",
|
||||
min = 0.0,
|
||||
max = 10.0,
|
||||
step = 10,
|
||||
default = RawVirtoolsLight.cDefaultQuadraticAttenuation,
|
||||
translation_context = 'BBP_PG_virtools_light/property'
|
||||
) # type: ignore
|
||||
|
||||
light_range: bpy.props.FloatProperty(
|
||||
name = "Range",
|
||||
description = "Defines the radius of the lighting area.",
|
||||
min = 0.0,
|
||||
max = 200.0,
|
||||
step = 100,
|
||||
default = RawVirtoolsLight.cDefaultRange,
|
||||
translation_context = 'BBP_PG_virtools_light/property'
|
||||
) # type: ignore
|
||||
|
||||
hot_spot: bpy.props.FloatProperty(
|
||||
name = "Hot Spot",
|
||||
description = "Sets the value of the hot spot of the light.",
|
||||
min = 0.0,
|
||||
max = math.radians(180),
|
||||
subtype = 'ANGLE',
|
||||
default = RawVirtoolsLight.cDefaultHotSpot,
|
||||
translation_context = 'BBP_PG_virtools_light/property'
|
||||
) # type: ignore
|
||||
|
||||
falloff: bpy.props.FloatProperty(
|
||||
name = "Fall Off",
|
||||
description = "Sets the light fall off rate.",
|
||||
min = 0.0,
|
||||
max = math.radians(180),
|
||||
subtype = 'ANGLE',
|
||||
default = RawVirtoolsLight.cDefaultFalloff,
|
||||
translation_context = 'BBP_PG_virtools_light/property'
|
||||
) # type: ignore
|
||||
|
||||
falloff_shape: bpy.props.FloatProperty(
|
||||
name = "Fall Off Shape",
|
||||
description = "Sets the value of the light fall off shape.",
|
||||
min = 0.0,
|
||||
max = 10.0,
|
||||
step = 10,
|
||||
default = RawVirtoolsLight.cDefaultFalloffShape,
|
||||
translation_context = 'BBP_PG_virtools_light/property'
|
||||
) # type: ignore
|
||||
|
||||
# Getter Setter and Applyer
|
||||
|
||||
def get_virtools_light(lit: bpy.types.Light) -> BBP_PG_virtools_light:
|
||||
return lit.virtools_light
|
||||
|
||||
def get_raw_virtools_light(lit: bpy.types.Light) -> RawVirtoolsLight:
|
||||
props: BBP_PG_virtools_light = get_virtools_light(lit)
|
||||
rawdata: RawVirtoolsLight = RawVirtoolsLight()
|
||||
|
||||
rawdata.mType = _g_Helper_VXLIGHT_TYPE.get_selection(props.light_type)
|
||||
rawdata.mColor.from_const_rgb(props.light_color)
|
||||
|
||||
rawdata.mConstantAttenuation = props.constant_attenuation
|
||||
rawdata.mLinearAttenuation = props.linear_attenuation
|
||||
rawdata.mQuadraticAttenuation = props.quadratic_attenuation
|
||||
|
||||
rawdata.mRange = props.light_range
|
||||
|
||||
rawdata.mHotSpot = props.hot_spot
|
||||
rawdata.mFalloff = props.falloff
|
||||
rawdata.mFalloffShape = props.falloff_shape
|
||||
|
||||
rawdata.regulate()
|
||||
return rawdata
|
||||
|
||||
def set_raw_virtools_light(lit: bpy.types.Light, rawdata: RawVirtoolsLight) -> None:
|
||||
props: BBP_PG_virtools_light = get_virtools_light(lit)
|
||||
|
||||
props.light_type = _g_Helper_VXLIGHT_TYPE.to_selection(rawdata.mType)
|
||||
props.light_color = rawdata.mColor.to_const_rgb()
|
||||
|
||||
props.constant_attenuation = rawdata.mConstantAttenuation
|
||||
props.linear_attenuation = rawdata.mLinearAttenuation
|
||||
props.quadratic_attenuation = rawdata.mQuadraticAttenuation
|
||||
|
||||
props.light_range = rawdata.mRange
|
||||
|
||||
props.hot_spot = rawdata.mHotSpot
|
||||
props.falloff = rawdata.mFalloff
|
||||
props.falloff_shape = rawdata.mFalloffShape
|
||||
|
||||
def apply_to_blender_light(lit: bpy.types.Light) -> None:
|
||||
# get raw data first
|
||||
rawdata: RawVirtoolsLight = get_raw_virtools_light(lit)
|
||||
|
||||
# set light type and color
|
||||
match(rawdata.mType):
|
||||
case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTPOINT:
|
||||
lit.type = 'POINT'
|
||||
case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTSPOT:
|
||||
lit.type = 'SPOT'
|
||||
case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTDIREC:
|
||||
lit.type = 'SUN'
|
||||
lit.color = rawdata.mColor.to_const_rgb()
|
||||
|
||||
# MARK:
|
||||
# After set light type, we must re-fetch light object,
|
||||
# because it seems that the object hold by this variable
|
||||
# is not the object after light type changes.
|
||||
#
|
||||
# If I do not do this, function will throw exception
|
||||
# like `'PointLight' object has no attribute 'spot_size'`.
|
||||
lit = bpy.data.lights[lit.name]
|
||||
match(rawdata.mType):
|
||||
case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTPOINT:
|
||||
point_lit: bpy.types.PointLight = typing.cast(bpy.types.PointLight, lit)
|
||||
point_lit.shadow_soft_size = rawdata.mRange
|
||||
case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTSPOT:
|
||||
spot_lit: bpy.types.SpotLight = typing.cast(bpy.types.SpotLight, lit)
|
||||
spot_lit.shadow_soft_size = rawdata.mRange
|
||||
spot_lit.spot_size = rawdata.mFalloff
|
||||
if rawdata.mFalloff == 0: spot_lit.spot_blend = 0.0
|
||||
else: spot_lit.spot_blend = 1.0 - rawdata.mHotSpot / rawdata.mFalloff
|
||||
case UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTDIREC:
|
||||
pass
|
||||
|
||||
# Operators
|
||||
|
||||
class BBP_OT_apply_virtools_light(bpy.types.Operator):
|
||||
"""Apply Virtools Light to Blender Light."""
|
||||
bl_idname = "bbp.apply_virtools_light"
|
||||
bl_label = "Apply to Blender Light"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_apply_virtools_light'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.light is not None
|
||||
|
||||
def execute(self, context):
|
||||
lit: bpy.types.Light = context.light
|
||||
apply_to_blender_light(lit)
|
||||
return {'FINISHED'}
|
||||
|
||||
# Display Panel
|
||||
|
||||
class BBP_PT_virtools_light(bpy.types.Panel):
|
||||
"""Show Virtools Light Properties"""
|
||||
bl_label = "Virtools Light"
|
||||
bl_idname = "BBP_PT_virtools_light"
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "data" # idk why blender use `data` as the light tab same as mesh.
|
||||
bl_translation_context = 'BBP_PT_virtools_light'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.light is not None
|
||||
|
||||
def draw(self, context):
|
||||
# get layout and target
|
||||
layout = self.layout
|
||||
layout.use_property_split = True
|
||||
lit: bpy.types.Light = context.light
|
||||
props: BBP_PG_virtools_light = get_virtools_light(lit)
|
||||
rawdata: RawVirtoolsLight = get_raw_virtools_light(lit)
|
||||
|
||||
# draw operator
|
||||
layout.operator(
|
||||
BBP_OT_apply_virtools_light.bl_idname, text='Apply', icon='NODETREE',
|
||||
text_ctxt='BBP_PT_virtools_light/draw')
|
||||
|
||||
# draw data
|
||||
layout.separator()
|
||||
layout.label(text='Basics', text_ctxt='BBP_PT_virtools_light/draw')
|
||||
# all lights has type and color property
|
||||
sublayout = layout.row()
|
||||
sublayout.use_property_split = False
|
||||
sublayout.prop(props, 'light_type', expand = True)
|
||||
layout.prop(props, 'light_color')
|
||||
# all light has range property exception directional light
|
||||
if rawdata.mType != UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTDIREC:
|
||||
layout.prop(props, 'light_range')
|
||||
|
||||
# all light has attenuation exception directional light
|
||||
if rawdata.mType != UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTDIREC:
|
||||
layout.separator()
|
||||
layout.label(text='Attenuation', text_ctxt='BBP_PT_virtools_light/draw')
|
||||
layout.prop(props, 'constant_attenuation', text='Constant', text_ctxt='BBP_PT_virtools_light/draw')
|
||||
layout.prop(props, 'linear_attenuation', text='Linear', text_ctxt='BBP_PT_virtools_light/draw')
|
||||
layout.prop(props, 'quadratic_attenuation', text='Quadratic', text_ctxt='BBP_PT_virtools_light/draw')
|
||||
|
||||
# only spot light has spot cone properties.
|
||||
if rawdata.mType == UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTSPOT:
|
||||
layout.separator()
|
||||
layout.label(text='Spot Cone', text_ctxt='BBP_PT_virtools_light/draw')
|
||||
layout.prop(props, 'hot_spot')
|
||||
layout.prop(props, 'falloff')
|
||||
layout.prop(props, 'falloff_shape')
|
||||
|
||||
# Register
|
||||
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_PG_virtools_light)
|
||||
bpy.utils.register_class(BBP_OT_apply_virtools_light)
|
||||
bpy.utils.register_class(BBP_PT_virtools_light)
|
||||
|
||||
# add into light metadata
|
||||
bpy.types.Light.virtools_light = bpy.props.PointerProperty(type = BBP_PG_virtools_light)
|
||||
|
||||
def unregister() -> None:
|
||||
# remove from metadata
|
||||
del bpy.types.Light.virtools_light
|
||||
|
||||
bpy.utils.unregister_class(BBP_PT_virtools_light)
|
||||
bpy.utils.unregister_class(BBP_OT_apply_virtools_light)
|
||||
bpy.utils.unregister_class(BBP_PG_virtools_light)
|
@ -1,5 +1,5 @@
|
||||
import bpy
|
||||
import typing, enum, copy
|
||||
import typing, enum, copy, os
|
||||
from . import UTIL_virtools_types, UTIL_functions, UTIL_ballance_texture, UTIL_file_browser
|
||||
from . import PROP_virtools_texture, PROP_preferences
|
||||
|
||||
@ -94,7 +94,7 @@ class RawVirtoolsMaterial():
|
||||
self.mTextureBorderColor = kwargs.get('mTextureBorderColor', RawVirtoolsMaterial.cDefaultTextureBorderColor).clone()
|
||||
self.mAlphaRef = kwargs.get('mAlphaRef', RawVirtoolsMaterial.cDefaultAlphaRef)
|
||||
|
||||
def regulate(self):
|
||||
def regulate(self) -> None:
|
||||
# regulate colors
|
||||
self.mDiffuse.regulate()
|
||||
self.mAmbient.regulate()
|
||||
@ -132,8 +132,9 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
|
||||
min = 0.0,
|
||||
max = 1.0,
|
||||
size = 3,
|
||||
default = RawVirtoolsMaterial.cDefaultAmbient.to_const_rgb()
|
||||
)
|
||||
default = RawVirtoolsMaterial.cDefaultAmbient.to_const_rgb(),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
diffuse: bpy.props.FloatVectorProperty(
|
||||
name = "Diffuse",
|
||||
@ -142,8 +143,9 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
|
||||
min = 0.0,
|
||||
max = 1.0,
|
||||
size = 4,
|
||||
default = RawVirtoolsMaterial.cDefaultDiffuse.to_const_rgba()
|
||||
)
|
||||
default = RawVirtoolsMaterial.cDefaultDiffuse.to_const_rgba(),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
specular: bpy.props.FloatVectorProperty(
|
||||
name = "Specular",
|
||||
@ -152,8 +154,9 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
|
||||
min = 0.0,
|
||||
max = 1.0,
|
||||
size = 3,
|
||||
default = RawVirtoolsMaterial.cDefaultSpecular.to_const_rgb()
|
||||
)
|
||||
default = RawVirtoolsMaterial.cDefaultSpecular.to_const_rgb(),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
emissive: bpy.props.FloatVectorProperty(
|
||||
name = "Emissive",
|
||||
@ -162,22 +165,25 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
|
||||
min = 0.0,
|
||||
max = 1.0,
|
||||
size = 3,
|
||||
default = RawVirtoolsMaterial.cDefaultEmissive.to_const_rgb()
|
||||
)
|
||||
default = RawVirtoolsMaterial.cDefaultEmissive.to_const_rgb(),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
specular_power: bpy.props.FloatProperty(
|
||||
name = "Power",
|
||||
description = "Specular highlight power",
|
||||
min = 0.0,
|
||||
max = 100.0,
|
||||
default = RawVirtoolsMaterial.cDefaultSpecularPower
|
||||
)
|
||||
default = RawVirtoolsMaterial.cDefaultSpecularPower,
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
texture: bpy.props.PointerProperty(
|
||||
type = bpy.types.Image,
|
||||
name = "Texture",
|
||||
description = "Texture of the material"
|
||||
)
|
||||
description = "Texture of the material",
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
texture_border_color: bpy.props.FloatVectorProperty(
|
||||
name = "Border Color",
|
||||
@ -186,112 +192,129 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
|
||||
min = 0.0,
|
||||
max = 1.0,
|
||||
size = 4,
|
||||
default = RawVirtoolsMaterial.cDefaultTextureBorderColor.to_const_rgba()
|
||||
)
|
||||
default = RawVirtoolsMaterial.cDefaultTextureBorderColor.to_const_rgba(),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
texture_blend_mode: bpy.props.EnumProperty(
|
||||
name = "Texture Blend",
|
||||
description = "Texture blend mode",
|
||||
items = _g_Helper_VXTEXTURE_BLENDMODE.generate_items(),
|
||||
default = _g_Helper_VXTEXTURE_BLENDMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureBlendMode)
|
||||
)
|
||||
default = _g_Helper_VXTEXTURE_BLENDMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureBlendMode),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
texture_min_mode: bpy.props.EnumProperty(
|
||||
name = "Filter Min",
|
||||
description = "Texture filter mode when the texture is minified",
|
||||
items = _g_Helper_VXTEXTURE_FILTERMODE.generate_items(),
|
||||
default = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureMinMode)
|
||||
)
|
||||
default = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureMinMode),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
texture_mag_mode: bpy.props.EnumProperty(
|
||||
name = "Filter Mag",
|
||||
description = "Texture filter mode when the texture is magnified",
|
||||
items = _g_Helper_VXTEXTURE_FILTERMODE.generate_items(),
|
||||
default = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureMagMode)
|
||||
)
|
||||
default = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureMagMode),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
texture_address_mode: bpy.props.EnumProperty(
|
||||
name = "Address Mode",
|
||||
description = "The address mode controls how the texture coordinates outside the range 0..1",
|
||||
items = _g_Helper_VXTEXTURE_ADDRESSMODE.generate_items(),
|
||||
default = _g_Helper_VXTEXTURE_ADDRESSMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureAddressMode)
|
||||
)
|
||||
default = _g_Helper_VXTEXTURE_ADDRESSMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureAddressMode),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
source_blend: bpy.props.EnumProperty(
|
||||
name = "Source Blend",
|
||||
description = "Source blend factor",
|
||||
items = _g_Helper_VXBLEND_MODE.generate_items(),
|
||||
default = _g_Helper_VXBLEND_MODE.to_selection(RawVirtoolsMaterial.cDefaultSourceBlend)
|
||||
)
|
||||
default = _g_Helper_VXBLEND_MODE.to_selection(RawVirtoolsMaterial.cDefaultSourceBlend),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
dest_blend: bpy.props.EnumProperty(
|
||||
name = "Destination Blend",
|
||||
description = "Destination blend factor",
|
||||
items = _g_Helper_VXBLEND_MODE.generate_items(),
|
||||
default = _g_Helper_VXBLEND_MODE.to_selection(RawVirtoolsMaterial.cDefaultDestBlend)
|
||||
)
|
||||
default = _g_Helper_VXBLEND_MODE.to_selection(RawVirtoolsMaterial.cDefaultDestBlend),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
fill_mode: bpy.props.EnumProperty(
|
||||
name = "Fill Mode",
|
||||
description = "Fill mode",
|
||||
items = _g_Helper_VXFILL_MODE.generate_items(),
|
||||
default = _g_Helper_VXFILL_MODE.to_selection(RawVirtoolsMaterial.cDefaultFillMode)
|
||||
)
|
||||
default = _g_Helper_VXFILL_MODE.to_selection(RawVirtoolsMaterial.cDefaultFillMode),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
shade_mode: bpy.props.EnumProperty(
|
||||
name = "Shade Mode",
|
||||
description = "Shade mode",
|
||||
items = _g_Helper_VXSHADE_MODE.generate_items(),
|
||||
default = _g_Helper_VXSHADE_MODE.to_selection(RawVirtoolsMaterial.cDefaultShadeMode)
|
||||
)
|
||||
default = _g_Helper_VXSHADE_MODE.to_selection(RawVirtoolsMaterial.cDefaultShadeMode),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
enable_alpha_test: bpy.props.BoolProperty(
|
||||
name = "Alpha Test",
|
||||
description = "Whether the alpha test is enabled",
|
||||
default = RawVirtoolsMaterial.cDefaultEnableAlphaTest
|
||||
)
|
||||
default = RawVirtoolsMaterial.cDefaultEnableAlphaTest,
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
enable_alpha_blend: bpy.props.BoolProperty(
|
||||
name = "Blend",
|
||||
description = "Whether alpha blending is enabled or not.",
|
||||
default = RawVirtoolsMaterial.cDefaultEnableAlphaBlend
|
||||
)
|
||||
default = RawVirtoolsMaterial.cDefaultEnableAlphaBlend,
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
enable_perspective_correction: bpy.props.BoolProperty(
|
||||
name = "Perspective Correction",
|
||||
description = "Whether texture perspective correction is enabled",
|
||||
default = RawVirtoolsMaterial.cDefaultEnablePerspectiveCorrection
|
||||
)
|
||||
default = RawVirtoolsMaterial.cDefaultEnablePerspectiveCorrection,
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
enable_z_write: bpy.props.BoolProperty(
|
||||
name = "Z-Buffer Write",
|
||||
description = "Whether writing in ZBuffer is enabled.",
|
||||
default = RawVirtoolsMaterial.cDefaultEnableZWrite
|
||||
)
|
||||
default = RawVirtoolsMaterial.cDefaultEnableZWrite,
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
enable_two_sided: bpy.props.BoolProperty(
|
||||
name = "Both Sided",
|
||||
description = "Whether the material is both sided or not",
|
||||
default = RawVirtoolsMaterial.cDefaultEnableTwoSided
|
||||
)
|
||||
default = RawVirtoolsMaterial.cDefaultEnableTwoSided,
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
alpha_ref: bpy.props.IntProperty(
|
||||
name = "Alpha Ref Value",
|
||||
description = "Alpha referential value",
|
||||
min = 0,
|
||||
max = 255,
|
||||
default = RawVirtoolsMaterial.cDefaultAlphaRef
|
||||
)
|
||||
default = RawVirtoolsMaterial.cDefaultAlphaRef,
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
alpha_func: bpy.props.EnumProperty(
|
||||
name = "Alpha Test Function",
|
||||
description = "Alpha comparision function",
|
||||
items = _g_Helper_VXCMPFUNC.generate_items(),
|
||||
default = _g_Helper_VXCMPFUNC.to_selection(RawVirtoolsMaterial.cDefaultAlphaFunc)
|
||||
)
|
||||
default = _g_Helper_VXCMPFUNC.to_selection(RawVirtoolsMaterial.cDefaultAlphaFunc),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
z_func: bpy.props.EnumProperty(
|
||||
name = "Z Compare Function",
|
||||
description = "Z Comparison function",
|
||||
items = _g_Helper_VXCMPFUNC.generate_items(),
|
||||
default = _g_Helper_VXCMPFUNC.to_selection(RawVirtoolsMaterial.cDefaultZFunc)
|
||||
)
|
||||
default = _g_Helper_VXCMPFUNC.to_selection(RawVirtoolsMaterial.cDefaultZFunc),
|
||||
translation_context = 'BBP_PG_virtools_material/property'
|
||||
) # type: ignore
|
||||
|
||||
#region Getter Setter
|
||||
|
||||
@ -397,13 +420,20 @@ def apply_to_blender_material(mtl: bpy.types.Material):
|
||||
# bnode.inputs["Emission"].default_value = rawdata.mEmissive.to_const_rgba()
|
||||
|
||||
mtl.specular_intensity = rawdata.mSpecularPower
|
||||
bnode.inputs["Specular"].default_value = UTIL_functions.clamp_float(
|
||||
bnode.inputs["Specular IOR Level"].default_value = UTIL_functions.clamp_float(
|
||||
rawdata.mSpecularPower, 0.0, 1.0
|
||||
)
|
||||
|
||||
# set some alpha data
|
||||
mtl.use_backface_culling = not rawdata.mEnableTwoSided
|
||||
mtl.blend_method = 'BLEND' if rawdata.mEnableAlphaBlend else 'OPAQUE'
|
||||
if rawdata.mEnableAlphaBlend:
|
||||
# In old format: mtl.blend_method = 'BLEND'
|
||||
mtl.surface_render_method = 'BLENDED'
|
||||
mtl.use_raytrace_refraction = True
|
||||
else:
|
||||
# In old format: mtl.blend_method = 'OPAQUE'
|
||||
mtl.surface_render_method = 'DITHERED'
|
||||
mtl.use_raytrace_refraction = False
|
||||
|
||||
# set texture
|
||||
if rawdata.mTexture is not None:
|
||||
@ -539,6 +569,330 @@ _g_Helper_MtlPreset: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelp
|
||||
|
||||
#endregion
|
||||
|
||||
#region Fix Material
|
||||
|
||||
def fix_material(mtl: bpy.types.Material) -> bool:
|
||||
"""!
|
||||
Fix single Blender material.
|
||||
|
||||
@remark The implementation of this function is copied from BallanceVirtoolsHelper/bvh/features/mapping/bmfile_fix_texture.cpp
|
||||
|
||||
@param mtl[in] The blender material need to be processed.
|
||||
@return True if we do a fix, otherwise return False.
|
||||
"""
|
||||
# prepare return value first
|
||||
ret: bool = False
|
||||
|
||||
# get raw mtl from this blender mtl first
|
||||
rawmtl: RawVirtoolsMaterial = get_raw_virtools_material(mtl)
|
||||
# if no associated texture, return
|
||||
if rawmtl.mTexture is None: return ret
|
||||
|
||||
# get associated texture name
|
||||
# we do not check whether it is ballance texture here, because the texture might be packed.
|
||||
# packed ballance texture is not recognised as a valid ballance texture.
|
||||
filename: str = os.path.basename(UTIL_ballance_texture.get_texture_filepath(rawmtl.mTexture))
|
||||
|
||||
# preset some field for raw mtl
|
||||
# first, we need store its as opaque mode
|
||||
rawmtl.mEnableAlphaTest = False
|
||||
rawmtl.mEnableAlphaBlend = False
|
||||
rawmtl.mEnableTwoSided = False
|
||||
# and z write must be enabled in default
|
||||
rawmtl.mEnableZWrite = True
|
||||
rawmtl.mZFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
|
||||
rawmtl.mAmbient.from_const_rgb
|
||||
|
||||
# route filename
|
||||
match(filename):
|
||||
# case 'atari.avi': pass
|
||||
# case 'atari.bmp': pass
|
||||
# case 'Ball_LightningSphere1.bmp': pass
|
||||
# case 'Ball_LightningSphere2.bmp': pass
|
||||
# case 'Ball_LightningSphere3.bmp': pass
|
||||
case 'Ball_Paper.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mEmissive.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
|
||||
rawmtl.mSpecularPower = 0.0
|
||||
ret = True
|
||||
case 'Ball_Stone.bmp' | 'Ball_Wood.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((60 / 255.0, 60 / 255.0, 60 / 255.0))
|
||||
rawmtl.mSpecularPower = 0.0
|
||||
ret = True
|
||||
case 'Brick.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mEmissive.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
|
||||
rawmtl.mSpecularPower = 0.0
|
||||
ret = True
|
||||
# case 'Button01_deselect.tga': pass
|
||||
# case 'Button01_select.tga': pass
|
||||
# case 'Button01_special.tga': pass
|
||||
case 'Column_beige.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((233 / 255.0, 233 / 255.0, 233 / 255.0))
|
||||
rawmtl.mSpecular.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mEmissive.from_const_rgb((80 / 255.0, 80 / 255.0, 80 / 255.0))
|
||||
rawmtl.mSpecularPower = 0.0
|
||||
ret = True
|
||||
case 'Column_beige_fade.tga':
|
||||
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((233 / 255.0, 233 / 255.0, 233 / 255.0))
|
||||
rawmtl.mSpecular.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mEmissive.from_const_rgb((80 / 255.0, 80 / 255.0, 80 / 255.0))
|
||||
rawmtl.mSpecularPower = 0.0
|
||||
|
||||
rawmtl.mEnableAlphaTest = False
|
||||
rawmtl.mAlphaFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_GREATER
|
||||
rawmtl.mAlphaRef = 1
|
||||
rawmtl.mEnableAlphaBlend = True
|
||||
rawmtl.mSourceBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_SRCALPHA
|
||||
rawmtl.mDestBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_INVSRCALPHA
|
||||
rawmtl.mEnableZWrite = True
|
||||
rawmtl.mZFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
|
||||
ret = True
|
||||
case 'Column_blue.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((209 / 255.0, 209 / 255.0, 209 / 255.0))
|
||||
rawmtl.mSpecular.from_const_rgb((150 / 255.0, 150 / 255.0, 150 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((80 / 255.0, 80 / 255.0, 80 / 255.0))
|
||||
rawmtl.mSpecularPower = 31.0
|
||||
ret = True
|
||||
# case 'Cursor.tga': pass
|
||||
# case 'Dome.bmp': pass
|
||||
# case 'DomeEnvironment.bmp': pass
|
||||
# case 'DomeShadow.tga': pass
|
||||
# case 'ExtraBall.bmp': pass
|
||||
# case 'ExtraParticle.bmp': pass
|
||||
case 'E_Holzbeschlag.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((186 / 255.0, 186 / 255.0, 186 / 255.0))
|
||||
rawmtl.mSpecular.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mEmissive.from_const_rgb((65 / 255.0, 65 / 255.0, 65 / 255.0))
|
||||
rawmtl.mSpecularPower = 0.0
|
||||
ret = True
|
||||
# case 'FloorGlow.bmp': pass
|
||||
case 'Floor_Side.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((122 / 255.0, 122 / 255.0, 122 / 255.0))
|
||||
rawmtl.mSpecular.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mEmissive.from_const_rgb((104 / 255.0, 104 / 255.0, 104 / 255.0))
|
||||
rawmtl.mSpecularPower = 0.0
|
||||
ret = True
|
||||
case 'Floor_Top_Border.bmp' | 'Floor_Top_Borderless.bmp' | 'Floor_Top_Checkpoint.bmp' | 'Floor_Top_Flat.bmp' | 'Floor_Top_Profil.bmp' | 'Floor_Top_ProfilFlat.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((80 / 255.0, 80 / 255.0, 80 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mSpecularPower = 100.0
|
||||
ret = True
|
||||
# case 'Font_1.tga': pass
|
||||
# case 'Gravitylogo_intro.bmp': pass
|
||||
# case 'HardShadow.bmp': pass
|
||||
case 'Laterne_Glas.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecularPower = 0.0
|
||||
ret = True
|
||||
case 'Laterne_Schatten.tga':
|
||||
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecularPower = 0.0
|
||||
|
||||
rawmtl.mEnableAlphaTest = True
|
||||
rawmtl.mAlphaFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_GREATER
|
||||
rawmtl.mAlphaRef = 1
|
||||
rawmtl.mEnableAlphaBlend = True
|
||||
rawmtl.mSourceBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_SRCALPHA
|
||||
rawmtl.mDestBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_INVSRCALPHA
|
||||
rawmtl.mEnableZWrite = True
|
||||
rawmtl.mZFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
|
||||
ret = True
|
||||
case 'Laterne_Verlauf.tga':
|
||||
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mSpecular.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mEmissive.from_const_rgb((59 / 255.0, 59 / 255.0, 59 / 255.0))
|
||||
rawmtl.mSpecularPower = 0.0
|
||||
|
||||
rawmtl.mEnableAlphaTest = True
|
||||
rawmtl.mAlphaFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_GREATER
|
||||
rawmtl.mAlphaRef = 1
|
||||
rawmtl.mEnableAlphaBlend = True
|
||||
rawmtl.mSourceBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_SRCALPHA
|
||||
rawmtl.mDestBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_INVSRCALPHA
|
||||
rawmtl.mEnableZWrite = True
|
||||
rawmtl.mZFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
|
||||
rawmtl.mEnableTwoSided = True
|
||||
ret = True
|
||||
# case 'Logo.bmp': pass
|
||||
case 'Metal_stained.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((65 / 255.0, 65 / 255.0, 65 / 255.0))
|
||||
rawmtl.mSpecularPower = 25.0
|
||||
ret = True
|
||||
# case 'Misc_Ufo.bmp': pass
|
||||
# case 'Misc_UFO_Flash.bmp': pass
|
||||
# case 'Modul03_Floor.bmp': pass
|
||||
# case 'Modul03_Wall.bmp': pass
|
||||
case 'Modul11_13_Wood.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((9 / 255.0, 9 / 255.0, 9 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((70 / 255.0, 70 / 255.0, 70 / 255.0))
|
||||
rawmtl.mSpecularPower = 50.0
|
||||
ret = True
|
||||
case 'Modul11_Wood.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((9 / 255.0, 9 / 255.0, 9 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((50 / 255.0, 50 / 255.0, 50 / 255.0))
|
||||
rawmtl.mSpecularPower = 50.0
|
||||
ret = True
|
||||
case 'Modul15.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((16 / 255.0, 16 / 255.0, 16 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((70 / 255.0, 70 / 255.0, 70 / 255.0))
|
||||
rawmtl.mSpecularPower = 100.0
|
||||
ret = True
|
||||
case 'Modul16.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((85 / 255.0, 85 / 255.0, 85 / 255.0))
|
||||
rawmtl.mSpecularPower = 100.0
|
||||
ret = True
|
||||
case 'Modul18.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mSpecularPower = 25.0
|
||||
ret = True
|
||||
case 'Modul18_Gitter.tga':
|
||||
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mSpecularPower = 25.0
|
||||
|
||||
rawmtl.mEnableAlphaTest = True
|
||||
rawmtl.mAlphaFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_GREATER
|
||||
rawmtl.mAlphaRef = 1
|
||||
rawmtl.mEnableAlphaBlend = True
|
||||
rawmtl.mSourceBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_SRCALPHA
|
||||
rawmtl.mDestBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_INVSRCALPHA
|
||||
rawmtl.mEnableZWrite = True
|
||||
rawmtl.mZFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
|
||||
ret = True
|
||||
# case 'Modul30_d_Seiten.bmp': pass
|
||||
# case 'Particle_Flames.bmp': pass
|
||||
# case 'Particle_Smoke.bmp': pass
|
||||
# case 'PE_Bal_balloons.bmp': pass
|
||||
# case 'PE_Bal_platform.bmp': pass
|
||||
# case 'PE_Ufo_env.bmp': pass
|
||||
# case 'Pfeil.tga': pass
|
||||
# case 'P_Extra_Life_Oil.bmp': pass
|
||||
# case 'P_Extra_Life_Particle.bmp': pass
|
||||
# case 'P_Extra_Life_Shadow.bmp': pass
|
||||
case 'Rail_Environment.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((100 / 255.0, 118 / 255.0, 133 / 255.0))
|
||||
rawmtl.mSpecular.from_const_rgb((210 / 255.0, 210 / 255.0, 210 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((124 / 255.0, 134 / 255.0, 150 / 255.0))
|
||||
rawmtl.mSpecularPower = 10.0
|
||||
ret = True
|
||||
# case 'sandsack.bmp': pass
|
||||
# case 'SkyLayer.bmp': pass
|
||||
# case 'Sky_Vortex.bmp': pass
|
||||
case 'Stick_Bottom.tga':
|
||||
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((100 / 255.0, 118 / 255.0, 133 / 255.0))
|
||||
rawmtl.mSpecular.from_const_rgb((210 / 255.0, 210 / 255.0, 210 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((124 / 255.0, 134 / 255.0, 150 / 255.0))
|
||||
rawmtl.mSpecularPower = 13.0
|
||||
|
||||
rawmtl.mEnableAlphaTest = False
|
||||
rawmtl.mAlphaFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_GREATER
|
||||
rawmtl.mAlphaRef = 1
|
||||
rawmtl.mEnableAlphaBlend = True
|
||||
rawmtl.mSourceBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_SRCALPHA
|
||||
rawmtl.mDestBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_INVSRCALPHA
|
||||
rawmtl.mEnableZWrite = True
|
||||
rawmtl.mZFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
|
||||
ret = True
|
||||
case 'Stick_Stripes.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((106 / 255.0, 106 / 255.0, 106 / 255.0))
|
||||
rawmtl.mSpecularPower = 13.0
|
||||
ret = True
|
||||
# case 'Target.bmp': pass
|
||||
case 'Tower_Roof.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((218 / 255.0, 218 / 255.0, 218 / 255.0))
|
||||
rawmtl.mSpecular.from_const_rgb((64 / 255.0, 64 / 255.0, 64 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((103 / 255.0, 103 / 255.0, 103 / 255.0))
|
||||
rawmtl.mSpecularPower = 100.0
|
||||
ret = True
|
||||
# case 'Trafo_Environment.bmp': pass
|
||||
# case 'Trafo_FlashField.bmp': pass
|
||||
# case 'Trafo_Shadow_Big.tga': pass
|
||||
# case 'Tut_Pfeil01.tga': pass
|
||||
# case 'Tut_Pfeil_Hoch.tga': pass
|
||||
# case 'Wolken_intro.tga': pass
|
||||
case 'Wood_Metal.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((40 / 255.0, 40 / 255.0, 40 / 255.0))
|
||||
rawmtl.mSpecularPower = 0.0
|
||||
ret = True
|
||||
# case 'Wood_MetalStripes.bmp': pass
|
||||
# case 'Wood_Misc.bmp': pass
|
||||
# case 'Wood_Nailed.bmp': pass
|
||||
# case 'Wood_Old.bmp': pass
|
||||
case 'Wood_Panel.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((2 / 255.0, 2 / 255.0, 2 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((59 / 255.0, 59 / 255.0, 59 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((30 / 255.0, 30 / 255.0, 30 / 255.0))
|
||||
rawmtl.mSpecularPower = 25.0
|
||||
ret = True
|
||||
# case 'Wood_Plain.bmp': pass
|
||||
case 'Wood_Plain2.bmp':
|
||||
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
|
||||
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
|
||||
rawmtl.mSpecular.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
|
||||
rawmtl.mEmissive.from_const_rgb((50 / 255.0, 50 / 255.0, 50 / 255.0))
|
||||
rawmtl.mSpecularPower = 50.0
|
||||
ret = True
|
||||
# case 'Wood_Raft.bmp': pass
|
||||
case _: pass # no mathed
|
||||
|
||||
# if changed, set to blender mtl
|
||||
if ret:
|
||||
set_raw_virtools_material(mtl, rawmtl)
|
||||
|
||||
# return result
|
||||
return ret
|
||||
|
||||
#endregion
|
||||
|
||||
#region Operators
|
||||
|
||||
class BBP_OT_apply_virtools_material(bpy.types.Operator):
|
||||
@ -546,27 +900,61 @@ class BBP_OT_apply_virtools_material(bpy.types.Operator):
|
||||
bl_idname = "bbp.apply_virtools_material"
|
||||
bl_label = "Apply to Blender Material"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_apply_virtools_material'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.material is not None
|
||||
|
||||
def execute(self, context):
|
||||
mtl: bpy.types.Material = context.material
|
||||
mtl = typing.cast(bpy.types.Material, context.material)
|
||||
apply_to_blender_material(mtl)
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_OT_fix_single_material(bpy.types.Operator):
|
||||
"""Fix Active Materials by Its Referred Ballance Texture Name."""
|
||||
bl_idname = "bbp.fix_single_material"
|
||||
bl_label = "Fix Material"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_fix_single_material'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.material is None: return False
|
||||
if not PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder(): return False
|
||||
return True
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_confirm(self, event)
|
||||
|
||||
def execute(self, context):
|
||||
# get mtl and try to fix
|
||||
mtl = typing.cast(bpy.types.Material, context.material)
|
||||
ret: bool = fix_material(mtl)
|
||||
|
||||
# if suc, apply to blender mtl and show info
|
||||
if ret:
|
||||
apply_to_blender_material(mtl)
|
||||
self.report({'INFO'}, 'Fix material successfully.')
|
||||
else:
|
||||
# otherwise report warning
|
||||
self.report({'WARNING'}, 'This material is not suit for fixer.')
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_OT_preset_virtools_material(bpy.types.Operator):
|
||||
"""Preset Virtools Material with Original Ballance Data."""
|
||||
bl_idname = "bbp.preset_virtools_material"
|
||||
bl_label = "Preset Virtools Material"
|
||||
bl_options = {'UNDO'}
|
||||
bl_translation_context = 'BBP_OT_preset_virtools_material'
|
||||
|
||||
preset_type: bpy.props.EnumProperty(
|
||||
name = "Preset",
|
||||
description = "The preset which you want to apply.",
|
||||
items = _g_Helper_MtlPreset.generate_items(),
|
||||
)
|
||||
) # type: ignore
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@ -581,18 +969,21 @@ class BBP_OT_preset_virtools_material(bpy.types.Operator):
|
||||
|
||||
def execute(self, context):
|
||||
# get essential value
|
||||
mtl: bpy.types.Material = context.material
|
||||
mtl = typing.cast(bpy.types.Material, context.material)
|
||||
expected_preset: MaterialPresetType = _g_Helper_MtlPreset.get_selection(self.preset_type)
|
||||
|
||||
# apply preset to material
|
||||
preset_virtools_material(mtl, expected_preset)
|
||||
# and apply to blender
|
||||
apply_to_blender_material(mtl)
|
||||
return {'FINISHED'}
|
||||
|
||||
class BBP_OT_direct_set_virtools_texture(bpy.types.Operator, UTIL_file_browser.ImportBallanceImage):
|
||||
"""Import and Assign Texture Directly"""
|
||||
bl_idname = "bbp.direct_set_virtools_texture"
|
||||
bl_label = "Import and Assign Texture"
|
||||
bl_options = {'UNDO'}
|
||||
bl_options = {'UNDO', 'INTERNAL'} # NOTE: Use 'INTERNAL' to remove it from search result.
|
||||
bl_translation_context = 'BBP_OT_direct_set_virtools_texture'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@ -613,7 +1004,7 @@ class BBP_OT_direct_set_virtools_texture(bpy.types.Operator, UTIL_file_browser.I
|
||||
|
||||
def execute(self, context):
|
||||
# get assoc mtl
|
||||
mtl: bpy.types.Material = context.material
|
||||
mtl = typing.cast(bpy.types.Material, context.material)
|
||||
rawmtl: RawVirtoolsMaterial = get_raw_virtools_material(mtl)
|
||||
|
||||
# import texture according to whether it is ballance texture
|
||||
@ -645,6 +1036,7 @@ class BBP_PT_virtools_material(bpy.types.Panel):
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "material"
|
||||
bl_translation_context = 'BBP_PT_virtools_material'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@ -653,15 +1045,22 @@ class BBP_PT_virtools_material(bpy.types.Panel):
|
||||
def draw(self, context):
|
||||
# get layout and target
|
||||
layout = self.layout
|
||||
props: BBP_PG_virtools_material = get_virtools_material(context.material)
|
||||
mtl = typing.cast(bpy.types.Material, context.material)
|
||||
props: BBP_PG_virtools_material = get_virtools_material(mtl)
|
||||
rawdata: RawVirtoolsMaterial = get_raw_virtools_material(mtl)
|
||||
|
||||
# draw operator
|
||||
row = layout.row()
|
||||
row.operator(BBP_OT_preset_virtools_material.bl_idname, text = 'Preset', icon = "PRESET")
|
||||
row.operator(BBP_OT_apply_virtools_material.bl_idname, text = 'Apply', icon = "NODETREE")
|
||||
row.operator(
|
||||
BBP_OT_preset_virtools_material.bl_idname, text='Preset', icon = "PRESET",
|
||||
text_ctxt='BBP_PT_virtools_material/draw')
|
||||
row.operator(
|
||||
BBP_OT_apply_virtools_material.bl_idname, text='Apply', icon = "NODETREE",
|
||||
text_ctxt='BBP_PT_virtools_material/draw')
|
||||
row.operator(BBP_OT_fix_single_material.bl_idname, text='', icon = "MODIFIER")
|
||||
|
||||
# draw data
|
||||
layout.label(text="Color Parameters")
|
||||
layout.label(text="Color Parameters", text_ctxt='BBP_PT_virtools_material/draw')
|
||||
layout.prop(props, 'ambient')
|
||||
layout.prop(props, 'diffuse')
|
||||
layout.prop(props, 'specular')
|
||||
@ -669,22 +1068,22 @@ class BBP_PT_virtools_material(bpy.types.Panel):
|
||||
layout.prop(props, 'specular_power')
|
||||
|
||||
layout.separator()
|
||||
layout.label(text="Mode Parameters")
|
||||
layout.label(text="Mode Parameters", text_ctxt='BBP_PT_virtools_material/draw')
|
||||
layout.prop(props, 'enable_two_sided')
|
||||
layout.prop(props, 'fill_mode')
|
||||
layout.prop(props, 'shade_mode')
|
||||
|
||||
layout.separator()
|
||||
layout.label(text="Texture Parameters")
|
||||
layout.label(text="Texture Parameters", text_ctxt='BBP_PT_virtools_material/draw')
|
||||
# texture prop with direct importing
|
||||
sublay = layout.row()
|
||||
sublay.prop(props, 'texture', emboss = True)
|
||||
sublay.operator(BBP_OT_direct_set_virtools_texture.bl_idname, text = '', icon = 'FILEBROWSER')
|
||||
sublay.operator(BBP_OT_direct_set_virtools_texture.bl_idname, text='', icon='FILEBROWSER')
|
||||
# texture detail
|
||||
if props.texture is not None:
|
||||
if rawdata.mTexture is not None:
|
||||
# have texture, show texture settings and enclosed by a border.
|
||||
boxlayout = layout.box()
|
||||
boxlayout.label(text="Virtools Texture Settings")
|
||||
boxlayout.label(text="Virtools Texture Settings", text_ctxt='BBP_PT_virtools_material/draw')
|
||||
PROP_virtools_texture.draw_virtools_texture(props.texture, boxlayout)
|
||||
|
||||
layout.prop(props, 'texture_blend_mode')
|
||||
@ -692,32 +1091,33 @@ class BBP_PT_virtools_material(bpy.types.Panel):
|
||||
layout.prop(props, 'texture_mag_mode')
|
||||
layout.prop(props, 'texture_address_mode')
|
||||
layout.prop(props, 'enable_perspective_correction')
|
||||
if (int(props.texture_address_mode) == UTIL_virtools_types.VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSBORDER.value):
|
||||
if rawdata.mTextureAddressMode == UTIL_virtools_types.VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSBORDER:
|
||||
layout.prop(props, 'texture_border_color')
|
||||
|
||||
layout.separator()
|
||||
layout.label(text="Alpha Test Parameters")
|
||||
layout.label(text="Alpha Test Parameters", text_ctxt='BBP_PT_virtools_material/draw')
|
||||
layout.prop(props, 'enable_alpha_test')
|
||||
if props.enable_alpha_test:
|
||||
if rawdata.mEnableAlphaTest:
|
||||
layout.prop(props, 'alpha_func')
|
||||
layout.prop(props, 'alpha_ref')
|
||||
|
||||
layout.separator()
|
||||
layout.label(text="Alpha Blend Parameters")
|
||||
layout.label(text="Alpha Blend Parameters", text_ctxt='BBP_PT_virtools_material/draw')
|
||||
layout.prop(props, 'enable_alpha_blend')
|
||||
if props.enable_alpha_blend:
|
||||
if rawdata.mEnableAlphaBlend:
|
||||
layout.prop(props, 'source_blend')
|
||||
layout.prop(props, 'dest_blend')
|
||||
|
||||
layout.separator()
|
||||
layout.label(text="Z Write Parameters")
|
||||
layout.label(text="Z Write Parameters", text_ctxt='BBP_PT_virtools_material/draw')
|
||||
layout.prop(props, 'enable_z_write')
|
||||
if props.enable_z_write:
|
||||
if rawdata.mEnableZWrite:
|
||||
layout.prop(props, 'z_func')
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_PG_virtools_material)
|
||||
bpy.utils.register_class(BBP_OT_apply_virtools_material)
|
||||
bpy.utils.register_class(BBP_OT_fix_single_material)
|
||||
bpy.utils.register_class(BBP_OT_preset_virtools_material)
|
||||
bpy.utils.register_class(BBP_OT_direct_set_virtools_texture)
|
||||
bpy.utils.register_class(BBP_PT_virtools_material)
|
||||
@ -725,12 +1125,13 @@ def register():
|
||||
# add into material metadata
|
||||
bpy.types.Material.virtools_material = bpy.props.PointerProperty(type = BBP_PG_virtools_material)
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
# del from material metadata
|
||||
del bpy.types.Material.virtools_material
|
||||
|
||||
bpy.utils.unregister_class(BBP_PT_virtools_material)
|
||||
bpy.utils.unregister_class(BBP_OT_direct_set_virtools_texture)
|
||||
bpy.utils.unregister_class(BBP_OT_preset_virtools_material)
|
||||
bpy.utils.unregister_class(BBP_OT_fix_single_material)
|
||||
bpy.utils.unregister_class(BBP_OT_apply_virtools_material)
|
||||
bpy.utils.unregister_class(BBP_PG_virtools_material)
|
||||
|
@ -24,8 +24,9 @@ class BBP_PG_virtools_mesh(bpy.types.PropertyGroup):
|
||||
name = "Lit Mode",
|
||||
description = "Lighting mode of the mesh.",
|
||||
items = _g_Helper_VXMESH_LITMODE.generate_items(),
|
||||
default = _g_Helper_VXMESH_LITMODE.to_selection(RawVirtoolsMesh.cDefaultLitMode)
|
||||
)
|
||||
default = _g_Helper_VXMESH_LITMODE.to_selection(RawVirtoolsMesh.cDefaultLitMode),
|
||||
translation_context = 'BBP_PG_virtools_mesh/property'
|
||||
) # type: ignore
|
||||
|
||||
# Getter Setter
|
||||
|
||||
@ -54,6 +55,7 @@ class BBP_PT_virtools_mesh(bpy.types.Panel):
|
||||
bl_space_type = 'PROPERTIES'
|
||||
bl_region_type = 'WINDOW'
|
||||
bl_context = "data" # idk why blender use `data` as the mesh tab.
|
||||
bl_translation_context = 'BBP_PT_virtools_mesh'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
@ -69,14 +71,14 @@ class BBP_PT_virtools_mesh(bpy.types.Panel):
|
||||
|
||||
# Register
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_PG_virtools_mesh)
|
||||
bpy.utils.register_class(BBP_PT_virtools_mesh)
|
||||
|
||||
# add into mesh metadata
|
||||
bpy.types.Mesh.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh)
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
# remove from metadata
|
||||
del bpy.types.Mesh.virtools_mesh
|
||||
|
||||
|
@ -29,15 +29,17 @@ class BBP_PG_virtools_texture(bpy.types.PropertyGroup):
|
||||
name = "Save Options",
|
||||
description = "When saving a composition textures or sprites can be kept as reference to external files or converted to a given format and saved inside the composition file.",
|
||||
items = _g_Helper_CK_TEXTURE_SAVEOPTIONS.generate_items(),
|
||||
default = _g_Helper_CK_TEXTURE_SAVEOPTIONS.to_selection(RawVirtoolsTexture.cDefaultSaveOptions)
|
||||
)
|
||||
default = _g_Helper_CK_TEXTURE_SAVEOPTIONS.to_selection(RawVirtoolsTexture.cDefaultSaveOptions),
|
||||
translation_context = 'BBP_PG_virtools_texture/property'
|
||||
) # type: ignore
|
||||
|
||||
video_format: bpy.props.EnumProperty(
|
||||
name = "Video Format",
|
||||
description = "The desired surface pixel format in video memory.",
|
||||
items = _g_Helper_VX_PIXELFORMAT.generate_items(),
|
||||
default = _g_Helper_VX_PIXELFORMAT.to_selection(RawVirtoolsTexture.cDefaultVideoFormat)
|
||||
)
|
||||
default = _g_Helper_VX_PIXELFORMAT.to_selection(RawVirtoolsTexture.cDefaultVideoFormat),
|
||||
translation_context = 'BBP_PG_virtools_texture/property'
|
||||
) # type: ignore
|
||||
|
||||
#region Virtools Texture Getter Setter
|
||||
|
||||
@ -192,13 +194,13 @@ def get_nonballance_texture_preset() -> RawVirtoolsTexture:
|
||||
|
||||
#endregion
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
bpy.utils.register_class(BBP_PG_virtools_texture)
|
||||
|
||||
# add into image metadata
|
||||
bpy.types.Image.virtools_texture = bpy.props.PointerProperty(type = BBP_PG_virtools_texture)
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
# del from image metadata
|
||||
del bpy.types.Image.virtools_texture
|
||||
|
||||
|
@ -1,3 +0,0 @@
|
||||
# PyBMap Binding
|
||||
|
||||
Please note that this folder is a part of [libcmo21](https://github.com/yyc12345/libcmo21). And all Python scripts is copied from source project. If any issues raised in this sub module, please correct it in parent project and this project will sync to parent project's work.
|
@ -1,785 +0,0 @@
|
||||
import ctypes, os, sys, typing
|
||||
|
||||
#region Type Defines
|
||||
|
||||
class BMapException(Exception):
|
||||
"""
|
||||
The exception thrown by BMap bindings.
|
||||
"""
|
||||
pass
|
||||
|
||||
bm_CKSTRING = ctypes.c_char_p
|
||||
bm_CKSTRING_p = ctypes.POINTER(bm_CKSTRING)
|
||||
bm_CKDWORD = ctypes.c_uint32
|
||||
bm_CKDWORD_p = ctypes.POINTER(bm_CKDWORD)
|
||||
bm_CKDWORD_pp = ctypes.POINTER(bm_CKDWORD_p)
|
||||
bm_CKWORD = ctypes.c_uint16
|
||||
bm_CKWORD_p = ctypes.POINTER(bm_CKWORD)
|
||||
bm_CKWORD_pp = ctypes.POINTER(bm_CKWORD_p)
|
||||
bm_CKID = ctypes.c_uint32
|
||||
bm_CKID_p = ctypes.POINTER(bm_CKID)
|
||||
bm_CKID_pp = ctypes.POINTER(bm_CKID_p)
|
||||
bm_CKFLOAT = ctypes.c_float
|
||||
bm_CKFLOAT_p = ctypes.POINTER(bm_CKFLOAT)
|
||||
bm_CKINT = ctypes.c_int32
|
||||
bm_CKBYTE = ctypes.c_uint8
|
||||
bm_CKBYTE_p = ctypes.POINTER(bm_CKBYTE)
|
||||
|
||||
bm_enum = bm_CKDWORD
|
||||
bm_enum_p = ctypes.POINTER(bm_enum)
|
||||
bm_bool = ctypes.c_bool
|
||||
bm_bool_p = ctypes.POINTER(bm_bool)
|
||||
bm_void_p = ctypes.c_void_p
|
||||
bm_void_pp = ctypes.POINTER(ctypes.c_void_p)
|
||||
|
||||
bm_callback = ctypes.CFUNCTYPE(None, bm_CKSTRING)
|
||||
|
||||
class bm_VxVector2(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('x', bm_CKFLOAT),
|
||||
('y', bm_CKFLOAT),
|
||||
]
|
||||
bm_VxVector2_p = ctypes.POINTER(bm_VxVector2)
|
||||
bm_VxVector2_pp = ctypes.POINTER(bm_VxVector2_p)
|
||||
class bm_VxVector3(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('x', bm_CKFLOAT),
|
||||
('y', bm_CKFLOAT),
|
||||
('z', bm_CKFLOAT),
|
||||
]
|
||||
bm_VxVector3_p = ctypes.POINTER(bm_VxVector3)
|
||||
bm_VxVector3_pp = ctypes.POINTER(bm_VxVector3_p)
|
||||
class bm_VxColor(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('r', bm_CKFLOAT),
|
||||
('g', bm_CKFLOAT),
|
||||
('b', bm_CKFLOAT),
|
||||
('a', bm_CKFLOAT),
|
||||
]
|
||||
bm_VxColor_p = ctypes.POINTER(bm_VxColor)
|
||||
class bm_VxMatrix(ctypes.Structure):
|
||||
_fields_ = list(
|
||||
(f'i{idx}', bm_CKFLOAT) for idx in range(16)
|
||||
)
|
||||
bm_VxMatrix_p = ctypes.POINTER(bm_VxMatrix)
|
||||
|
||||
#endregion
|
||||
|
||||
#region BMap Loader
|
||||
|
||||
_g_BMapLibName: str
|
||||
|
||||
if sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
||||
_g_BMapLibName = "BMap.dll"
|
||||
elif sys.platform.startswith('linux') or sys.platform.startswith('freebsd'):
|
||||
_g_BMapLibName = "BMap.so"
|
||||
elif sys.platform.startswith('darwin'):
|
||||
_g_BMapLibName = "BMap.dylib"
|
||||
else:
|
||||
_g_BMapLibName = "BMap.bin"
|
||||
|
||||
_g_BMapModule: ctypes.CDLL | None = None
|
||||
try:
|
||||
_g_BMapModule = ctypes.cdll.LoadLibrary(
|
||||
os.path.join(os.path.dirname(__file__), _g_BMapLibName)
|
||||
)
|
||||
except:
|
||||
_g_BMapModule = None
|
||||
|
||||
def is_bmap_available() -> bool:
|
||||
return _g_BMapModule is not None
|
||||
|
||||
def _bmap_error_check(result: bool, func, args):
|
||||
if not result:
|
||||
raise BMapException("BMap operation failed.")
|
||||
return result
|
||||
|
||||
def _create_bmap_func(fct_name: str, fct_params: list[typing.Any]) -> typing.Callable[..., bm_bool]:
|
||||
if _g_BMapModule is None: return None
|
||||
|
||||
cache: typing.Callable[..., bm_bool] = getattr(_g_BMapModule, fct_name)
|
||||
cache.argtypes = fct_params
|
||||
cache.restype = bm_bool
|
||||
cache.errcheck = _bmap_error_check
|
||||
return cache
|
||||
|
||||
#endregion
|
||||
|
||||
#region Function Defines
|
||||
|
||||
## BMInit
|
||||
# @return True if no error, otherwise False.
|
||||
BMInit = _create_bmap_func('BMInit', [])
|
||||
## BMDispose
|
||||
# @return True if no error, otherwise False.
|
||||
BMDispose = _create_bmap_func('BMDispose', [])
|
||||
## BMFile_Load
|
||||
# @param file_name[in] Type: LibCmo::CKSTRING.
|
||||
# @param temp_folder[in] Type: LibCmo::CKSTRING.
|
||||
# @param texture_folder[in] Type: LibCmo::CKSTRING.
|
||||
# @param raw_callback[in] Type: BMap::NakedOutputCallback.
|
||||
# @param encoding_count[in] Type: LibCmo::CKDWORD.
|
||||
# @param encodings[in] Type: LibCmo::CKSTRING*.
|
||||
# @param out_file[out] Type: BMap::BMFile*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Load = _create_bmap_func('BMFile_Load', [bm_CKSTRING, bm_CKSTRING, bm_CKSTRING, bm_callback, bm_CKDWORD, bm_CKSTRING_p, bm_void_pp])
|
||||
## BMFile_Create
|
||||
# @param temp_folder[in] Type: LibCmo::CKSTRING.
|
||||
# @param texture_folder[in] Type: LibCmo::CKSTRING.
|
||||
# @param raw_callback[in] Type: BMap::NakedOutputCallback.
|
||||
# @param encoding_count[in] Type: LibCmo::CKDWORD.
|
||||
# @param encodings[in] Type: LibCmo::CKSTRING*.
|
||||
# @param out_file[out] Type: BMap::BMFile*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Create = _create_bmap_func('BMFile_Create', [bm_CKSTRING, bm_CKSTRING, bm_callback, bm_CKDWORD, bm_CKSTRING_p, bm_void_pp])
|
||||
## BMFile_Save
|
||||
# @param map_file[in] Type: BMap::BMFile*.
|
||||
# @param file_name[in] Type: LibCmo::CKSTRING.
|
||||
# @param texture_save_opt[in] Type: LibCmo::CK2::CK_TEXTURE_SAVEOPTIONS.
|
||||
# @param use_compress[in] Type: bool.
|
||||
# @param compreess_level[in] Type: LibCmo::CKINT.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Save = _create_bmap_func('BMFile_Save', [bm_void_p, bm_CKSTRING, bm_enum, bm_bool, bm_CKINT])
|
||||
## BMFile_Free
|
||||
# @param map_file[in] Type: BMap::BMFile*.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Free = _create_bmap_func('BMFile_Free', [bm_void_p])
|
||||
## BMFile_GetGroupCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetGroupCount = _create_bmap_func('BMFile_GetGroupCount', [bm_void_p, bm_CKDWORD_p])
|
||||
## BMFile_GetGroup
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param idx[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetGroup = _create_bmap_func('BMFile_GetGroup', [bm_void_p, bm_CKDWORD, bm_CKID_p])
|
||||
## BMFile_CreateGroup
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_CreateGroup = _create_bmap_func('BMFile_CreateGroup', [bm_void_p, bm_CKID_p])
|
||||
## BMFile_Get3dObjectCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Get3dObjectCount = _create_bmap_func('BMFile_Get3dObjectCount', [bm_void_p, bm_CKDWORD_p])
|
||||
## BMFile_Get3dObject
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param idx[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Get3dObject = _create_bmap_func('BMFile_Get3dObject', [bm_void_p, bm_CKDWORD, bm_CKID_p])
|
||||
## BMFile_Create3dObject
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_Create3dObject = _create_bmap_func('BMFile_Create3dObject', [bm_void_p, bm_CKID_p])
|
||||
## BMFile_GetMeshCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetMeshCount = _create_bmap_func('BMFile_GetMeshCount', [bm_void_p, bm_CKDWORD_p])
|
||||
## BMFile_GetMesh
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param idx[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetMesh = _create_bmap_func('BMFile_GetMesh', [bm_void_p, bm_CKDWORD, bm_CKID_p])
|
||||
## BMFile_CreateMesh
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_CreateMesh = _create_bmap_func('BMFile_CreateMesh', [bm_void_p, bm_CKID_p])
|
||||
## BMFile_GetMaterialCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetMaterialCount = _create_bmap_func('BMFile_GetMaterialCount', [bm_void_p, bm_CKDWORD_p])
|
||||
## BMFile_GetMaterial
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param idx[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetMaterial = _create_bmap_func('BMFile_GetMaterial', [bm_void_p, bm_CKDWORD, bm_CKID_p])
|
||||
## BMFile_CreateMaterial
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_CreateMaterial = _create_bmap_func('BMFile_CreateMaterial', [bm_void_p, bm_CKID_p])
|
||||
## BMFile_GetTextureCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetTextureCount = _create_bmap_func('BMFile_GetTextureCount', [bm_void_p, bm_CKDWORD_p])
|
||||
## BMFile_GetTexture
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param idx[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_GetTexture = _create_bmap_func('BMFile_GetTexture', [bm_void_p, bm_CKDWORD, bm_CKID_p])
|
||||
## BMFile_CreateTexture
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMFile_CreateTexture = _create_bmap_func('BMFile_CreateTexture', [bm_void_p, bm_CKID_p])
|
||||
## BMMeshTrans_New
|
||||
# @param out_trans[out] Type: BMap::BMMeshTransition*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_New = _create_bmap_func('BMMeshTrans_New', [bm_void_pp])
|
||||
## BMMeshTrans_Delete
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_Delete = _create_bmap_func('BMMeshTrans_Delete', [bm_void_p])
|
||||
## BMMeshTrans_PrepareVertexCount
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareVertexCount = _create_bmap_func('BMMeshTrans_PrepareVertexCount', [bm_void_p, bm_CKDWORD])
|
||||
## BMMeshTrans_PrepareVertex
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::VxMath::VxVector3*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareVertex = _create_bmap_func('BMMeshTrans_PrepareVertex', [bm_void_p, bm_VxVector3_pp])
|
||||
## BMMeshTrans_PrepareNormalCount
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareNormalCount = _create_bmap_func('BMMeshTrans_PrepareNormalCount', [bm_void_p, bm_CKDWORD])
|
||||
## BMMeshTrans_PrepareNormal
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::VxMath::VxVector3*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareNormal = _create_bmap_func('BMMeshTrans_PrepareNormal', [bm_void_p, bm_VxVector3_pp])
|
||||
## BMMeshTrans_PrepareUVCount
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareUVCount = _create_bmap_func('BMMeshTrans_PrepareUVCount', [bm_void_p, bm_CKDWORD])
|
||||
## BMMeshTrans_PrepareUV
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::VxMath::VxVector2*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareUV = _create_bmap_func('BMMeshTrans_PrepareUV', [bm_void_p, bm_VxVector2_pp])
|
||||
## BMMeshTrans_PrepareMtlSlotCount
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareMtlSlotCount = _create_bmap_func('BMMeshTrans_PrepareMtlSlotCount', [bm_void_p, bm_CKDWORD])
|
||||
## BMMeshTrans_PrepareMtlSlot
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::CK2::CK_ID*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareMtlSlot = _create_bmap_func('BMMeshTrans_PrepareMtlSlot', [bm_void_p, bm_CKID_pp])
|
||||
## BMMeshTrans_PrepareFaceCount
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareFaceCount = _create_bmap_func('BMMeshTrans_PrepareFaceCount', [bm_void_p, bm_CKDWORD])
|
||||
## BMMeshTrans_PrepareFaceVertexIndices
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::CKDWORD*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareFaceVertexIndices = _create_bmap_func('BMMeshTrans_PrepareFaceVertexIndices', [bm_void_p, bm_CKDWORD_pp])
|
||||
## BMMeshTrans_PrepareFaceNormalIndices
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::CKDWORD*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareFaceNormalIndices = _create_bmap_func('BMMeshTrans_PrepareFaceNormalIndices', [bm_void_p, bm_CKDWORD_pp])
|
||||
## BMMeshTrans_PrepareFaceUVIndices
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::CKDWORD*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareFaceUVIndices = _create_bmap_func('BMMeshTrans_PrepareFaceUVIndices', [bm_void_p, bm_CKDWORD_pp])
|
||||
## BMMeshTrans_PrepareFaceMtlSlot
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param out_mem[out] Type: LibCmo::CKDWORD*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_PrepareFaceMtlSlot = _create_bmap_func('BMMeshTrans_PrepareFaceMtlSlot', [bm_void_p, bm_CKDWORD_pp])
|
||||
## BMMeshTrans_Parse
|
||||
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
|
||||
# @param bmfile[in] Type: BMap::BMFile*.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMeshTrans_Parse = _create_bmap_func('BMMeshTrans_Parse', [bm_void_p, bm_void_p, bm_CKID])
|
||||
## BMObject_GetName
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_name[out] Type: LibCmo::CKSTRING. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMObject_GetName = _create_bmap_func('BMObject_GetName', [bm_void_p, bm_CKID, bm_CKSTRING_p])
|
||||
## BMObject_SetName
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param name[in] Type: LibCmo::CKSTRING.
|
||||
# @return True if no error, otherwise False.
|
||||
BMObject_SetName = _create_bmap_func('BMObject_SetName', [bm_void_p, bm_CKID, bm_CKSTRING])
|
||||
## BMGroup_AddObject
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param memberid[in] Type: LibCmo::CK2::CK_ID.
|
||||
# @return True if no error, otherwise False.
|
||||
BMGroup_AddObject = _create_bmap_func('BMGroup_AddObject', [bm_void_p, bm_CKID, bm_CKID])
|
||||
## BMGroup_GetObjectCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMGroup_GetObjectCount = _create_bmap_func('BMGroup_GetObjectCount', [bm_void_p, bm_CKID, bm_CKDWORD_p])
|
||||
## BMGroup_GetObject
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param pos[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_objid[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMGroup_GetObject = _create_bmap_func('BMGroup_GetObject', [bm_void_p, bm_CKID, bm_CKDWORD, bm_CKID_p])
|
||||
## BMTexture_GetFileName
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_filename[out] Type: LibCmo::CKSTRING. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_GetFileName = _create_bmap_func('BMTexture_GetFileName', [bm_void_p, bm_CKID, bm_CKSTRING_p])
|
||||
## BMTexture_LoadImage
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param filename[in] Type: LibCmo::CKSTRING.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_LoadImage = _create_bmap_func('BMTexture_LoadImage', [bm_void_p, bm_CKID, bm_CKSTRING])
|
||||
## BMTexture_SaveImage
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param filename[in] Type: LibCmo::CKSTRING.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_SaveImage = _create_bmap_func('BMTexture_SaveImage', [bm_void_p, bm_CKID, bm_CKSTRING])
|
||||
## BMTexture_GetSaveOptions
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_saveopt[out] Type: LibCmo::CK2::CK_TEXTURE_SAVEOPTIONS. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_GetSaveOptions = _create_bmap_func('BMTexture_GetSaveOptions', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMTexture_SetSaveOptions
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param saveopt[in] Type: LibCmo::CK2::CK_TEXTURE_SAVEOPTIONS.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_SetSaveOptions = _create_bmap_func('BMTexture_SetSaveOptions', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMTexture_GetVideoFormat
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_vfmt[out] Type: LibCmo::VxMath::VX_PIXELFORMAT. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_GetVideoFormat = _create_bmap_func('BMTexture_GetVideoFormat', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMTexture_SetVideoFormat
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param vfmt[in] Type: LibCmo::VxMath::VX_PIXELFORMAT.
|
||||
# @return True if no error, otherwise False.
|
||||
BMTexture_SetVideoFormat = _create_bmap_func('BMTexture_SetVideoFormat', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetDiffuse
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VxColor. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetDiffuse = _create_bmap_func('BMMaterial_GetDiffuse', [bm_void_p, bm_CKID, bm_VxColor_p])
|
||||
## BMMaterial_SetDiffuse
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param col[in] Type: LibCmo::VxMath::VxColor.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetDiffuse = _create_bmap_func('BMMaterial_SetDiffuse', [bm_void_p, bm_CKID, bm_VxColor])
|
||||
## BMMaterial_GetAmbient
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VxColor. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetAmbient = _create_bmap_func('BMMaterial_GetAmbient', [bm_void_p, bm_CKID, bm_VxColor_p])
|
||||
## BMMaterial_SetAmbient
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param col[in] Type: LibCmo::VxMath::VxColor.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetAmbient = _create_bmap_func('BMMaterial_SetAmbient', [bm_void_p, bm_CKID, bm_VxColor])
|
||||
## BMMaterial_GetSpecular
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VxColor. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetSpecular = _create_bmap_func('BMMaterial_GetSpecular', [bm_void_p, bm_CKID, bm_VxColor_p])
|
||||
## BMMaterial_SetSpecular
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param col[in] Type: LibCmo::VxMath::VxColor.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetSpecular = _create_bmap_func('BMMaterial_SetSpecular', [bm_void_p, bm_CKID, bm_VxColor])
|
||||
## BMMaterial_GetEmissive
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VxColor. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetEmissive = _create_bmap_func('BMMaterial_GetEmissive', [bm_void_p, bm_CKID, bm_VxColor_p])
|
||||
## BMMaterial_SetEmissive
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param col[in] Type: LibCmo::VxMath::VxColor.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetEmissive = _create_bmap_func('BMMaterial_SetEmissive', [bm_void_p, bm_CKID, bm_VxColor])
|
||||
## BMMaterial_GetSpecularPower
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::CKFLOAT. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetSpecularPower = _create_bmap_func('BMMaterial_GetSpecularPower', [bm_void_p, bm_CKID, bm_CKFLOAT_p])
|
||||
## BMMaterial_SetSpecularPower
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::CKFLOAT.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetSpecularPower = _create_bmap_func('BMMaterial_SetSpecularPower', [bm_void_p, bm_CKID, bm_CKFLOAT])
|
||||
## BMMaterial_GetTexture
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_texid[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTexture = _create_bmap_func('BMMaterial_GetTexture', [bm_void_p, bm_CKID, bm_CKID_p])
|
||||
## BMMaterial_SetTexture
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param texid[in] Type: LibCmo::CK2::CK_ID.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTexture = _create_bmap_func('BMMaterial_SetTexture', [bm_void_p, bm_CKID, bm_CKID])
|
||||
## BMMaterial_GetTextureBorderColor
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTextureBorderColor = _create_bmap_func('BMMaterial_GetTextureBorderColor', [bm_void_p, bm_CKID, bm_CKDWORD_p])
|
||||
## BMMaterial_SetTextureBorderColor
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTextureBorderColor = _create_bmap_func('BMMaterial_SetTextureBorderColor', [bm_void_p, bm_CKID, bm_CKDWORD])
|
||||
## BMMaterial_GetTextureBlendMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXTEXTURE_BLENDMODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTextureBlendMode = _create_bmap_func('BMMaterial_GetTextureBlendMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetTextureBlendMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXTEXTURE_BLENDMODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTextureBlendMode = _create_bmap_func('BMMaterial_SetTextureBlendMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetTextureMinMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXTEXTURE_FILTERMODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTextureMinMode = _create_bmap_func('BMMaterial_GetTextureMinMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetTextureMinMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXTEXTURE_FILTERMODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTextureMinMode = _create_bmap_func('BMMaterial_SetTextureMinMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetTextureMagMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXTEXTURE_FILTERMODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTextureMagMode = _create_bmap_func('BMMaterial_GetTextureMagMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetTextureMagMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXTEXTURE_FILTERMODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTextureMagMode = _create_bmap_func('BMMaterial_SetTextureMagMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetTextureAddressMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXTEXTURE_ADDRESSMODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTextureAddressMode = _create_bmap_func('BMMaterial_GetTextureAddressMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetTextureAddressMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXTEXTURE_ADDRESSMODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTextureAddressMode = _create_bmap_func('BMMaterial_SetTextureAddressMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetSourceBlend
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXBLEND_MODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetSourceBlend = _create_bmap_func('BMMaterial_GetSourceBlend', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetSourceBlend
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXBLEND_MODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetSourceBlend = _create_bmap_func('BMMaterial_SetSourceBlend', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetDestBlend
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXBLEND_MODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetDestBlend = _create_bmap_func('BMMaterial_GetDestBlend', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetDestBlend
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXBLEND_MODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetDestBlend = _create_bmap_func('BMMaterial_SetDestBlend', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetFillMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXFILL_MODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetFillMode = _create_bmap_func('BMMaterial_GetFillMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetFillMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXFILL_MODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetFillMode = _create_bmap_func('BMMaterial_SetFillMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetShadeMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXSHADE_MODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetShadeMode = _create_bmap_func('BMMaterial_GetShadeMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetShadeMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXSHADE_MODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetShadeMode = _create_bmap_func('BMMaterial_SetShadeMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetAlphaTestEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetAlphaTestEnabled = _create_bmap_func('BMMaterial_GetAlphaTestEnabled', [bm_void_p, bm_CKID, bm_bool_p])
|
||||
## BMMaterial_SetAlphaTestEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param enabled[in] Type: bool.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetAlphaTestEnabled = _create_bmap_func('BMMaterial_SetAlphaTestEnabled', [bm_void_p, bm_CKID, bm_bool])
|
||||
## BMMaterial_GetAlphaBlendEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetAlphaBlendEnabled = _create_bmap_func('BMMaterial_GetAlphaBlendEnabled', [bm_void_p, bm_CKID, bm_bool_p])
|
||||
## BMMaterial_SetAlphaBlendEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param enabled[in] Type: bool.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetAlphaBlendEnabled = _create_bmap_func('BMMaterial_SetAlphaBlendEnabled', [bm_void_p, bm_CKID, bm_bool])
|
||||
## BMMaterial_GetPerspectiveCorrectionEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetPerspectiveCorrectionEnabled = _create_bmap_func('BMMaterial_GetPerspectiveCorrectionEnabled', [bm_void_p, bm_CKID, bm_bool_p])
|
||||
## BMMaterial_SetPerspectiveCorrectionEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param enabled[in] Type: bool.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetPerspectiveCorrectionEnabled = _create_bmap_func('BMMaterial_SetPerspectiveCorrectionEnabled', [bm_void_p, bm_CKID, bm_bool])
|
||||
## BMMaterial_GetZWriteEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetZWriteEnabled = _create_bmap_func('BMMaterial_GetZWriteEnabled', [bm_void_p, bm_CKID, bm_bool_p])
|
||||
## BMMaterial_SetZWriteEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param enabled[in] Type: bool.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetZWriteEnabled = _create_bmap_func('BMMaterial_SetZWriteEnabled', [bm_void_p, bm_CKID, bm_bool])
|
||||
## BMMaterial_GetTwoSidedEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetTwoSidedEnabled = _create_bmap_func('BMMaterial_GetTwoSidedEnabled', [bm_void_p, bm_CKID, bm_bool_p])
|
||||
## BMMaterial_SetTwoSidedEnabled
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param enabled[in] Type: bool.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetTwoSidedEnabled = _create_bmap_func('BMMaterial_SetTwoSidedEnabled', [bm_void_p, bm_CKID, bm_bool])
|
||||
## BMMaterial_GetAlphaRef
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::CKBYTE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetAlphaRef = _create_bmap_func('BMMaterial_GetAlphaRef', [bm_void_p, bm_CKID, bm_CKBYTE_p])
|
||||
## BMMaterial_SetAlphaRef
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::CKBYTE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetAlphaRef = _create_bmap_func('BMMaterial_SetAlphaRef', [bm_void_p, bm_CKID, bm_CKBYTE])
|
||||
## BMMaterial_GetAlphaFunc
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXCMPFUNC. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetAlphaFunc = _create_bmap_func('BMMaterial_GetAlphaFunc', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetAlphaFunc
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXCMPFUNC.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetAlphaFunc = _create_bmap_func('BMMaterial_SetAlphaFunc', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMaterial_GetZFunc
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_val[out] Type: LibCmo::VxMath::VXCMPFUNC. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_GetZFunc = _create_bmap_func('BMMaterial_GetZFunc', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMaterial_SetZFunc
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param val[in] Type: LibCmo::VxMath::VXCMPFUNC.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMaterial_SetZFunc = _create_bmap_func('BMMaterial_SetZFunc', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMesh_GetLitMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mode[out] Type: LibCmo::VxMath::VXMESH_LITMODE. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetLitMode = _create_bmap_func('BMMesh_GetLitMode', [bm_void_p, bm_CKID, bm_enum_p])
|
||||
## BMMesh_SetLitMode
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param mode[in] Type: LibCmo::VxMath::VXMESH_LITMODE.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_SetLitMode = _create_bmap_func('BMMesh_SetLitMode', [bm_void_p, bm_CKID, bm_enum])
|
||||
## BMMesh_GetVertexCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetVertexCount = _create_bmap_func('BMMesh_GetVertexCount', [bm_void_p, bm_CKID, bm_CKDWORD_p])
|
||||
## BMMesh_SetVertexCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_SetVertexCount = _create_bmap_func('BMMesh_SetVertexCount', [bm_void_p, bm_CKID, bm_CKDWORD])
|
||||
## BMMesh_GetVertexPositions
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mem[out] Type: LibCmo::VxMath::VxVector3*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetVertexPositions = _create_bmap_func('BMMesh_GetVertexPositions', [bm_void_p, bm_CKID, bm_VxVector3_pp])
|
||||
## BMMesh_GetVertexNormals
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mem[out] Type: LibCmo::VxMath::VxVector3*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetVertexNormals = _create_bmap_func('BMMesh_GetVertexNormals', [bm_void_p, bm_CKID, bm_VxVector3_pp])
|
||||
## BMMesh_GetVertexUVs
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mem[out] Type: LibCmo::VxMath::VxVector2*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetVertexUVs = _create_bmap_func('BMMesh_GetVertexUVs', [bm_void_p, bm_CKID, bm_VxVector2_pp])
|
||||
## BMMesh_GetFaceCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetFaceCount = _create_bmap_func('BMMesh_GetFaceCount', [bm_void_p, bm_CKID, bm_CKDWORD_p])
|
||||
## BMMesh_SetFaceCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_SetFaceCount = _create_bmap_func('BMMesh_SetFaceCount', [bm_void_p, bm_CKID, bm_CKDWORD])
|
||||
## BMMesh_GetFaceIndices
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mem[out] Type: LibCmo::CKWORD*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetFaceIndices = _create_bmap_func('BMMesh_GetFaceIndices', [bm_void_p, bm_CKID, bm_CKWORD_pp])
|
||||
## BMMesh_GetFaceMaterialSlotIndexs
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mem[out] Type: LibCmo::CKWORD*. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetFaceMaterialSlotIndexs = _create_bmap_func('BMMesh_GetFaceMaterialSlotIndexs', [bm_void_p, bm_CKID, bm_CKWORD_pp])
|
||||
## BMMesh_GetMaterialSlotCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetMaterialSlotCount = _create_bmap_func('BMMesh_GetMaterialSlotCount', [bm_void_p, bm_CKID, bm_CKDWORD_p])
|
||||
## BMMesh_SetMaterialSlotCount
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param count[in] Type: LibCmo::CKDWORD.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_SetMaterialSlotCount = _create_bmap_func('BMMesh_SetMaterialSlotCount', [bm_void_p, bm_CKID, bm_CKDWORD])
|
||||
## BMMesh_GetMaterialSlot
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param index[in] Type: LibCmo::CKDWORD.
|
||||
# @param out_mtlid[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_GetMaterialSlot = _create_bmap_func('BMMesh_GetMaterialSlot', [bm_void_p, bm_CKID, bm_CKDWORD, bm_CKID_p])
|
||||
## BMMesh_SetMaterialSlot
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param index[in] Type: LibCmo::CKDWORD.
|
||||
# @param mtlid[in] Type: LibCmo::CK2::CK_ID.
|
||||
# @return True if no error, otherwise False.
|
||||
BMMesh_SetMaterialSlot = _create_bmap_func('BMMesh_SetMaterialSlot', [bm_void_p, bm_CKID, bm_CKDWORD, bm_CKID])
|
||||
## BM3dObject_GetWorldMatrix
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_mat[out] Type: LibCmo::VxMath::VxMatrix. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BM3dObject_GetWorldMatrix = _create_bmap_func('BM3dObject_GetWorldMatrix', [bm_void_p, bm_CKID, bm_VxMatrix_p])
|
||||
## BM3dObject_SetWorldMatrix
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param mat[in] Type: LibCmo::VxMath::VxMatrix.
|
||||
# @return True if no error, otherwise False.
|
||||
BM3dObject_SetWorldMatrix = _create_bmap_func('BM3dObject_SetWorldMatrix', [bm_void_p, bm_CKID, bm_VxMatrix])
|
||||
## BM3dObject_GetCurrentMesh
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_meshid[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BM3dObject_GetCurrentMesh = _create_bmap_func('BM3dObject_GetCurrentMesh', [bm_void_p, bm_CKID, bm_CKID_p])
|
||||
## BM3dObject_SetCurrentMesh
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param meshid[in] Type: LibCmo::CK2::CK_ID.
|
||||
# @return True if no error, otherwise False.
|
||||
BM3dObject_SetCurrentMesh = _create_bmap_func('BM3dObject_SetCurrentMesh', [bm_void_p, bm_CKID, bm_CKID])
|
||||
## BM3dObject_GetVisibility
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param out_isVisible[out] Type: bool. Use ctypes.byref(data) pass it.
|
||||
# @return True if no error, otherwise False.
|
||||
BM3dObject_GetVisibility = _create_bmap_func('BM3dObject_GetVisibility', [bm_void_p, bm_CKID, bm_bool_p])
|
||||
## BM3dObject_SetVisibility
|
||||
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
|
||||
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
|
||||
# @param is_visible[in] Type: bool.
|
||||
# @return True if no error, otherwise False.
|
||||
BM3dObject_SetVisibility = _create_bmap_func('BM3dObject_SetVisibility', [bm_void_p, bm_CKID, bm_bool])
|
||||
|
||||
#endregion
|
||||
|
@ -1,865 +0,0 @@
|
||||
import ctypes, typing, atexit
|
||||
from . import bmap, virtools_types
|
||||
|
||||
#region Basic Class & Constant Defines
|
||||
|
||||
g_InvalidPtr: bmap.bm_void_p = bmap.bm_void_p(0)
|
||||
g_InvalidCKID: int = 0
|
||||
g_BMapEncoding: str = "utf-8"
|
||||
|
||||
def _python_callback(strl: bytes):
|
||||
"""
|
||||
The Python type callback for BMFile.
|
||||
Simply add a prefix when output.
|
||||
Need a convertion before passing to BMFile.
|
||||
"""
|
||||
# the passing value is bytes, not bmap.bm_CKSTRING.
|
||||
# i think Python do a auto convertion here.
|
||||
if strl is not None:
|
||||
print(f'[PyBMap] {strl.decode(g_BMapEncoding)}')
|
||||
_g_RawCallback: bmap.bm_callback = bmap.bm_callback(_python_callback)
|
||||
|
||||
class _AbstractPointer():
|
||||
__mRawPointer: int
|
||||
|
||||
def __init__(self, raw_pointer: bmap.bm_void_p):
|
||||
self._set_pointer(raw_pointer)
|
||||
|
||||
def _is_valid(self) -> bool:
|
||||
return self.__mRawPointer != 0
|
||||
|
||||
def _get_pointer(self) -> bmap.bm_void_p:
|
||||
return bmap.bm_void_p(self.__mRawPointer)
|
||||
|
||||
def _set_pointer(self, raw_pointer: bmap.bm_void_p):
|
||||
if raw_pointer.value is None:
|
||||
self.__mRawPointer = 0
|
||||
else:
|
||||
self.__mRawPointer = raw_pointer.value
|
||||
|
||||
def __eq__(self, obj: object) -> bool:
|
||||
if isinstance(obj, self.__class__):
|
||||
return obj.__mRawPointer == self.__mRawPointer
|
||||
else:
|
||||
return False
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self.__mRawPointer)
|
||||
|
||||
class _AbstractCKObject(_AbstractPointer):
|
||||
__mCKID: int
|
||||
|
||||
def __init__(self, raw_pointer: bmap.bm_void_p, ckid: bmap.bm_CKID):
|
||||
_AbstractPointer.__init__(self, raw_pointer)
|
||||
self.__mCKID = ckid.value
|
||||
|
||||
def _is_valid(self) -> bool:
|
||||
return _AbstractPointer._is_valid(self) and self.__mCKID != 0
|
||||
|
||||
def _get_ckid(self) -> bmap.bm_CKID:
|
||||
return bmap.bm_CKID(self.__mCKID)
|
||||
|
||||
def __eq__(self, obj: object) -> bool:
|
||||
if not _AbstractPointer.__eq__(self, obj): return False
|
||||
|
||||
if isinstance(obj, self.__class__):
|
||||
return obj.__mCKID == self.__mCKID
|
||||
else:
|
||||
return False
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash((_AbstractPointer.__hash__(self), self.__mCKID))
|
||||
|
||||
#endregion
|
||||
|
||||
#region Help Function & Type Define
|
||||
|
||||
TCKObj = typing.TypeVar('TCKObj', bound = _AbstractCKObject)
|
||||
|
||||
def _vxvector3_assigner(pvector: bmap.bm_VxVector3_p, count: int, itor: typing.Iterator[virtools_types.VxVector3]) -> None:
|
||||
pfloat: bmap.bm_CKFLOAT_p = ctypes.cast(pvector, bmap.bm_CKFLOAT_p)
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
uservector: virtools_types.VxVector3 = next(itor)
|
||||
pfloat[idx] = uservector.x
|
||||
pfloat[idx + 1] = uservector.y
|
||||
pfloat[idx + 2] = uservector.z
|
||||
idx += 3
|
||||
|
||||
def _vxvector3_iterator(pvector: bmap.bm_VxVector3_p, count: int) -> typing.Iterator[virtools_types.VxVector3]:
|
||||
ret: virtools_types.VxVector3 = virtools_types.VxVector3()
|
||||
pfloat: bmap.bm_CKFLOAT_p = ctypes.cast(pvector, bmap.bm_CKFLOAT_p)
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
ret.x = pfloat[idx]
|
||||
ret.y = pfloat[idx + 1]
|
||||
ret.z = pfloat[idx + 2]
|
||||
idx += 3
|
||||
yield ret
|
||||
|
||||
def _vxvector2_assigner(pvector: bmap.bm_VxVector2_p, count: int, itor: typing.Iterator[virtools_types.VxVector2]) -> None:
|
||||
pfloat: bmap.bm_CKFLOAT_p = ctypes.cast(pvector, bmap.bm_CKFLOAT_p)
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
uservector: virtools_types.VxVector2 = next(itor)
|
||||
pfloat[idx] = uservector.x
|
||||
pfloat[idx + 1] = uservector.y
|
||||
idx += 2
|
||||
|
||||
def _vxvector2_iterator(pvector: bmap.bm_VxVector2_p, count: int) -> typing.Iterator[virtools_types.VxVector2]:
|
||||
ret: virtools_types.VxVector2 = virtools_types.VxVector2()
|
||||
pfloat: bmap.bm_CKFLOAT_p = ctypes.cast(pvector, bmap.bm_CKFLOAT_p)
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
ret.x = pfloat[idx]
|
||||
ret.y = pfloat[idx + 1]
|
||||
idx += 2
|
||||
yield ret
|
||||
|
||||
# bmap.bm_CKWORD_p | bmap.bm_CKDWORD_p is just a type hint
|
||||
# wo do not need distinguish them in code.
|
||||
# because the type of pindices is decided by runtime.
|
||||
|
||||
def _ckfaceindices_assigner(pindices: bmap.bm_CKWORD_p | bmap.bm_CKDWORD_p, count: int, itor: typing.Iterator[virtools_types.CKFaceIndices]) -> None:
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
userindices: virtools_types.CKFaceIndices = next(itor)
|
||||
pindices[idx] = userindices.i1
|
||||
pindices[idx + 1] = userindices.i2
|
||||
pindices[idx + 2] = userindices.i3
|
||||
idx += 3
|
||||
|
||||
def _ckfaceindices_iterator(pindices: bmap.bm_CKWORD_p | bmap.bm_CKDWORD_p, count: int) -> typing.Iterator[virtools_types.CKFaceIndices]:
|
||||
ret: virtools_types.CKFaceIndices = virtools_types.CKFaceIndices()
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
ret.i1 = pindices[idx]
|
||||
ret.i2 = pindices[idx + 1]
|
||||
ret.i3 = pindices[idx + 2]
|
||||
idx += 3
|
||||
yield ret
|
||||
|
||||
#endregion
|
||||
|
||||
#region Valid Check, Init and Dispose
|
||||
|
||||
def is_bmap_available() -> bool:
|
||||
return bmap.is_bmap_available()
|
||||
|
||||
# init module self and register exit function
|
||||
if is_bmap_available():
|
||||
bmap.BMInit()
|
||||
|
||||
def _auto_exit():
|
||||
bmap.BMDispose()
|
||||
atexit.register(_auto_exit)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Real Type Defines
|
||||
|
||||
"""!
|
||||
@remark
|
||||
BMFileReader, BMFileWriter, and BMMeshTrans can be create by given constructor.
|
||||
But they must be destroyed by calling dispose(). Otherwise it may cause memory leak.
|
||||
You also can use python `with` statement to achieve this automatically.
|
||||
|
||||
BMObject, BMTexture, BMMaterial, BMMesh, and BM3dObject should NOT be constructed from given constructor.
|
||||
They must be obtained from BMFileReader, BMFileWriter, and BMMeshTrans.
|
||||
Thus BMObject, BMTexture, BMMaterial, BMMesh, and BM3dObject also do not need to free
|
||||
because these resources are sotred in BMFileReader, BMFileWriter, and BMMeshTrans.
|
||||
We just provide them as a visitor.
|
||||
"""
|
||||
|
||||
class BMObject(_AbstractCKObject):
|
||||
def get_name(self) -> str | None:
|
||||
name: bmap.bm_CKSTRING = bmap.bm_CKSTRING()
|
||||
bmap.BMObject_GetName(self._get_pointer(), self._get_ckid(), ctypes.byref(name))
|
||||
if name.value is None:
|
||||
return None
|
||||
else:
|
||||
return name.value.decode(g_BMapEncoding)
|
||||
|
||||
def set_name(self, name_: str | None) -> None:
|
||||
name: bmap.bm_CKSTRING
|
||||
if name_ is None:
|
||||
name = bmap.bm_CKSTRING(0)
|
||||
else:
|
||||
name = bmap.bm_CKSTRING(name_.encode(g_BMapEncoding))
|
||||
bmap.BMObject_SetName(self._get_pointer(), self._get_ckid(), name)
|
||||
|
||||
class BMTexture(BMObject):
|
||||
def get_file_name(self) -> str | None:
|
||||
filename: bmap.bm_CKSTRING = bmap.bm_CKSTRING()
|
||||
bmap.BMTexture_GetFileName(self._get_pointer(), self._get_ckid(), ctypes.byref(filename))
|
||||
if filename.value is None:
|
||||
return None
|
||||
else:
|
||||
return filename.value.decode(g_BMapEncoding)
|
||||
|
||||
def load_image(self, filepath: str) -> None:
|
||||
filename: bmap.bm_CKSTRING = bmap.bm_CKSTRING(filepath.encode(g_BMapEncoding))
|
||||
bmap.BMTexture_LoadImage(self._get_pointer(), self._get_ckid(), filename)
|
||||
|
||||
def save_image(self, filepath: str) -> None:
|
||||
filename: bmap.bm_CKSTRING = bmap.bm_CKSTRING(filepath.encode(g_BMapEncoding))
|
||||
bmap.BMTexture_SaveImage(self._get_pointer(), self._get_ckid(), filename)
|
||||
|
||||
def get_save_options(self) -> virtools_types.CK_TEXTURE_SAVEOPTIONS:
|
||||
opt: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMTexture_GetSaveOptions(self._get_pointer(), self._get_ckid(), ctypes.byref(opt))
|
||||
return virtools_types.CK_TEXTURE_SAVEOPTIONS(opt.value)
|
||||
|
||||
def set_save_options(self, opt_: virtools_types.CK_TEXTURE_SAVEOPTIONS) -> None:
|
||||
opt: bmap.bm_enum = bmap.bm_enum(opt_.value)
|
||||
bmap.BMTexture_SetSaveOptions(self._get_pointer(), self._get_ckid(), opt)
|
||||
|
||||
def get_video_format(self) -> virtools_types.VX_PIXELFORMAT:
|
||||
fmt: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMTexture_GetVideoFormat(self._get_pointer(), self._get_ckid(), ctypes.byref(fmt))
|
||||
return virtools_types.VX_PIXELFORMAT(fmt.value)
|
||||
|
||||
def set_video_format(self, fmt_: virtools_types.VX_PIXELFORMAT) -> None:
|
||||
fmt: bmap.bm_enum = bmap.bm_enum(fmt_.value)
|
||||
bmap.BMTexture_SetVideoFormat(self._get_pointer(), self._get_ckid(), fmt)
|
||||
|
||||
class BMMaterial(BMObject):
|
||||
def _set_vxcolor(self,
|
||||
setter_: typing.Callable[[bmap.bm_void_p, bmap.bm_CKID, bmap.bm_VxColor], bool],
|
||||
col_: virtools_types.VxColor) -> None:
|
||||
# set to raw color
|
||||
col: bmap.bm_VxColor = bmap.bm_VxColor()
|
||||
col.r = col_.r
|
||||
col.g = col_.g
|
||||
col.b = col_.b
|
||||
col.a = col_.a
|
||||
# assign
|
||||
setter_(self._get_pointer(), self._get_ckid(), col)
|
||||
def _get_vxcolor(self,
|
||||
getter_: typing.Callable[[bmap.bm_void_p, bmap.bm_CKID, bmap.bm_VxColor_p], bool]) -> virtools_types.VxColor:
|
||||
# get raw color
|
||||
col: bmap.bm_VxColor = bmap.bm_VxColor()
|
||||
getter_(self._get_pointer(), self._get_ckid(), ctypes.byref(col))
|
||||
# get from raw color
|
||||
ret: virtools_types.VxColor = virtools_types.VxColor()
|
||||
ret.r = col.r
|
||||
ret.g = col.g
|
||||
ret.b = col.b
|
||||
ret.a = col.a
|
||||
return ret
|
||||
|
||||
def get_diffuse(self) -> virtools_types.VxColor:
|
||||
return self._get_vxcolor(bmap.BMMaterial_GetDiffuse)
|
||||
def set_diffuse(self, col: virtools_types.VxColor) -> None:
|
||||
self._set_vxcolor(bmap.BMMaterial_SetDiffuse, col)
|
||||
def get_ambient(self) -> virtools_types.VxColor:
|
||||
return self._get_vxcolor(bmap.BMMaterial_GetAmbient)
|
||||
def set_ambient(self, col: virtools_types.VxColor) -> None:
|
||||
self._set_vxcolor(bmap.BMMaterial_SetAmbient, col)
|
||||
def get_specular(self) -> virtools_types.VxColor:
|
||||
return self._get_vxcolor(bmap.BMMaterial_GetSpecular)
|
||||
def set_specular(self, col: virtools_types.VxColor) -> None:
|
||||
self._set_vxcolor(bmap.BMMaterial_SetSpecular, col)
|
||||
def get_emissive(self) -> virtools_types.VxColor:
|
||||
return self._get_vxcolor(bmap.BMMaterial_GetEmissive)
|
||||
def set_emissive(self, col: virtools_types.VxColor) -> None:
|
||||
self._set_vxcolor(bmap.BMMaterial_SetEmissive, col)
|
||||
|
||||
def get_specular_power(self) -> float:
|
||||
power: bmap.bm_CKFLOAT = bmap.bm_CKFLOAT()
|
||||
bmap.BMMaterial_GetSpecularPower(self._get_pointer(), self._get_ckid(), ctypes.byref(power))
|
||||
return power.value
|
||||
def set_specular_power(self, power_: float) -> None:
|
||||
power: bmap.bm_CKFLOAT = bmap.bm_CKFLOAT(power_)
|
||||
bmap.BMMaterial_SetSpecularPower(self._get_pointer(), self._get_ckid(), power)
|
||||
|
||||
def get_texture(self) -> BMTexture | None:
|
||||
objid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
bmap.BMMaterial_GetTexture(self._get_pointer(), self._get_ckid(), ctypes.byref(objid))
|
||||
if objid.value == g_InvalidCKID:
|
||||
return None
|
||||
else:
|
||||
return BMTexture(self._get_pointer(), objid)
|
||||
|
||||
def set_texture(self, tex_: BMTexture | None) -> None:
|
||||
objid: bmap.bm_CKID = bmap.bm_CKID(g_InvalidCKID)
|
||||
if tex_ is not None:
|
||||
objid = tex_._get_ckid()
|
||||
bmap.BMMaterial_SetTexture(self._get_pointer(), self._get_ckid(), objid)
|
||||
|
||||
def get_texture_border_color(self) -> virtools_types.VxColor:
|
||||
col: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
bmap.BMMaterial_GetTextureBorderColor(self._get_pointer(), self._get_ckid(), ctypes.byref(col))
|
||||
ret: virtools_types.VxColor = virtools_types.VxColor()
|
||||
ret.from_dword(col.value)
|
||||
return ret
|
||||
def set_texture_border_color(self, col_: virtools_types.VxColor) -> None:
|
||||
col: bmap.bm_CKDWORD = bmap.bm_CKDWORD(col_.to_dword())
|
||||
bmap.BMMaterial_SetTextureBorderColor(self._get_pointer(), self._get_ckid(), col)
|
||||
|
||||
def get_texture_blend_mode(self) -> virtools_types.VXTEXTURE_BLENDMODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetTextureBlendMode(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXTEXTURE_BLENDMODE(data.value)
|
||||
def set_texture_blend_mode(self, data_: virtools_types.VXTEXTURE_BLENDMODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetTextureBlendMode(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_texture_min_mode(self) -> virtools_types.VXTEXTURE_FILTERMODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetTextureMinMode(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXTEXTURE_FILTERMODE(data.value)
|
||||
def set_texture_min_mode(self, data_: virtools_types.VXTEXTURE_FILTERMODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetTextureMinMode(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_texture_mag_mode(self) -> virtools_types.VXTEXTURE_FILTERMODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetTextureMagMode(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXTEXTURE_FILTERMODE(data.value)
|
||||
def set_texture_mag_mode(self, data_: virtools_types.VXTEXTURE_FILTERMODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetTextureMagMode(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_texture_address_mode(self) -> virtools_types.VXTEXTURE_ADDRESSMODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetTextureAddressMode(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXTEXTURE_ADDRESSMODE(data.value)
|
||||
def set_texture_address_mode(self, data_: virtools_types.VXTEXTURE_ADDRESSMODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetTextureAddressMode(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_source_blend(self) -> virtools_types.VXBLEND_MODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetSourceBlend(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXBLEND_MODE(data.value)
|
||||
def set_source_blend(self, data_: virtools_types.VXBLEND_MODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetSourceBlend(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_dest_blend(self) -> virtools_types.VXBLEND_MODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetDestBlend(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXBLEND_MODE(data.value)
|
||||
def set_dest_blend(self, data_: virtools_types.VXBLEND_MODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetDestBlend(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_fill_mode(self) -> virtools_types.VXFILL_MODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetFillMode(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXFILL_MODE(data.value)
|
||||
def set_fill_mode(self, data_: virtools_types.VXFILL_MODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetFillMode(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_shade_mode(self) -> virtools_types.VXSHADE_MODE:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetShadeMode(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXSHADE_MODE(data.value)
|
||||
def set_shade_mode(self, data_: virtools_types.VXSHADE_MODE) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetShadeMode(self._get_pointer(), self._get_ckid(), data)
|
||||
|
||||
def get_alpha_test_enabled(self) -> bool:
|
||||
data: bmap.bm_bool = bmap.bm_bool()
|
||||
bmap.BMMaterial_GetAlphaTestEnabled(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return data.value
|
||||
def set_alpha_test_enabled(self, data_: bool) -> None:
|
||||
data: bmap.bm_bool = bmap.bm_bool(data_)
|
||||
bmap.BMMaterial_SetAlphaTestEnabled(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_alpha_blend_enabled(self) -> bool:
|
||||
data: bmap.bm_bool = bmap.bm_bool()
|
||||
bmap.BMMaterial_GetAlphaBlendEnabled(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return data.value
|
||||
def set_alpha_blend_enabled(self, data_: bool) -> None:
|
||||
data: bmap.bm_bool = bmap.bm_bool(data_)
|
||||
bmap.BMMaterial_SetAlphaBlendEnabled(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_perspective_correction_enabled(self) -> bool:
|
||||
data: bmap.bm_bool = bmap.bm_bool()
|
||||
bmap.BMMaterial_GetPerspectiveCorrectionEnabled(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return data.value
|
||||
def set_perspective_correction_enabled(self, data_: bool) -> None:
|
||||
data: bmap.bm_bool = bmap.bm_bool(data_)
|
||||
bmap.BMMaterial_SetPerspectiveCorrectionEnabled(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_z_write_enabled(self) -> bool:
|
||||
data: bmap.bm_bool = bmap.bm_bool()
|
||||
bmap.BMMaterial_GetZWriteEnabled(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return data.value
|
||||
def set_z_write_enabled(self, data_: bool) -> None:
|
||||
data: bmap.bm_bool = bmap.bm_bool(data_)
|
||||
bmap.BMMaterial_SetZWriteEnabled(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_two_sided_enabled(self) -> bool:
|
||||
data: bmap.bm_bool = bmap.bm_bool()
|
||||
bmap.BMMaterial_GetTwoSidedEnabled(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return data.value
|
||||
def set_two_sided_enabled(self, data_: bool) -> None:
|
||||
data: bmap.bm_bool = bmap.bm_bool(data_)
|
||||
bmap.BMMaterial_SetTwoSidedEnabled(self._get_pointer(), self._get_ckid(), data)
|
||||
|
||||
def get_alpha_ref(self) -> int:
|
||||
data: bmap.bm_CKBYTE = bmap.bm_CKBYTE()
|
||||
bmap.BMMaterial_GetAlphaRef(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return data.value
|
||||
def set_alpha_ref(self, data_: int):
|
||||
data: bmap.bm_CKBYTE = bmap.bm_CKBYTE(data_)
|
||||
bmap.BMMaterial_SetAlphaRef(self._get_pointer(), self._get_ckid(), data)
|
||||
|
||||
def get_alpha_func(self) -> virtools_types.VXCMPFUNC:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetAlphaFunc(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXCMPFUNC(data.value)
|
||||
def set_alpha_func(self, data_: virtools_types.VXCMPFUNC) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetAlphaFunc(self._get_pointer(), self._get_ckid(), data)
|
||||
def get_z_func(self) -> virtools_types.VXCMPFUNC:
|
||||
data: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMaterial_GetZFunc(self._get_pointer(), self._get_ckid(), ctypes.byref(data))
|
||||
return virtools_types.VXCMPFUNC(data.value)
|
||||
def set_z_func(self, data_: virtools_types.VXCMPFUNC) -> None:
|
||||
data: bmap.bm_enum = bmap.bm_enum(data_.value)
|
||||
bmap.BMMaterial_SetZFunc(self._get_pointer(), self._get_ckid(), data)
|
||||
|
||||
class BMMesh(BMObject):
|
||||
|
||||
def get_lit_mode(self) -> virtools_types.VXMESH_LITMODE:
|
||||
mode: bmap.bm_enum = bmap.bm_enum()
|
||||
bmap.BMMesh_GetLitMode(self._get_pointer(), self._get_ckid(), ctypes.byref(mode))
|
||||
return virtools_types.VXMESH_LITMODE(mode.value)
|
||||
|
||||
def set_lit_mode(self, mode_: virtools_types.VXMESH_LITMODE) -> None:
|
||||
mode: bmap.bm_enum = bmap.bm_enum(mode_.value)
|
||||
bmap.BMMesh_SetLitMode(self._get_pointer(), self._get_ckid(), mode)
|
||||
|
||||
def get_vertex_count(self) -> int:
|
||||
count: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
bmap.BMMesh_GetVertexCount(self._get_pointer(), self._get_ckid(), ctypes.byref(count))
|
||||
return count.value
|
||||
|
||||
def set_vertex_count(self, count_: int) -> None:
|
||||
count: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count_)
|
||||
bmap.BMMesh_SetVertexCount(self._get_pointer(), self._get_ckid(), count)
|
||||
|
||||
def get_vertex_positions(self) -> typing.Iterator[virtools_types.VxVector3]:
|
||||
# get raw pointer and return
|
||||
raw_vector: bmap.bm_VxVector3_p = bmap.bm_VxVector3_p()
|
||||
bmap.BMMesh_GetVertexPositions(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_vector))
|
||||
return _vxvector3_iterator(raw_vector, self.get_vertex_count())
|
||||
|
||||
def set_vertex_positions(self, itor: typing.Iterator[virtools_types.VxVector3]) -> None:
|
||||
# get raw float pointer and assign
|
||||
raw_vector: bmap.bm_VxVector3_p = bmap.bm_VxVector3_p()
|
||||
bmap.BMMesh_GetVertexPositions(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_vector))
|
||||
_vxvector3_assigner(raw_vector, self.get_vertex_count(), itor)
|
||||
|
||||
def get_vertex_normals(self) -> typing.Iterator[virtools_types.VxVector3]:
|
||||
raw_vector: bmap.bm_VxVector3_p = bmap.bm_VxVector3_p()
|
||||
bmap.BMMesh_GetVertexNormals(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_vector))
|
||||
return _vxvector3_iterator(raw_vector, self.get_vertex_count())
|
||||
|
||||
def set_vertex_normals(self, itor: typing.Iterator[virtools_types.VxVector3]) -> None:
|
||||
raw_vector: bmap.bm_VxVector3_p = bmap.bm_VxVector3_p()
|
||||
bmap.BMMesh_GetVertexNormals(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_vector))
|
||||
_vxvector3_assigner(raw_vector, self.get_vertex_count(), itor)
|
||||
|
||||
def get_vertex_uvs(self) -> typing.Iterator[virtools_types.VxVector2]:
|
||||
raw_vector: bmap.bm_VxVector2_p = bmap.bm_VxVector2_p()
|
||||
bmap.BMMesh_GetVertexUVs(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_vector))
|
||||
return _vxvector2_iterator(raw_vector, self.get_vertex_count())
|
||||
|
||||
def set_vertex_uvs(self, itor: typing.Iterator[virtools_types.VxVector2]) -> None:
|
||||
raw_vector: bmap.bm_VxVector2_p = bmap.bm_VxVector2_p()
|
||||
bmap.BMMesh_GetVertexUVs(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_vector))
|
||||
_vxvector2_assigner(raw_vector, self.get_vertex_count(), itor)
|
||||
|
||||
def get_face_count(self) -> int:
|
||||
count: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
bmap.BMMesh_GetFaceCount(self._get_pointer(), self._get_ckid(), ctypes.byref(count))
|
||||
return count.value
|
||||
|
||||
def set_face_count(self, count_: int) -> None:
|
||||
count: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count_)
|
||||
bmap.BMMesh_SetFaceCount(self._get_pointer(), self._get_ckid(), count)
|
||||
|
||||
def get_face_indices(self) -> typing.Iterator[virtools_types.CKFaceIndices]:
|
||||
raw_idx: bmap.bm_CKWORD_p = bmap.bm_CKWORD_p()
|
||||
bmap.BMMesh_GetFaceIndices(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_idx))
|
||||
return _ckfaceindices_iterator(raw_idx, self.get_face_count())
|
||||
|
||||
def set_face_indices(self, itor: typing.Iterator[virtools_types.CKFaceIndices]) -> None:
|
||||
raw_idx: bmap.bm_CKWORD_p = bmap.bm_CKWORD_p()
|
||||
bmap.BMMesh_GetFaceIndices(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_idx))
|
||||
_ckfaceindices_assigner(raw_idx, self.get_face_count(), itor)
|
||||
|
||||
def get_face_material_slot_indexs(self) -> typing.Iterator[int]:
|
||||
raw_idx: bmap.bm_CKWORD_p = bmap.bm_CKWORD_p()
|
||||
bmap.BMMesh_GetFaceMaterialSlotIndexs(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_idx))
|
||||
for i in range(self.get_face_count()):
|
||||
yield raw_idx[i]
|
||||
|
||||
def set_face_material_slot_indexs(self, itor: typing.Iterator[int]) -> None:
|
||||
raw_idx: bmap.bm_CKWORD_p = bmap.bm_CKWORD_p()
|
||||
bmap.BMMesh_GetFaceMaterialSlotIndexs(self._get_pointer(), self._get_ckid(), ctypes.byref(raw_idx))
|
||||
for i in range(self.get_face_count()):
|
||||
raw_idx[i] = next(itor)
|
||||
|
||||
def get_material_slot_count(self) -> int:
|
||||
count: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
bmap.BMMesh_GetMaterialSlotCount(self._get_pointer(), self._get_ckid(), ctypes.byref(count))
|
||||
return count.value
|
||||
|
||||
def set_material_slot_count(self, count_: int) -> None:
|
||||
count: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count_)
|
||||
bmap.BMMesh_SetMaterialSlotCount(self._get_pointer(), self._get_ckid(), count)
|
||||
|
||||
def get_material_slots(self) -> typing.Iterator[BMMaterial | None]:
|
||||
idx: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
mtlid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
for i in range(self.get_material_slot_count()):
|
||||
idx.value = i
|
||||
bmap.BMMesh_GetMaterialSlot(self._get_pointer(), self._get_ckid(), idx, ctypes.byref(mtlid))
|
||||
if mtlid.value == g_InvalidCKID:
|
||||
yield None
|
||||
else:
|
||||
yield BMMaterial(self._get_pointer(), mtlid)
|
||||
|
||||
def set_material_slots(self, itor: typing.Iterator[BMMaterial | None]) -> None:
|
||||
idx: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
mtlid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
for i in range(self.get_material_slot_count()):
|
||||
idx.value = i
|
||||
# analyze mtl item
|
||||
mtlobj: BMMaterial | None = next(itor)
|
||||
if mtlobj is None:
|
||||
mtlid.value = g_InvalidCKID
|
||||
else:
|
||||
mtlid = mtlobj._get_ckid()
|
||||
# set
|
||||
bmap.BMMesh_SetMaterialSlot(self._get_pointer(), self._get_ckid(), idx, mtlid)
|
||||
|
||||
class BM3dObject(BMObject):
|
||||
def get_world_matrix(self) -> virtools_types.VxMatrix:
|
||||
mat: bmap.bm_VxMatrix = bmap.bm_VxMatrix()
|
||||
bmap.BM3dObject_GetWorldMatrix(self._get_pointer(), self._get_ckid(), ctypes.byref(mat))
|
||||
# use cast & pointer to get matrix data conveniently
|
||||
flat: bmap.bm_CKFLOAT_p = ctypes.cast(ctypes.byref(mat), bmap.bm_CKFLOAT_p)
|
||||
ret: virtools_types.VxMatrix = virtools_types.VxMatrix()
|
||||
ret.from_const(tuple(flat[i] for i in range(16)))
|
||||
return ret
|
||||
|
||||
def set_world_matrix(self, mat_: virtools_types.VxMatrix) -> None:
|
||||
# star syntax expand the tuple as the argument.
|
||||
mat: bmap.bm_VxMatrix = bmap.bm_VxMatrix(*(mat_.to_const()))
|
||||
bmap.BM3dObject_SetWorldMatrix(self._get_pointer(), self._get_ckid(), mat)
|
||||
|
||||
def get_current_mesh(self) -> BMMesh | None:
|
||||
ckid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
bmap.BM3dObject_GetCurrentMesh(self._get_pointer(), self._get_ckid(), ctypes.byref(ckid))
|
||||
if ckid.value == g_InvalidCKID:
|
||||
return None
|
||||
else:
|
||||
return BMMesh(self._get_pointer(), ckid)
|
||||
|
||||
def set_current_mesh(self, mesh: BMMesh | None) -> None:
|
||||
ckid: bmap.bm_CKID = bmap.bm_CKID(g_InvalidCKID)
|
||||
if mesh is not None:
|
||||
ckid = mesh._get_ckid()
|
||||
bmap.BM3dObject_SetCurrentMesh(self._get_pointer(), self._get_ckid(), ckid)
|
||||
|
||||
def get_visibility(self) -> bool:
|
||||
visb: bmap.bm_bool = bmap.bm_bool()
|
||||
bmap.BM3dObject_GetVisibility(self._get_pointer(), self._get_ckid(), ctypes.byref(visb))
|
||||
return visb.value
|
||||
|
||||
def set_visibility(self, visb_: bool) -> None:
|
||||
visb: bmap.bm_bool = bmap.bm_bool(visb_)
|
||||
bmap.BM3dObject_SetVisibility(self._get_pointer(), self._get_ckid(), visb)
|
||||
|
||||
class BMGroup(BMObject):
|
||||
def add_object(self, member: BM3dObject) -> None:
|
||||
bmap.BMGroup_AddObject(self._get_pointer(), self._get_ckid(), member._get_ckid())
|
||||
|
||||
def get_object_count(self) -> int:
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
bmap.BMGroup_GetObjectCount(self._get_pointer(), self._get_ckid(), ctypes.byref(csize))
|
||||
return csize.value
|
||||
|
||||
def get_objects(self) -> typing.Iterator[BM3dObject]:
|
||||
csize: int = self.get_object_count()
|
||||
|
||||
# iterate list
|
||||
cidx: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
retid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
for i in range(csize):
|
||||
cidx.value = i
|
||||
bmap.BMGroup_GetObject(self._get_pointer(), self._get_ckid(), cidx, ctypes.byref(retid))
|
||||
# return visitor
|
||||
yield BM3dObject(self._get_pointer(), retid)
|
||||
|
||||
class BMFileReader(_AbstractPointer):
|
||||
def __init__(self, file_name_: str, temp_folder_: str, texture_folder_: str, encodings_: tuple[str]):
|
||||
# create param
|
||||
file_name: bmap.bm_CKSTRING = bmap.bm_CKSTRING(file_name_.encode(g_BMapEncoding))
|
||||
temp_folder: bmap.bm_CKSTRING = bmap.bm_CKSTRING(temp_folder_.encode(g_BMapEncoding))
|
||||
texture_folder: bmap.bm_CKSTRING = bmap.bm_CKSTRING(texture_folder_.encode(g_BMapEncoding))
|
||||
encoding_count: bmap.bm_CKDWORD = bmap.bm_CKDWORD(len(encodings_))
|
||||
encodings: ctypes.Array = (bmap.bm_CKSTRING * len(encodings_))(
|
||||
*(strl.encode(g_BMapEncoding) for strl in encodings_)
|
||||
)
|
||||
out_file: bmap.bm_void_p = bmap.bm_void_p()
|
||||
# exec
|
||||
bmap.BMFile_Load(
|
||||
file_name, temp_folder, texture_folder, _g_RawCallback,
|
||||
encoding_count, encodings,
|
||||
ctypes.byref(out_file)
|
||||
)
|
||||
# init self
|
||||
_AbstractPointer.__init__(self, out_file)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.dispose()
|
||||
|
||||
def dispose(self) -> None:
|
||||
if self._is_valid():
|
||||
bmap.BMFile_Free(self._get_pointer())
|
||||
self._set_pointer(g_InvalidPtr)
|
||||
|
||||
def __get_ckobject_count(self,
|
||||
count_getter: typing.Callable[[bmap.bm_void_p, bmap.bm_CKDWORD_p], bool]) -> int:
|
||||
# get size
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
count_getter(self._get_pointer(), ctypes.byref(csize))
|
||||
return csize.value
|
||||
|
||||
def __get_ckobjects(self,
|
||||
class_type: type[TCKObj],
|
||||
count_getter: typing.Callable[[bmap.bm_void_p, bmap.bm_CKDWORD_p], bool],
|
||||
obj_getter: typing.Callable[[bmap.bm_void_p, bmap.bm_CKDWORD, bmap.bm_CKID_p], bool]) -> typing.Iterator[TCKObj]:
|
||||
# get size first
|
||||
csize: int = self.__get_ckobject_count(count_getter)
|
||||
|
||||
# iterate list
|
||||
cidx: bmap.bm_CKDWORD = bmap.bm_CKDWORD()
|
||||
retid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
for i in range(csize):
|
||||
cidx.value = i
|
||||
obj_getter(self._get_pointer(), cidx, ctypes.byref(retid))
|
||||
# yield return constructed obj visitor
|
||||
yield class_type(self._get_pointer(), retid)
|
||||
|
||||
def get_texture_count(self) -> int:
|
||||
return self.__get_ckobject_count(bmap.BMFile_GetTextureCount)
|
||||
def get_textures(self) -> typing.Iterator[BMTexture]:
|
||||
return self.__get_ckobjects(
|
||||
BMTexture,
|
||||
bmap.BMFile_GetTextureCount,
|
||||
bmap.BMFile_GetTexture
|
||||
)
|
||||
|
||||
def get_material_count(self) -> int:
|
||||
return self.__get_ckobject_count(bmap.BMFile_GetMaterialCount)
|
||||
def get_materials(self) -> typing.Iterator[BMMaterial]:
|
||||
return self.__get_ckobjects(
|
||||
BMMaterial,
|
||||
bmap.BMFile_GetMaterialCount,
|
||||
bmap.BMFile_GetMaterial
|
||||
)
|
||||
|
||||
def get_mesh_count(self) -> int:
|
||||
return self.__get_ckobject_count(bmap.BMFile_GetMeshCount)
|
||||
def get_meshs(self) -> typing.Iterator[BMMesh]:
|
||||
return self.__get_ckobjects(
|
||||
BMMesh,
|
||||
bmap.BMFile_GetMeshCount,
|
||||
bmap.BMFile_GetMesh
|
||||
)
|
||||
|
||||
def get_3dobject_count(self) -> int:
|
||||
return self.__get_ckobject_count(bmap.BMFile_Get3dObjectCount)
|
||||
def get_3dobjects(self) -> typing.Iterator[BM3dObject]:
|
||||
return self.__get_ckobjects(
|
||||
BM3dObject,
|
||||
bmap.BMFile_Get3dObjectCount,
|
||||
bmap.BMFile_Get3dObject
|
||||
)
|
||||
|
||||
def get_group_count(self) -> int:
|
||||
return self.__get_ckobject_count(bmap.BMFile_GetGroupCount)
|
||||
def get_groups(self) -> typing.Iterator[BMGroup]:
|
||||
return self.__get_ckobjects(
|
||||
BMGroup,
|
||||
bmap.BMFile_GetGroupCount,
|
||||
bmap.BMFile_GetGroup
|
||||
)
|
||||
|
||||
class BMFileWriter(_AbstractPointer):
|
||||
def __init__(self, temp_folder_: str, texture_folder_: str, encodings_: tuple[str]):
|
||||
# create param
|
||||
temp_folder: bmap.bm_CKSTRING = bmap.bm_CKSTRING(temp_folder_.encode(g_BMapEncoding))
|
||||
texture_folder: bmap.bm_CKSTRING = bmap.bm_CKSTRING(texture_folder_.encode(g_BMapEncoding))
|
||||
encoding_count: bmap.bm_CKDWORD = bmap.bm_CKDWORD(len(encodings_))
|
||||
encodings: ctypes.Array = (bmap.bm_CKSTRING * len(encodings_))(
|
||||
*(strl.encode(g_BMapEncoding) for strl in encodings_)
|
||||
)
|
||||
out_file: bmap.bm_void_p = bmap.bm_void_p()
|
||||
# exec
|
||||
bmap.BMFile_Create(
|
||||
temp_folder, texture_folder, _g_RawCallback,
|
||||
encoding_count, encodings,
|
||||
ctypes.byref(out_file)
|
||||
)
|
||||
# init self
|
||||
_AbstractPointer.__init__(self, out_file)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.dispose()
|
||||
|
||||
def save(self, file_name_: str, texture_save_opt_: virtools_types.CK_TEXTURE_SAVEOPTIONS, use_compress_: bool, compress_level_: int) -> None:
|
||||
# create param
|
||||
file_name: bmap.bm_CKSTRING = bmap.bm_CKSTRING(file_name_.encode(g_BMapEncoding))
|
||||
texture_save_opt: bmap.bm_enum = bmap.bm_enum(texture_save_opt_.value)
|
||||
use_compress: bmap.bm_bool = bmap.bm_bool(use_compress_)
|
||||
compress_level: bmap.bm_CKINT = bmap.bm_CKINT(compress_level_)
|
||||
# exec
|
||||
bmap.BMFile_Save(self._get_pointer(), file_name, texture_save_opt, use_compress, compress_level)
|
||||
|
||||
def dispose(self) -> None:
|
||||
if self._is_valid():
|
||||
bmap.BMFile_Free(self._get_pointer())
|
||||
self._set_pointer(g_InvalidPtr)
|
||||
|
||||
def __create_ckobject(self,
|
||||
class_type: type[TCKObj],
|
||||
creator: typing.Callable[[bmap.bm_void_p, bmap.bm_CKID_p], bool]) -> TCKObj:
|
||||
# prepare id container
|
||||
retid: bmap.bm_CKID = bmap.bm_CKID()
|
||||
# create new one
|
||||
creator(self._get_pointer(), ctypes.byref(retid))
|
||||
# return visitor
|
||||
return class_type(self._get_pointer(), retid)
|
||||
|
||||
def create_texture(self) -> BMTexture:
|
||||
return self.__create_ckobject(
|
||||
BMTexture,
|
||||
bmap.BMFile_CreateTexture
|
||||
)
|
||||
|
||||
def create_material(self) -> BMMaterial:
|
||||
return self.__create_ckobject(
|
||||
BMMaterial,
|
||||
bmap.BMFile_CreateMaterial
|
||||
)
|
||||
|
||||
def create_mesh(self) -> BMMesh:
|
||||
return self.__create_ckobject(
|
||||
BMMesh,
|
||||
bmap.BMFile_CreateMesh
|
||||
)
|
||||
|
||||
def create_3dobject(self) -> BM3dObject:
|
||||
return self.__create_ckobject(
|
||||
BM3dObject,
|
||||
bmap.BMFile_Create3dObject
|
||||
)
|
||||
|
||||
def create_group(self) -> BMGroup:
|
||||
return self.__create_ckobject(
|
||||
BMGroup,
|
||||
bmap.BMFile_CreateGroup
|
||||
)
|
||||
|
||||
class BMMeshTrans(_AbstractPointer):
|
||||
def __init__(self):
|
||||
ptr: bmap.bm_void_p = bmap.bm_void_p()
|
||||
bmap.BMMeshTrans_New(ctypes.byref(ptr))
|
||||
_AbstractPointer.__init__(self, ptr)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.dispose()
|
||||
|
||||
def dispose(self) -> None:
|
||||
if self._is_valid():
|
||||
bmap.BMMeshTrans_Delete(self._get_pointer())
|
||||
self._set_pointer(g_InvalidPtr)
|
||||
|
||||
def parse(self, bmfile: BMFileWriter, objmesh: BMMesh) -> None:
|
||||
bmap.BMMeshTrans_Parse(self._get_pointer(), bmfile._get_pointer(), objmesh._get_ckid())
|
||||
|
||||
def prepare_vertex(self, count: int, itor: typing.Iterator[virtools_types.VxVector3]) -> None:
|
||||
# prepare count first
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count)
|
||||
bmap.BMMeshTrans_PrepareVertexCount(self._get_pointer(), csize)
|
||||
# get raw pointer and conv to float ptr for convenient visit
|
||||
raw_vector: bmap.bm_VxVector3_p = bmap.bm_VxVector3_p()
|
||||
bmap.BMMeshTrans_PrepareVertex(self._get_pointer(), ctypes.byref(raw_vector))
|
||||
# set by pointer
|
||||
_vxvector3_assigner(raw_vector, count, itor)
|
||||
|
||||
def prepare_normal(self, count: int, itor: typing.Iterator[virtools_types.VxVector3]) -> None:
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count)
|
||||
bmap.BMMeshTrans_PrepareNormalCount(self._get_pointer(), csize)
|
||||
|
||||
raw_vector: bmap.bm_VxVector3_p = bmap.bm_VxVector3_p()
|
||||
bmap.BMMeshTrans_PrepareNormal(self._get_pointer(), ctypes.byref(raw_vector))
|
||||
|
||||
_vxvector3_assigner(raw_vector, count, itor)
|
||||
|
||||
def prepare_uv(self, count: int, itor: typing.Iterator[virtools_types.VxVector2]) -> None:
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count)
|
||||
bmap.BMMeshTrans_PrepareUVCount(self._get_pointer(), csize)
|
||||
|
||||
raw_vector: bmap.bm_VxVector2_p = bmap.bm_VxVector2_p()
|
||||
bmap.BMMeshTrans_PrepareUV(self._get_pointer(), ctypes.byref(raw_vector))
|
||||
|
||||
_vxvector2_assigner(raw_vector, count, itor)
|
||||
|
||||
def prepare_mtl_slot(self, count: int, itor: typing.Iterator[BMMaterial | None]) -> None:
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count)
|
||||
bmap.BMMeshTrans_PrepareMtlSlotCount(self._get_pointer(), csize)
|
||||
|
||||
raw_ckid: bmap.bm_CKID_p = bmap.bm_CKID_p()
|
||||
bmap.BMMeshTrans_PrepareMtlSlot(self._get_pointer(), ctypes.byref(raw_ckid))
|
||||
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
usermtl: BMMaterial | None = next(itor)
|
||||
if usermtl is None:
|
||||
raw_ckid[idx] = g_InvalidCKID
|
||||
else:
|
||||
raw_ckid[idx] = usermtl._get_ckid().value
|
||||
idx += 1
|
||||
|
||||
def prepare_face(self,
|
||||
count: int,
|
||||
vec_idx: typing.Iterator[virtools_types.CKFaceIndices],
|
||||
nml_idx: typing.Iterator[virtools_types.CKFaceIndices],
|
||||
uv_idx: typing.Iterator[virtools_types.CKFaceIndices],
|
||||
mtl_idx: typing.Iterator[int]) -> None:
|
||||
# prepare face size
|
||||
csize: bmap.bm_CKDWORD = bmap.bm_CKDWORD(count)
|
||||
bmap.BMMeshTrans_PrepareFaceCount(self._get_pointer(), csize)
|
||||
|
||||
# get 4 raw pointer for following assign
|
||||
raw_vec_idx: bmap.bm_CKDWORD_p = bmap.bm_CKDWORD_p()
|
||||
raw_nml_idx: bmap.bm_CKDWORD_p = bmap.bm_CKDWORD_p()
|
||||
raw_uv_idx: bmap.bm_CKDWORD_p = bmap.bm_CKDWORD_p()
|
||||
raw_mtl_idx: bmap.bm_CKDWORD_p = bmap.bm_CKDWORD_p()
|
||||
bmap.BMMeshTrans_PrepareFaceVertexIndices(self._get_pointer(), ctypes.byref(raw_vec_idx))
|
||||
bmap.BMMeshTrans_PrepareFaceNormalIndices(self._get_pointer(), ctypes.byref(raw_nml_idx))
|
||||
bmap.BMMeshTrans_PrepareFaceUVIndices(self._get_pointer(), ctypes.byref(raw_uv_idx))
|
||||
bmap.BMMeshTrans_PrepareFaceMtlSlot(self._get_pointer(), ctypes.byref(raw_mtl_idx))
|
||||
|
||||
# iterate and assign
|
||||
# assigne triple indices
|
||||
_ckfaceindices_assigner(raw_vec_idx, count, vec_idx)
|
||||
_ckfaceindices_assigner(raw_nml_idx, count, nml_idx)
|
||||
_ckfaceindices_assigner(raw_uv_idx, count, uv_idx)
|
||||
# assign mtl index
|
||||
idx: int = 0
|
||||
for _ in range(count):
|
||||
raw_mtl_idx[idx] = next(mtl_idx)
|
||||
idx += 1
|
||||
|
||||
#endregion
|
@ -1,318 +0,0 @@
|
||||
import typing, enum
|
||||
|
||||
ConstVxVector2 = tuple[float, float]
|
||||
ConstVxVector3 = tuple[float, float, float]
|
||||
ConstVxVector4 = tuple[float, float, float, float]
|
||||
|
||||
class VxVector2():
|
||||
x: float
|
||||
y: float
|
||||
|
||||
def __init__(self, _x: float = 0.0, _y: float = 0.0):
|
||||
self.x = _x
|
||||
self.y = _y
|
||||
|
||||
def from_const(self, cv: ConstVxVector2) -> None:
|
||||
(self.x, self.y, ) = cv
|
||||
|
||||
def to_const(self) -> ConstVxVector2:
|
||||
return (self.x, self.y, )
|
||||
|
||||
class VxVector3():
|
||||
x: float
|
||||
y: float
|
||||
z: float
|
||||
|
||||
def __init__(self, _x: float = 0.0, _y: float = 0.0, _z: float = 0.0):
|
||||
self.x = _x
|
||||
self.y = _y
|
||||
self.z = _z
|
||||
|
||||
def from_const(self, cv: ConstVxVector3) -> None:
|
||||
(self.x, self.y, self.z) = cv
|
||||
|
||||
def to_const(self) -> ConstVxVector3:
|
||||
return (self.x, self.y, self.z)
|
||||
|
||||
ConstCKFaceIndices = tuple[int, int, int]
|
||||
|
||||
class CKFaceIndices():
|
||||
i1: int
|
||||
i2: int
|
||||
i3: int
|
||||
|
||||
def __init__(self, i1_: int = 0, i2_: int = 0, i3_: int = 0):
|
||||
self.i1 = i1_
|
||||
self.i2 = i2_
|
||||
self.i3 = i3_
|
||||
|
||||
def from_const(self, cv: ConstCKFaceIndices) -> None:
|
||||
(self.i1, self.i2, self.i3) = cv
|
||||
|
||||
def to_const(self) -> ConstCKFaceIndices:
|
||||
return (self.i1, self.i2, self.i3)
|
||||
|
||||
ConstVxColorRGBA = tuple[float, float, float, float]
|
||||
ConstVxColorRGB = tuple[float, float, float]
|
||||
|
||||
class VxColor():
|
||||
"""
|
||||
The Color struct support RGBA.
|
||||
"""
|
||||
a: float
|
||||
r: float
|
||||
g: float
|
||||
b: float
|
||||
def __init__(self, _r: float = 0.0, _g: float = 0.0, _b: float = 0.0, _a: float = 1.0):
|
||||
self.r = _r
|
||||
self.g = _g
|
||||
self.b = _b
|
||||
self.a = _a
|
||||
self.regulate()
|
||||
|
||||
def to_const_rgba(self) -> ConstVxColorRGBA:
|
||||
return (self.r, self.g, self.b, self.a)
|
||||
|
||||
def to_const_rgb(self) -> ConstVxColorRGB:
|
||||
return (self.r, self.g, self.b)
|
||||
|
||||
def from_const_rgba(self, val: ConstVxColorRGBA) -> None:
|
||||
(self.r, self.g, self.b, self.a) = val
|
||||
self.regulate()
|
||||
|
||||
def from_const_rgb(self, val: ConstVxColorRGB) -> None:
|
||||
(self.r, self.g, self.b) = val
|
||||
self.a = 1.0
|
||||
self.regulate()
|
||||
|
||||
def from_dword(self, val: int) -> None:
|
||||
self.b = float(val & 0xFF) / 255.0
|
||||
val >>= 8
|
||||
self.g = float(val & 0xFF) / 255.0
|
||||
val >>= 8
|
||||
self.r = float(val & 0xFF) / 255.0
|
||||
val >>= 8
|
||||
self.a = float(val & 0xFF) / 255.0
|
||||
val >>= 8
|
||||
|
||||
def to_dword(self) -> int:
|
||||
# regulate self
|
||||
self.regulate()
|
||||
# construct value
|
||||
val: int = 0
|
||||
val |= int(self.a * 255)
|
||||
val <<= 8
|
||||
val |= int(self.r * 255)
|
||||
val <<= 8
|
||||
val |= int(self.g * 255)
|
||||
val <<= 8
|
||||
val |= int(self.b * 255)
|
||||
return val
|
||||
|
||||
def clone(self):
|
||||
return VxColor(self.r, self.g, self.b, self.a)
|
||||
|
||||
@staticmethod
|
||||
def _clamp_factor(val: float) -> float:
|
||||
if val > 1.0: return 1.0
|
||||
elif val < 0.0: return 0.0
|
||||
else: return val
|
||||
|
||||
def regulate(self):
|
||||
self.a = VxColor._clamp_factor(self.a)
|
||||
self.r = VxColor._clamp_factor(self.r)
|
||||
self.g = VxColor._clamp_factor(self.g)
|
||||
self.b = VxColor._clamp_factor(self.b)
|
||||
|
||||
ConstVxMatrix = tuple[
|
||||
float, float, float, float,
|
||||
float, float, float, float,
|
||||
float, float, float, float,
|
||||
float, float, float, float
|
||||
]
|
||||
|
||||
class VxMatrix():
|
||||
"""
|
||||
The Matrix representation.
|
||||
The bracket statement exactly equal with Virtools.
|
||||
"""
|
||||
data: list[list[float]]
|
||||
|
||||
def __init__(self):
|
||||
# init array
|
||||
self.data = [[0] * 4 for i in range(4)]
|
||||
# set to identy
|
||||
self.reset()
|
||||
|
||||
def _get_raw(self) -> list[list[float]]:
|
||||
return self.data
|
||||
|
||||
def reset(self) -> None:
|
||||
# reset to identy
|
||||
for i in range(4):
|
||||
for j in range(4):
|
||||
self.data[i][j] = 0.0
|
||||
|
||||
self.data[0][0] = 1.0
|
||||
self.data[1][1] = 1.0
|
||||
self.data[2][2] = 1.0
|
||||
self.data[3][3] = 1.0
|
||||
|
||||
def from_const(self, cm: ConstVxMatrix) -> None:
|
||||
(
|
||||
self.data[0][0], self.data[0][1], self.data[0][2], self.data[0][3],
|
||||
self.data[1][0], self.data[1][1], self.data[1][2], self.data[1][3],
|
||||
self.data[2][0], self.data[2][1], self.data[2][2], self.data[2][3],
|
||||
self.data[3][0], self.data[3][1], self.data[3][2], self.data[3][3]
|
||||
) = cm
|
||||
|
||||
def to_const(self) -> ConstVxMatrix:
|
||||
return (
|
||||
self.data[0][0], self.data[0][1], self.data[0][2], self.data[0][3],
|
||||
self.data[1][0], self.data[1][1], self.data[1][2], self.data[1][3],
|
||||
self.data[2][0], self.data[2][1], self.data[2][2], self.data[2][3],
|
||||
self.data[3][0], self.data[3][1], self.data[3][2], self.data[3][3]
|
||||
)
|
||||
|
||||
class CK_TEXTURE_SAVEOPTIONS(enum.IntEnum):
|
||||
"""!
|
||||
Specify the way textures or sprites will be saved
|
||||
"""
|
||||
CKTEXTURE_RAWDATA = 0 ##< Save raw data inside file. The bitmap is saved in a raw 32 bit per pixel format.
|
||||
CKTEXTURE_EXTERNAL = 1 ##< Store only the file name for the texture. The bitmap file must be present in the bitmap paths when loading the composition.
|
||||
CKTEXTURE_IMAGEFORMAT = 2 ##< Save using format specified. The bitmap data will be converted to the specified format by the correspondant bitmap plugin and saved inside file.
|
||||
CKTEXTURE_USEGLOBAL = 3 ##< Use Global settings, that is the settings given with CKContext::SetGlobalImagesSaveOptions. (Not valid when using CKContext::SetImagesSaveOptions).
|
||||
CKTEXTURE_INCLUDEORIGINALFILE = 4 ##< Insert original image file inside CMO file. The bitmap file that was used originally for the texture or sprite will be append to the composition file and extracted when the file is loaded.
|
||||
|
||||
class VX_PIXELFORMAT(enum.IntEnum):
|
||||
"""!
|
||||
Pixel format types.
|
||||
"""
|
||||
#UNKNOWN_PF = 0 ##< Unknown pixel format
|
||||
_32_ARGB8888 = 1 ##< 32-bit ARGB pixel format with alpha
|
||||
_32_RGB888 = 2 ##< 32-bit RGB pixel format without alpha
|
||||
_24_RGB888 = 3 ##< 24-bit RGB pixel format
|
||||
_16_RGB565 = 4 ##< 16-bit RGB pixel format
|
||||
_16_RGB555 = 5 ##< 16-bit RGB pixel format (5 bits per color)
|
||||
_16_ARGB1555 = 6 ##< 16-bit ARGB pixel format (5 bits per color + 1 bit for alpha)
|
||||
_16_ARGB4444 = 7 ##< 16-bit ARGB pixel format (4 bits per color)
|
||||
_8_RGB332 = 8 ##< 8-bit RGB pixel format
|
||||
_8_ARGB2222 = 9 ##< 8-bit ARGB pixel format
|
||||
_32_ABGR8888 = 10 ##< 32-bit ABGR pixel format
|
||||
_32_RGBA8888 = 11 ##< 32-bit RGBA pixel format
|
||||
_32_BGRA8888 = 12 ##< 32-bit BGRA pixel format
|
||||
_32_BGR888 = 13 ##< 32-bit BGR pixel format
|
||||
_24_BGR888 = 14 ##< 24-bit BGR pixel format
|
||||
_16_BGR565 = 15 ##< 16-bit BGR pixel format
|
||||
_16_BGR555 = 16 ##< 16-bit BGR pixel format (5 bits per color)
|
||||
_16_ABGR1555 = 17 ##< 16-bit ABGR pixel format (5 bits per color + 1 bit for alpha)
|
||||
_16_ABGR4444 = 18 ##< 16-bit ABGR pixel format (4 bits per color)
|
||||
_DXT1 = 19 ##< S3/DirectX Texture Compression 1
|
||||
_DXT2 = 20 ##< S3/DirectX Texture Compression 2
|
||||
_DXT3 = 21 ##< S3/DirectX Texture Compression 3
|
||||
_DXT4 = 22 ##< S3/DirectX Texture Compression 4
|
||||
_DXT5 = 23 ##< S3/DirectX Texture Compression 5
|
||||
_16_V8U8 = 24 ##< 16-bit Bump Map format format (8 bits per color)
|
||||
_32_V16U16 = 25 ##< 32-bit Bump Map format format (16 bits per color)
|
||||
_16_L6V5U5 = 26 ##< 16-bit Bump Map format format with luminance
|
||||
_32_X8L8V8U8 = 27 ##< 32-bit Bump Map format format with luminance
|
||||
_8_ABGR8888_CLUT = 28 ##< 8 bits indexed CLUT (ABGR)
|
||||
_8_ARGB8888_CLUT = 29 ##< 8 bits indexed CLUT (ARGB)
|
||||
_4_ABGR8888_CLUT = 30 ##< 4 bits indexed CLUT (ABGR)
|
||||
_4_ARGB8888_CLUT = 31 ##< 4 bits indexed CLUT (ARGB)
|
||||
|
||||
class VXTEXTURE_BLENDMODE(enum.IntEnum):
|
||||
"""!
|
||||
Blend Mode Flags
|
||||
"""
|
||||
VXTEXTUREBLEND_DECAL = 1 ##< Texture replace any material information
|
||||
VXTEXTUREBLEND_MODULATE = 2 ##< Texture and material are combine. Alpha information of the texture replace material alpha component.
|
||||
VXTEXTUREBLEND_DECALALPHA = 3 ##< Alpha information in the texture specify how material and texture are combined. Alpha information of the texture replace material alpha component.
|
||||
VXTEXTUREBLEND_MODULATEALPHA = 4 ##< Alpha information in the texture specify how material and texture are combined
|
||||
VXTEXTUREBLEND_DECALMASK = 5
|
||||
VXTEXTUREBLEND_MODULATEMASK = 6
|
||||
VXTEXTUREBLEND_COPY = 7 ##< Equivalent to DECAL
|
||||
VXTEXTUREBLEND_ADD = 8
|
||||
VXTEXTUREBLEND_DOTPRODUCT3 = 9 ##< Perform a Dot Product 3 between texture (normal map) and a referential vector given in VXRENDERSTATE_TEXTUREFACTOR.
|
||||
VXTEXTUREBLEND_MAX = 10
|
||||
|
||||
class VXTEXTURE_FILTERMODE(enum.IntEnum):
|
||||
"""!
|
||||
Filter Mode Options
|
||||
"""
|
||||
VXTEXTUREFILTER_NEAREST = 1 ##< No Filter
|
||||
VXTEXTUREFILTER_LINEAR = 2 ##< Bilinear Interpolation
|
||||
VXTEXTUREFILTER_MIPNEAREST = 3 ##< Mip mapping
|
||||
VXTEXTUREFILTER_MIPLINEAR = 4 ##< Mip Mapping with Bilinear interpolation
|
||||
VXTEXTUREFILTER_LINEARMIPNEAREST = 5 ##< Mip Mapping with Bilinear interpolation between mipmap levels.
|
||||
VXTEXTUREFILTER_LINEARMIPLINEAR = 6 ##< Trilinear Filtering
|
||||
VXTEXTUREFILTER_ANISOTROPIC = 7 ##< Anisotropic filtering
|
||||
|
||||
class VXTEXTURE_ADDRESSMODE(enum.IntEnum):
|
||||
"""!
|
||||
Texture addressing modes.
|
||||
"""
|
||||
VXTEXTURE_ADDRESSWRAP = 1 ##< Default mesh wrap mode is used (see CKMesh::SetWrapMode)
|
||||
VXTEXTURE_ADDRESSMIRROR = 2 ##< Texture coordinates outside the range [0..1] are flipped evenly.
|
||||
VXTEXTURE_ADDRESSCLAMP = 3 ##< Texture coordinates greater than 1.0 are set to 1.0, and values less than 0.0 are set to 0.0.
|
||||
VXTEXTURE_ADDRESSBORDER = 4 ##< When texture coordinates are greater than 1.0 or less than 0.0 texture is set to a color defined in CKMaterial::SetTextureBorderColor.
|
||||
VXTEXTURE_ADDRESSMIRRORONCE = 5 ##<
|
||||
|
||||
class VXBLEND_MODE(enum.IntEnum):
|
||||
"""!
|
||||
Blending Mode options
|
||||
"""
|
||||
VXBLEND_ZERO = 1 ##< Blend factor is (0, 0, 0, 0).
|
||||
VXBLEND_ONE = 2 ##< Blend factor is (1, 1, 1, 1).
|
||||
VXBLEND_SRCCOLOR = 3 ##< Blend factor is (Rs, Gs, Bs, As).
|
||||
VXBLEND_INVSRCCOLOR = 4 ##< Blend factor is (1-Rs, 1-Gs, 1-Bs, 1-As).
|
||||
VXBLEND_SRCALPHA = 5 ##< Blend factor is (As, As, As, As).
|
||||
VXBLEND_INVSRCALPHA = 6 ##< Blend factor is (1-As, 1-As, 1-As, 1-As).
|
||||
VXBLEND_DESTALPHA = 7 ##< Blend factor is (Ad, Ad, Ad, Ad).
|
||||
VXBLEND_INVDESTALPHA = 8 ##< Blend factor is (1-Ad, 1-Ad, 1-Ad, 1-Ad).
|
||||
VXBLEND_DESTCOLOR = 9 ##< Blend factor is (Rd, Gd, Bd, Ad).
|
||||
VXBLEND_INVDESTCOLOR = 10 ##< Blend factor is (1-Rd, 1-Gd, 1-Bd, 1-Ad).
|
||||
VXBLEND_SRCALPHASAT = 11 ##< Blend factor is (f, f, f, 1); f = min(As, 1-Ad).
|
||||
#VXBLEND_BOTHSRCALPHA = 12 ##< Source blend factor is (As, As, As, As) and destination blend factor is (1-As, 1-As, 1-As, 1-As)
|
||||
#VXBLEND_BOTHINVSRCALPHA = 13 ##< Source blend factor is (1-As, 1-As, 1-As, 1-As) and destination blend factor is (As, As, As, As)
|
||||
|
||||
class VXFILL_MODE(enum.IntEnum):
|
||||
"""!
|
||||
Fill Mode Options
|
||||
"""
|
||||
VXFILL_POINT = 1 ##< Vertices rendering
|
||||
VXFILL_WIREFRAME = 2 ##< Edges rendering
|
||||
VXFILL_SOLID = 3 ##< Face rendering
|
||||
|
||||
class VXSHADE_MODE(enum.IntEnum):
|
||||
"""!
|
||||
Shade Mode Options
|
||||
"""
|
||||
VXSHADE_FLAT = 1 ##< Flat Shading
|
||||
VXSHADE_GOURAUD = 2 ##< Gouraud Shading
|
||||
VXSHADE_PHONG = 3 ##< Phong Shading (Not yet supported by most implementation)
|
||||
|
||||
class VXCMPFUNC(enum.IntEnum):
|
||||
"""!
|
||||
Comparison Function
|
||||
"""
|
||||
VXCMP_NEVER = 1 ##< Always fail the test.
|
||||
VXCMP_LESS = 2 ##< Accept if value if less than current value.
|
||||
VXCMP_EQUAL = 3 ##< Accept if value if equal than current value.
|
||||
VXCMP_LESSEQUAL = 4 ##< Accept if value if less or equal than current value.
|
||||
VXCMP_GREATER = 5 ##< Accept if value if greater than current value.
|
||||
VXCMP_NOTEQUAL = 6 ##< Accept if value if different than current value.
|
||||
VXCMP_GREATEREQUAL = 7 ##< Accept if value if greater or equal current value.
|
||||
VXCMP_ALWAYS = 8 ##< Always accept the test.
|
||||
|
||||
class VXMESH_LITMODE(enum.IntEnum):
|
||||
"""!
|
||||
{filename:VXMESH_LITMODE}
|
||||
Summary: Mesh lighting options
|
||||
|
||||
Remarks:
|
||||
+ The VXMESH_LITMODE is used by CKMesh::SetLitMode to specify how lighting is done.
|
||||
See Also: CKMaterial,CKMesh
|
||||
"""
|
||||
VX_PRELITMESH = 0 ##< Lighting use color information store with vertices
|
||||
VX_LITMESH = 1 ##< Lighting is done by renderer using normals and face material information.
|
@ -104,11 +104,15 @@ def _nest_custom_split_normal(nml_array: array.array) -> typing.Iterator[UTIL_vi
|
||||
|
||||
class TemporaryMesh():
|
||||
"""
|
||||
|
||||
Create a temporary mesh for convenient exporting.
|
||||
When exporting mesh, we need triangulate it first.
|
||||
We create a temporary mesh to hold the triangulated mesh result.
|
||||
So that original object will not be affected and keep its original geometry.
|
||||
Please note passed bpy.types.Object must be Mesh Object.
|
||||
"""
|
||||
|
||||
__mBindingObject: bpy.types.Object | None
|
||||
__mTempMesh: bpy.types.Mesh | None
|
||||
__mBindingObject: bpy.types.Object
|
||||
__mTempMesh: bpy.types.Mesh
|
||||
|
||||
def __init__(self, binding_obj: bpy.types.Object):
|
||||
self.__mBindingObject = binding_obj
|
||||
@ -149,7 +153,7 @@ class MeshReader():
|
||||
A helper class TemporaryMesh can help you do this.
|
||||
"""
|
||||
|
||||
__mAssocMesh: bpy.types.Mesh | None ##< The binding mesh for this reader. None if this reader is invalid.
|
||||
__mAssocMesh: bpy.types.Mesh ##< The binding mesh for this reader. None if this reader is invalid.
|
||||
|
||||
def __init__(self, assoc_mesh: bpy.types.Mesh):
|
||||
self.__mAssocMesh = assoc_mesh
|
||||
@ -157,7 +161,6 @@ class MeshReader():
|
||||
# triangulate temp mesh
|
||||
if self.is_valid():
|
||||
self.__triangulate_mesh()
|
||||
self.__mAssocMesh.calc_normals_split()
|
||||
|
||||
def is_valid(self) -> bool:
|
||||
return self.__mAssocMesh is not None
|
||||
@ -171,7 +174,6 @@ class MeshReader():
|
||||
def dispose(self) -> None:
|
||||
if self.is_valid():
|
||||
# reset mesh
|
||||
self.__mAssocMesh.free_normals_split()
|
||||
self.__mAssocMesh = None
|
||||
|
||||
def get_vertex_position_count(self) -> int:
|
||||
@ -203,10 +205,10 @@ class MeshReader():
|
||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||
|
||||
cache: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
||||
for nml in self.__mAssocMesh.loops:
|
||||
cache.x = nml.normal.x
|
||||
cache.y = nml.normal.y
|
||||
cache.z = nml.normal.z
|
||||
for nml in self.__mAssocMesh.corner_normals:
|
||||
cache.x = nml.vector.x
|
||||
cache.y = nml.vector.y
|
||||
cache.z = nml.vector.z
|
||||
yield cache
|
||||
|
||||
def get_vertex_uv_count(self) -> int:
|
||||
@ -318,7 +320,7 @@ class MeshWriter():
|
||||
then refer it to all face uv.
|
||||
"""
|
||||
|
||||
__mAssocMesh: bpy.types.Mesh | None ##< The binding mesh for this writer. None if this writer is invalid.
|
||||
__mAssocMesh: bpy.types.Mesh ##< The binding mesh for this writer. None if this writer is invalid.
|
||||
|
||||
__mVertexPos: array.array ##< Array item is float(f). Length must be an integer multiple of 3.
|
||||
__mVertexNormal: array.array ##< Array item is float(f). Length must be an integer multiple of 3.
|
||||
@ -348,6 +350,9 @@ class MeshWriter():
|
||||
# Value is key's index in __mMtlSlot.
|
||||
__mMtlSlotMap: dict[bpy.types.Material | None, int]
|
||||
|
||||
## The attribute name storing temporary normals data inside mesh.
|
||||
__cTempNormalAttrName: typing.ClassVar[str] = 'temp_custom_normals'
|
||||
|
||||
def __init__(self, assoc_mesh: bpy.types.Mesh):
|
||||
self.__mAssocMesh = assoc_mesh
|
||||
|
||||
@ -446,15 +451,19 @@ class MeshWriter():
|
||||
self.__mAssocMesh.polygons.add(len(self.__mFaceVertexCount))
|
||||
# create uv layer
|
||||
self.__mAssocMesh.uv_layers.new(do_init = False)
|
||||
# split normals, it is IMPORTANT
|
||||
self.__mAssocMesh.create_normals_split()
|
||||
|
||||
# add vertex position data
|
||||
self.__mAssocMesh.vertices.foreach_set('co', self.__mVertexPos)
|
||||
# add face vertex pos index data
|
||||
self.__mAssocMesh.loops.foreach_set('vertex_index', self.__mFacePosIndices)
|
||||
# add face vertex nml by function
|
||||
self.__mAssocMesh.loops.foreach_set('normal',
|
||||
# add face vertex nml by function via mesh custom attribute
|
||||
# NOTE: Blender 4.0 / 4.1 changed. I copy these code from FBX Importer.
|
||||
temp_normal_attribute: bpy.types.FloatVectorAttribute
|
||||
temp_normal_attribute = typing.cast(
|
||||
bpy.types.FloatVectorAttribute,
|
||||
self.__mAssocMesh.attributes.new(MeshWriter.__cTempNormalAttrName, 'FLOAT_VECTOR', 'CORNER')
|
||||
)
|
||||
temp_normal_attribute.data.foreach_set('vector',
|
||||
tuple(_flat_face_nml_index(self.__mFaceNmlIndices, self.__mVertexNormal))
|
||||
)
|
||||
# add face vertex uv by function
|
||||
@ -494,14 +503,22 @@ class MeshWriter():
|
||||
# this should not happend in normal case, for testing, please load "Level_1.NMO" (Ballance Level 1).
|
||||
|
||||
# copy data from loops preserved in validate().
|
||||
# NOTE: Blender 4.0 / 4.1 changed. I copy these code from FBX Importer.
|
||||
loops_normals = array.array('f', [0.0] * (len(self.__mAssocMesh.loops) * 3))
|
||||
self.__mAssocMesh.loops.foreach_get('normal', loops_normals)
|
||||
temp_normal_attribute = typing.cast(
|
||||
bpy.types.FloatVectorAttribute,
|
||||
self.__mAssocMesh.attributes[MeshWriter.__cTempNormalAttrName]
|
||||
)
|
||||
temp_normal_attribute.data.foreach_get("vector", loops_normals)
|
||||
# apply data
|
||||
self.__mAssocMesh.normals_split_custom_set(
|
||||
tuple(_nest_custom_split_normal(loops_normals))
|
||||
)
|
||||
# enable auto smooth. it is IMPORTANT
|
||||
self.__mAssocMesh.use_auto_smooth = True
|
||||
self.__mAssocMesh.attributes.remove(
|
||||
# MARK: idk why I need fucking get this attribute again.
|
||||
# But if I were not, this function must raise bullshit exception!
|
||||
self.__mAssocMesh.attributes[MeshWriter.__cTempNormalAttrName]
|
||||
)
|
||||
|
||||
def __clear_mesh(self):
|
||||
if not self.is_valid():
|
||||
|
@ -88,6 +88,25 @@ def _get_prototype_by_identifier(ident: str) -> dict[str, typing.Any]:
|
||||
|
||||
#region Programmable Field Calc
|
||||
|
||||
def _env_fct_distance(x1: float, y1: float, x2: float, y2: float) -> float:
|
||||
diff = mathutils.Vector((x2, y2)) - mathutils.Vector((x1, y1))
|
||||
return diff.length
|
||||
|
||||
def _env_fct_angle(x1: float, y1: float, x2: float, y2: float) -> float:
|
||||
# compute blender angle first
|
||||
# computed blender angle has some issues:
|
||||
# first, it is range from -180 to 180 (0 is +X axis).
|
||||
# second, its direction (clockwise is positive) is opposite with blender rotation direction (counter-clockwise is positive).
|
||||
diff = mathutils.Vector((x2, y2)) - mathutils.Vector((x1, y1))
|
||||
bld_angle = math.degrees(mathutils.Vector((1,0)).angle_signed(diff, 0))
|
||||
|
||||
# flip it first
|
||||
bld_angle = -bld_angle
|
||||
# process positove number and negative number respectively
|
||||
# to let it range change from -180~180 to 0~360
|
||||
if bld_angle > 0: return bld_angle
|
||||
else: return 360 + bld_angle
|
||||
|
||||
_g_ProgFieldGlobals: dict[str, typing.Any] = {
|
||||
# constant
|
||||
'pi': math.pi,
|
||||
@ -122,6 +141,10 @@ _g_ProgFieldGlobals: dict[str, typing.Any] = {
|
||||
'rot': lambda x, y, z: mathutils.Matrix.LocRotScale(None, mathutils.Euler((math.radians(x), math.radians(y), math.radians(z)), 'XYZ'), None),
|
||||
'scale': lambda x, y, z: mathutils.Matrix.LocRotScale(None, None, (x, y, z)),
|
||||
'ident': lambda: mathutils.Matrix.Identity(4),
|
||||
|
||||
# my misc custom functions
|
||||
'distance': _env_fct_distance,
|
||||
'angle': _env_fct_angle,
|
||||
}
|
||||
|
||||
def _eval_showcase_cfgs_default(strl: str) -> typing.Any:
|
||||
@ -345,6 +368,12 @@ def create_bme_struct(
|
||||
# get result
|
||||
prebuild_vec_data.append((cache_bv.x, cache_bv.y, cache_bv.z))
|
||||
|
||||
# Check whether given transform is mirror matrix
|
||||
# because mirror matrix will reverse triangle indice order.
|
||||
# If matrix is mirror matrix, we need reverse it again in following procession,
|
||||
# including getting uv, calculating normal and providing face data.
|
||||
mirror_matrix: bool = _is_mirror_matrix(transform)
|
||||
|
||||
# prepare mesh part data
|
||||
mesh_part: UTIL_blender_mesh.MeshWriterIngredient = UTIL_blender_mesh.MeshWriterIngredient()
|
||||
def vpos_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||
@ -370,7 +399,12 @@ def create_bme_struct(
|
||||
if face_nml_data is None:
|
||||
# nml is null, we need compute by ourselves
|
||||
# get first 3 entries in indices list as the compution ref
|
||||
face_indices_data: list[int] = face_data[TOKEN_FACES_INDICES]
|
||||
# please note that we may need reverse it
|
||||
face_indices_data: list[int]
|
||||
if mirror_matrix:
|
||||
face_indices_data = face_data[TOKEN_FACES_INDICES][::-1]
|
||||
else:
|
||||
face_indices_data = face_data[TOKEN_FACES_INDICES][:]
|
||||
# compute it by getting vertices info from prebuild vertices data
|
||||
# because the normals is computed from transformed vertices
|
||||
# so no need to correct its by normal transform.
|
||||
@ -399,7 +433,9 @@ def create_bme_struct(
|
||||
v: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2()
|
||||
for face_idx in valid_face_idx:
|
||||
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx]
|
||||
for i in range(len(face_data[TOKEN_FACES_INDICES])):
|
||||
# iterate uv list considering mirror matrix
|
||||
indices_count: int = len(face_data[TOKEN_FACES_INDICES])
|
||||
for i in (range(indices_count)[::-1] if mirror_matrix else range(indices_count)):
|
||||
# BME uv do not need any extra process
|
||||
v.x, v.y = _eval_others(face_data[TOKEN_FACES_UVS][i], params)
|
||||
yield v
|
||||
@ -424,8 +460,13 @@ def create_bme_struct(
|
||||
# get face data
|
||||
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx]
|
||||
|
||||
# get face indices considering the mirror matrix
|
||||
face_indices: list[int]
|
||||
if mirror_matrix:
|
||||
face_indices = face_data[TOKEN_FACES_INDICES][::-1]
|
||||
else:
|
||||
face_indices = face_data[TOKEN_FACES_INDICES][:]
|
||||
# calc indices count
|
||||
face_indices: list[int] = face_data[TOKEN_FACES_INDICES]
|
||||
indices_count: int = len(face_indices)
|
||||
# resize face data to fulfill req
|
||||
while len(f.mIndices) > indices_count:
|
||||
@ -499,5 +540,18 @@ def _compute_normals(
|
||||
corss_mul.normalize()
|
||||
return (corss_mul.x, corss_mul.y, corss_mul.z)
|
||||
|
||||
def _is_mirror_matrix(mat: mathutils.Matrix) -> bool:
|
||||
"""
|
||||
Reflection matrix (aka. mirror matrix) is a special scaling matrix.
|
||||
In this matrix, 1 or 3 scaling factor is minus number.
|
||||
|
||||
Mirror matrix will cause the inverse of triangle indice order.
|
||||
So we need detect it and re-reverse when creating bm struct.
|
||||
This function can detect whether given matrix is mirror matrix.
|
||||
|
||||
Reference: https://zhuanlan.zhihu.com/p/96717729
|
||||
"""
|
||||
return mat.is_negative
|
||||
#return mat.to_3x3().determinant() < 0
|
||||
|
||||
#endregion
|
||||
|
@ -29,7 +29,7 @@ class ImportBallanceImage(bpy_extras.io_utils.ImportHelper):
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default = "*.bmp;*.tga",
|
||||
options = {'HIDDEN'}
|
||||
)
|
||||
) # type: ignore
|
||||
|
||||
def general_set_filename(self, filename: str) -> None:
|
||||
self.filepath = filename
|
||||
@ -44,7 +44,7 @@ class ImportBmxFile(bpy_extras.io_utils.ImportHelper):
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default = "*.bmx",
|
||||
options = {'HIDDEN'}
|
||||
)
|
||||
) # type: ignore
|
||||
|
||||
def general_get_filename(self) -> str:
|
||||
return self.filepath
|
||||
@ -56,7 +56,7 @@ class ExportBmxFile(bpy_extras.io_utils.ExportHelper):
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default = "*.bmx",
|
||||
options = {'HIDDEN'}
|
||||
)
|
||||
) # type: ignore
|
||||
|
||||
def general_get_filename(self) -> str:
|
||||
return self.filepath
|
||||
@ -68,7 +68,7 @@ class ImportVirtoolsFile(bpy_extras.io_utils.ImportHelper):
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default = "*.nmo;*.cmo;*.vmo",
|
||||
options = {'HIDDEN'}
|
||||
)
|
||||
) # type: ignore
|
||||
|
||||
def general_get_filename(self) -> str:
|
||||
return self.filepath
|
||||
@ -80,7 +80,7 @@ class ExportVirtoolsFile(bpy_extras.io_utils.ExportHelper):
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default = "*.nmo",
|
||||
options = {'HIDDEN'}
|
||||
)
|
||||
) # type: ignore
|
||||
|
||||
def general_get_filename(self) -> str:
|
||||
return self.filepath
|
||||
@ -88,13 +88,13 @@ class ExportVirtoolsFile(bpy_extras.io_utils.ExportHelper):
|
||||
class ImportDirectory(bpy_extras.io_utils.ImportHelper):
|
||||
|
||||
# add directory prop to receive directory
|
||||
directory: bpy.props.StringProperty()
|
||||
directory: bpy.props.StringProperty() # type: ignore
|
||||
|
||||
# blank filter
|
||||
filter_glob: bpy.props.StringProperty(
|
||||
default = "",
|
||||
options = {'HIDDEN'}
|
||||
)
|
||||
) # type: ignore
|
||||
|
||||
def general_get_directory(self) -> str:
|
||||
return self.directory
|
||||
|
@ -2,6 +2,10 @@ import bpy, mathutils
|
||||
import struct, os, io, typing
|
||||
from . import UTIL_virtools_types
|
||||
|
||||
## MARK:
|
||||
# This module may be deprecated because the host refering this module,
|
||||
# BM file import and export is no longer existing.
|
||||
|
||||
_FileWriter_t = io.BufferedWriter
|
||||
_FileReader_t = io.BufferedReader
|
||||
|
||||
@ -31,10 +35,10 @@ def write_float(fs: _FileWriter_t, fl: float) -> None:
|
||||
fs.write(struct.pack("<f", fl))
|
||||
|
||||
def write_world_matrix(fs: _FileWriter_t, mat: UTIL_virtools_types.VxMatrix) -> None:
|
||||
fs.write(struct.pack("<16f", *mat.to_tuple()))
|
||||
fs.write(struct.pack("<16f", *mat.to_const()))
|
||||
|
||||
def write_color(fs: _FileWriter_t, colors: UTIL_virtools_types.VxColor) -> None:
|
||||
fs.write(struct.pack("<fff", *colors.to_tuple_rgb()))
|
||||
fs.write(struct.pack("<fff", *colors.to_const_rgb()))
|
||||
|
||||
def write_uint32_array(fs: _FileWriter_t, vals: typing.Iterable[int], count: int) -> None:
|
||||
fs.write(struct.pack('<' + str(count) + 'I', *vals))
|
||||
@ -67,11 +71,11 @@ def read_string(fs: _FileReader_t) -> str:
|
||||
count = read_uint32(fs)
|
||||
return fs.read(count * 4).decode("utf_32_le")
|
||||
|
||||
def read_bool(fs: _FileReader_t) -> None:
|
||||
def read_bool(fs: _FileReader_t) -> bool:
|
||||
return read_uint8(fs) != 0
|
||||
|
||||
def read_world_materix(fs: _FileReader_t, mat: UTIL_virtools_types.VxMatrix) -> None:
|
||||
mat.from_tuple(struct.unpack("<16f", fs.read(16 * 4)))
|
||||
mat.from_const(struct.unpack("<16f", fs.read(16 * 4)))
|
||||
|
||||
def read_color(fs: _FileReader_t, target: UTIL_virtools_types.VxColor) -> None:
|
||||
target.from_const_rgb(struct.unpack("fff", fs.read(3 * 4)))
|
||||
|
@ -52,6 +52,11 @@ def message_box(message: tuple[str, ...], title: str, icon: str):
|
||||
|
||||
bpy.context.window_manager.popup_menu(draw, title = title, icon = icon)
|
||||
|
||||
def add_into_scene(obj: bpy.types.Object):
|
||||
view_layer = bpy.context.view_layer
|
||||
collection = view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
|
||||
def move_to_cursor(obj: bpy.types.Object):
|
||||
# use obj.matrix_world to move, not obj.location because this bug:
|
||||
# https://blender.stackexchange.com/questions/27667/incorrect-matrix-world-after-transformation
|
||||
@ -62,12 +67,31 @@ def move_to_cursor(obj: bpy.types.Object):
|
||||
obj.matrix_world = obj.matrix_world @ mathutils.Matrix.Translation(bpy.context.scene.cursor.location - obj.location)
|
||||
|
||||
def add_into_scene_and_move_to_cursor(obj: bpy.types.Object):
|
||||
view_layer = bpy.context.view_layer
|
||||
collection = view_layer.active_layer_collection.collection
|
||||
collection.objects.link(obj)
|
||||
|
||||
add_into_scene(obj)
|
||||
move_to_cursor(obj)
|
||||
|
||||
def select_certain_objects(objs: tuple[bpy.types.Object, ...]) -> None:
|
||||
# deselect all objects first
|
||||
bpy.ops.object.select_all(action = 'DESELECT')
|
||||
# if no objects, return
|
||||
if len(objs) == 0: return
|
||||
|
||||
# set selection for each object
|
||||
for obj in objs:
|
||||
obj.select_set(True)
|
||||
# select first object as active object
|
||||
bpy.context.view_layer.objects.active = objs[0]
|
||||
|
||||
def is_in_object_mode() -> bool:
|
||||
# get active object from context
|
||||
obj = bpy.context.active_object
|
||||
|
||||
# if there is no active object, we think it is in object mode
|
||||
if obj is None: return True
|
||||
|
||||
# simply check active object mode
|
||||
return obj.mode == 'OBJECT'
|
||||
|
||||
class EnumPropHelper():
|
||||
"""
|
||||
These class contain all functions related to EnumProperty, including generating `items`,
|
||||
@ -149,3 +173,99 @@ class EnumPropHelper():
|
||||
# call to_str fct ptr
|
||||
return self.__mFctToStr(val)
|
||||
|
||||
#region Blender Collection Visitor
|
||||
|
||||
_TPropertyGroup = typing.TypeVar('_TPropertyGroup', bound = bpy.types.PropertyGroup)
|
||||
|
||||
class CollectionVisitor(typing.Generic[_TPropertyGroup]):
|
||||
"""
|
||||
This is a patch class for Blender collection property.
|
||||
Blender collcetion property lack essential type hint and document.
|
||||
So I create a wrapper for my personal use to reduce type hint errors raised by my linter.
|
||||
"""
|
||||
|
||||
__mSrcProp: bpy.types.CollectionProperty
|
||||
|
||||
def __init__(self, src_prop: bpy.types.CollectionProperty):
|
||||
self.__mSrcProp = src_prop
|
||||
|
||||
def add(self) -> _TPropertyGroup:
|
||||
"""!
|
||||
@brief Adds a new item to the collection.
|
||||
@return The instance of newly created item.
|
||||
"""
|
||||
return self.__mSrcProp.add()
|
||||
|
||||
def remove(self, index: int) -> None:
|
||||
"""!
|
||||
@brief Removes the item at the specified index from the collection.
|
||||
@param[in] index The index of the item to remove.
|
||||
"""
|
||||
self.__mSrcProp.remove(index)
|
||||
|
||||
def move(self, from_index: int, to_index: int) -> None:
|
||||
"""!
|
||||
@brief Moves an item from one index to another within the collection.
|
||||
@param[in] from_index The current index of the item to move.
|
||||
@param[in] to_index The target index where the item should be moved.
|
||||
"""
|
||||
self.__mSrcProp.move(from_index, to_index)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""!
|
||||
@brief Clears all items from the collection.
|
||||
"""
|
||||
self.__mSrcProp.clear()
|
||||
|
||||
def __len__(self) -> int:
|
||||
return self.__mSrcProp.__len__()
|
||||
def __getitem__(self, index: int | str) -> _TPropertyGroup:
|
||||
return self.__mSrcProp.__getitem__(index)
|
||||
def __setitem__(self, index: int | str, value: _TPropertyGroup) -> None:
|
||||
self.__mSrcProp.__setitem__(index, value)
|
||||
def __delitem__(self, index: int | str) -> None:
|
||||
self.__mSrcProp.__delitem__(index)
|
||||
def __iter__(self) -> typing.Iterator[_TPropertyGroup]:
|
||||
return self.__mSrcProp.__iter__()
|
||||
def __contains__(self, item: _TPropertyGroup) -> bool:
|
||||
return self.__mSrcProp.__contains__(item)
|
||||
|
||||
#endregion
|
||||
|
||||
#region Tiny Mutex for With Context
|
||||
|
||||
_TMutexObject = typing.TypeVar('_TMutexObject')
|
||||
|
||||
class TinyMutex(typing.Generic[_TMutexObject]):
|
||||
"""
|
||||
In this plugin, some class have "with" context feature.
|
||||
However, it is essential to block any futher visiting if some "with" context are operating on some object.
|
||||
This is the reason why this tiny mutex is designed.
|
||||
|
||||
Please note this class is not a real MUTEX.
|
||||
We just want to make sure the resources only can be visited by one "with" context.
|
||||
So it doesn't matter that we do not use lock before operating something.
|
||||
"""
|
||||
|
||||
__mProtectedObjects: set[_TMutexObject]
|
||||
|
||||
def __init__(self):
|
||||
self.__mProtectedObjects = set()
|
||||
|
||||
def lock(self, obj: _TMutexObject) -> None:
|
||||
if obj in self.__mProtectedObjects:
|
||||
raise BBPException('It is not allowed that operate multiple "with" contexts on a single object.')
|
||||
self.__mProtectedObjects.add(obj)
|
||||
|
||||
def try_lock(self, obj: _TMutexObject) -> bool:
|
||||
if obj in self.__mProtectedObjects:
|
||||
return False
|
||||
self.__mProtectedObjects.add(obj)
|
||||
return True
|
||||
|
||||
def unlock(self, obj: _TMutexObject) -> None:
|
||||
if obj not in self.__mProtectedObjects:
|
||||
raise BBPException('It is not allowed that unlock an non-existent object.')
|
||||
self.__mProtectedObjects.remove(obj)
|
||||
|
||||
#endregion
|
||||
|
@ -66,7 +66,7 @@ def get_group_icon(name: str) -> int | None:
|
||||
|
||||
#endregion
|
||||
|
||||
def register():
|
||||
def register() -> None:
|
||||
global _g_IconsManager
|
||||
global _g_EmptyIcon
|
||||
global _g_BmeIconsMap, _g_ComponentIconsMap, _g_GroupIconsMap
|
||||
@ -100,7 +100,7 @@ def register():
|
||||
_g_GroupIconPrefix
|
||||
)
|
||||
|
||||
def unregister():
|
||||
def unregister() -> None:
|
||||
global _g_IconsManager
|
||||
global _g_EmptyIcon
|
||||
global _g_BmeIconsMap, _g_ComponentIconsMap, _g_GroupIconsMap
|
||||
|
@ -1,7 +1,7 @@
|
||||
import bpy
|
||||
import enum, typing
|
||||
from . import UTIL_virtools_types, UTIL_functions
|
||||
from . import PROP_ptrprop_resolver
|
||||
from . import PROP_ptrprop_resolver, PROP_ballance_map_info
|
||||
|
||||
## Intent
|
||||
# Some importer or exporter may share same properties.
|
||||
@ -67,20 +67,31 @@ class ConflictResolver():
|
||||
"""
|
||||
|
||||
__mObjectStrategy: ConflictStrategy
|
||||
__mLightStrategy: ConflictStrategy
|
||||
__mMeshStrategy: ConflictStrategy
|
||||
__mMaterialStrategy: ConflictStrategy
|
||||
__mTextureStrategy: ConflictStrategy
|
||||
|
||||
def __init__(self, obj_strategy: ConflictStrategy, mesh_strategy: ConflictStrategy, mtl_strategy: ConflictStrategy, tex_strategy: ConflictStrategy):
|
||||
def __init__(self,
|
||||
obj_strategy: ConflictStrategy,
|
||||
light_strategy: ConflictStrategy,
|
||||
mesh_strategy: ConflictStrategy,
|
||||
mtl_strategy: ConflictStrategy,
|
||||
tex_strategy: ConflictStrategy):
|
||||
self.__mObjectStrategy = obj_strategy
|
||||
self.__mLightStrategy = light_strategy
|
||||
self.__mMeshStrategy = mesh_strategy
|
||||
self.__mMaterialStrategy = mtl_strategy
|
||||
self.__mTextureStrategy = tex_strategy
|
||||
|
||||
def create_object(self, name: str, data: bpy.types.Mesh) -> tuple[bpy.types.Object, bool]:
|
||||
def create_object(self, name: str, data: bpy.types.Mesh | None) -> tuple[bpy.types.Object, bool]:
|
||||
"""
|
||||
Create object according to conflict strategy.
|
||||
`data` will only be applied when creating new object (no existing instance or strategy order rename)
|
||||
`data` will only be applied when creating new object (no existing instance or strategy order rename).
|
||||
|
||||
Please note this function is only used to create mesh 3d object.
|
||||
If you want to create light object, please use other functions provided by this class.
|
||||
The 3d object and data block of light is created together.
|
||||
"""
|
||||
if self.__mObjectStrategy == ConflictStrategy.Current:
|
||||
old: bpy.types.Object | None = bpy.data.objects.get(name, None)
|
||||
@ -88,6 +99,26 @@ class ConflictResolver():
|
||||
return (old, False)
|
||||
return (bpy.data.objects.new(name, data), True)
|
||||
|
||||
def create_light(self, name: str) -> tuple[bpy.types.Object, bpy.types.Light, bool]:
|
||||
"""
|
||||
Create light data block and associated 3d object.
|
||||
|
||||
If conflict strategy is "Current", we try fetch 3d object with given name first,
|
||||
then check whether it is light.
|
||||
If no given name object or this object is not light, we create a new one,
|
||||
otherwise return old one.
|
||||
"""
|
||||
if self.__mLightStrategy == ConflictStrategy.Current:
|
||||
old_obj: bpy.types.Object | None = bpy.data.objects.get(name, None)
|
||||
if old_obj is not None and old_obj.type == 'LIGHT':
|
||||
return (old_obj, typing.cast(bpy.types.Light, old_obj.data), False)
|
||||
# create new object.
|
||||
# if object or light name is conflict, rename it directly without considering conflict strategy.
|
||||
# create light with default point light type
|
||||
new_light: bpy.types.Light = bpy.data.lights.new(name, 'POINT')
|
||||
new_obj: bpy.types.Object = bpy.data.objects.new(name, new_light)
|
||||
return (new_obj, new_light, True)
|
||||
|
||||
def create_mesh(self, name: str) -> tuple[bpy.types.Mesh, bool]:
|
||||
if self.__mMeshStrategy == ConflictStrategy.Current:
|
||||
old: bpy.types.Mesh | None = bpy.data.meshes.get(name, None)
|
||||
@ -128,38 +159,61 @@ class ImportParams():
|
||||
items = _g_EnumHelper_ConflictStrategy.generate_items(),
|
||||
description = "Define how to process texture name conflict",
|
||||
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Current),
|
||||
)
|
||||
translation_context = 'BBP/UTIL_ioport_shared.ImportParams/property'
|
||||
) # type: ignore
|
||||
|
||||
material_conflict_strategy: bpy.props.EnumProperty(
|
||||
name = "Material Name Conflict",
|
||||
items = _g_EnumHelper_ConflictStrategy.generate_items(),
|
||||
description = "Define how to process material name conflict",
|
||||
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
|
||||
)
|
||||
translation_context = 'BBP/UTIL_ioport_shared.ImportParams/property'
|
||||
) # type: ignore
|
||||
|
||||
mesh_conflict_strategy: bpy.props.EnumProperty(
|
||||
name = "Mesh Name Conflict",
|
||||
items = _g_EnumHelper_ConflictStrategy.generate_items(),
|
||||
description = "Define how to process mesh name conflict",
|
||||
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
|
||||
)
|
||||
translation_context = 'BBP/UTIL_ioport_shared.ImportParams/property'
|
||||
) # type: ignore
|
||||
|
||||
light_conflict_strategy: bpy.props.EnumProperty(
|
||||
name = "Light Name Conflict",
|
||||
items = _g_EnumHelper_ConflictStrategy.generate_items(),
|
||||
description = "Define how to process light name conflict",
|
||||
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
|
||||
translation_context = 'BBP/UTIL_ioport_shared.ImportParams/property'
|
||||
) # type: ignore
|
||||
|
||||
object_conflict_strategy: bpy.props.EnumProperty(
|
||||
name = "Object Name Conflict",
|
||||
items = _g_EnumHelper_ConflictStrategy.generate_items(),
|
||||
description = "Define how to process object name conflict",
|
||||
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
|
||||
)
|
||||
translation_context = 'BBP/UTIL_ioport_shared.ImportParams/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw_import_params(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.label(text = 'Object Name Conflict')
|
||||
layout.prop(self, 'object_conflict_strategy', text = '')
|
||||
layout.label(text = 'Mesh Name Conflict')
|
||||
layout.prop(self, 'mesh_conflict_strategy', text = '')
|
||||
layout.label(text = 'Material Name Conflict')
|
||||
layout.prop(self, 'material_conflict_strategy', text = '')
|
||||
layout.label(text = 'Texture Name Conflict')
|
||||
layout.prop(self, 'texture_conflict_strategy', text = '')
|
||||
header: bpy.types.UILayout
|
||||
body: bpy.types.UILayout
|
||||
header, body = layout.panel("BBP_PT_ioport_shared_import_params", default_closed=False)
|
||||
header.label(text='Import Parameters', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
|
||||
# NOTE: if panel is collapsed, body will be None. So we need check it.
|
||||
if body is None: return
|
||||
|
||||
body.label(text='Name Conflict Strategy', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
|
||||
grid = body.grid_flow(row_major=False, columns=2)
|
||||
grid.label(text='Object', icon='CUBE', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
|
||||
grid.label(text='Light', icon='LIGHT', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
|
||||
grid.label(text='Mesh', icon='MESH_DATA', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
|
||||
grid.label(text='Material', icon='MATERIAL', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
|
||||
grid.label(text='Texture', icon='TEXTURE', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
|
||||
grid.prop(self, 'object_conflict_strategy', text='')
|
||||
grid.prop(self, 'light_conflict_strategy', text='')
|
||||
grid.prop(self, 'mesh_conflict_strategy', text='')
|
||||
grid.prop(self, 'material_conflict_strategy', text='')
|
||||
grid.prop(self, 'texture_conflict_strategy', text='')
|
||||
|
||||
def general_get_texture_conflict_strategy(self) -> ConflictStrategy:
|
||||
return _g_EnumHelper_ConflictStrategy.get_selection(self.texture_conflict_strategy)
|
||||
@ -170,12 +224,16 @@ class ImportParams():
|
||||
def general_get_mesh_conflict_strategy(self) -> ConflictStrategy:
|
||||
return _g_EnumHelper_ConflictStrategy.get_selection(self.mesh_conflict_strategy)
|
||||
|
||||
def general_get_light_conflict_strategy(self) -> ConflictStrategy:
|
||||
return _g_EnumHelper_ConflictStrategy.get_selection(self.light_conflict_strategy)
|
||||
|
||||
def general_get_object_conflict_strategy(self) -> ConflictStrategy:
|
||||
return _g_EnumHelper_ConflictStrategy.get_selection(self.object_conflict_strategy)
|
||||
|
||||
def general_get_conflict_resolver(self) -> ConflictResolver:
|
||||
return ConflictResolver(
|
||||
self.general_get_object_conflict_strategy(),
|
||||
self.general_get_light_conflict_strategy(),
|
||||
self.general_get_mesh_conflict_strategy(),
|
||||
self.general_get_material_conflict_strategy(),
|
||||
self.general_get_texture_conflict_strategy()
|
||||
@ -188,46 +246,144 @@ class ExportParams():
|
||||
('COLLECTION', "Collection", "Export a collection", 'OUTLINER_COLLECTION', 0),
|
||||
('OBJECT', "Object", "Export an object", 'OBJECT_DATA', 1),
|
||||
),
|
||||
)
|
||||
translation_context = 'BBP/UTIL_ioport_shared.ExportParams/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw_export_params(self, context: bpy.types.Context, layout: bpy.types.UILayout) -> None:
|
||||
header: bpy.types.UILayout
|
||||
body: bpy.types.UILayout
|
||||
header, body = layout.panel("BBP_PT_ioport_shared_export_params", default_closed=False)
|
||||
header.label(text='Export Parameters', text_ctxt='BBP/UTIL_ioport_shared.ExportParams/draw')
|
||||
if body is None: return
|
||||
|
||||
def draw_export_params(self, layout: bpy.types.UILayout) -> None:
|
||||
# make prop expand horizontaly, not vertical.
|
||||
sublayout = layout.row()
|
||||
horizon_body = body.row()
|
||||
# draw switch
|
||||
sublayout.prop(self, "export_mode", expand = True)
|
||||
horizon_body.prop(self, "export_mode", expand=True)
|
||||
|
||||
# draw picker
|
||||
ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene)
|
||||
if self.export_mode == 'COLLECTION':
|
||||
PROP_ptrprop_resolver.draw_export_collection(layout)
|
||||
ptrprops.draw_export_collection(body)
|
||||
elif self.export_mode == 'OBJECT':
|
||||
PROP_ptrprop_resolver.draw_export_object(layout)
|
||||
ptrprops.draw_export_object(body)
|
||||
|
||||
def general_get_export_objects(self) -> tuple[bpy.types.Object] | None:
|
||||
def general_get_export_objects(self, context: bpy.types.Context) -> tuple[bpy.types.Object] | None:
|
||||
"""
|
||||
Return resolved exported objects or None if no selection.
|
||||
"""
|
||||
ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene)
|
||||
if self.export_mode == 'COLLECTION':
|
||||
col: bpy.types.Collection = PROP_ptrprop_resolver.get_export_collection()
|
||||
col: bpy.types.Collection = ptrprops.get_export_collection()
|
||||
if col is None: return None
|
||||
else:
|
||||
return tuple(col.all_objects)
|
||||
else:
|
||||
obj: bpy.types.Object = PROP_ptrprop_resolver.get_export_object()
|
||||
obj: bpy.types.Object = ptrprops.get_export_object()
|
||||
if obj is None: return None
|
||||
else: return (obj, )
|
||||
|
||||
# define global tex save opt blender enum prop helper
|
||||
_g_EnumHelper_CK_TEXTURE_SAVEOPTIONS: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS)
|
||||
|
||||
class VirtoolsParams():
|
||||
vt_encodings: bpy.props.StringProperty(
|
||||
name = "Encodings",
|
||||
description = "The encoding list used by Virtools engine to resolve object name. Use `;` to split multiple encodings",
|
||||
default = UTIL_virtools_types.g_PyBMapDefaultEncoding
|
||||
)
|
||||
texture_save_opt: bpy.props.EnumProperty(
|
||||
name = "Global Texture Save Options",
|
||||
description = "Decide how texture saved if texture is specified as Use Global as its Save Options.",
|
||||
items = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.generate_items(),
|
||||
default = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.to_selection(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL),
|
||||
translation_context = 'BBP/UTIL_ioport_shared.VirtoolsParams/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw_virtools_params(self, layout: bpy.types.UILayout) -> None:
|
||||
layout.label(text = 'Encodings')
|
||||
layout.prop(self, 'vt_encodings', text = '')
|
||||
use_compress: bpy.props.BoolProperty(
|
||||
name="Use Compress",
|
||||
description = "Whether use ZLib to compress result when saving composition.",
|
||||
default = True,
|
||||
translation_context = 'BBP/UTIL_ioport_shared.VirtoolsParams/property'
|
||||
) # type: ignore
|
||||
|
||||
def general_get_vt_encodings(self) -> tuple[str]:
|
||||
# get encoding, split it by `;` and strip blank chars.
|
||||
encodings: str = self.vt_encodings
|
||||
return tuple(map(lambda x: x.strip(), encodings.split(';')))
|
||||
compress_level: bpy.props.IntProperty(
|
||||
name = "Compress Level",
|
||||
description = "The ZLib compress level used by Virtools Engine when saving composition.",
|
||||
min = 1, max = 9,
|
||||
default = 5,
|
||||
translation_context = 'BBP/UTIL_ioport_shared.VirtoolsParams/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw_virtools_params(self, context: bpy.types.Context, layout: bpy.types.UILayout, is_importer: bool) -> None:
|
||||
header: bpy.types.UILayout
|
||||
body: bpy.types.UILayout
|
||||
header, body = layout.panel("BBP_PT_ioport_shared_virtools_params", default_closed=False)
|
||||
header.label(text='Virtools Parameters', text_ctxt='BBP/UTIL_ioport_shared.VirtoolsParams/draw')
|
||||
if body is None: return
|
||||
|
||||
# draw encodings
|
||||
body.label(text='Encodings', text_ctxt='BBP/UTIL_ioport_shared.VirtoolsParams/draw')
|
||||
ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene)
|
||||
ptrprops.draw_ioport_encodings(body)
|
||||
|
||||
# following field are only valid in exporter
|
||||
if not is_importer:
|
||||
body.separator()
|
||||
body.label(text='Global Texture Save Options', text_ctxt='BBP/UTIL_ioport_shared.VirtoolsParams/draw')
|
||||
body.prop(self, 'texture_save_opt', text='')
|
||||
|
||||
body.separator()
|
||||
body.label(text='Compression', text_ctxt='BBP/UTIL_ioport_shared.VirtoolsParams/draw')
|
||||
body.prop(self, 'use_compress')
|
||||
if self.use_compress:
|
||||
body.prop(self, 'compress_level')
|
||||
|
||||
|
||||
def general_get_vt_encodings(self, context: bpy.types.Context) -> tuple[str, ...]:
|
||||
# get from ptrprop resolver then filter empty item
|
||||
ptrprops = PROP_ptrprop_resolver.PropsVisitor(context.scene)
|
||||
return tuple(filter(lambda encoding: len(encoding) != 0, ptrprops.get_ioport_encodings()))
|
||||
|
||||
def general_get_texture_save_opt(self) -> UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS:
|
||||
return _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.get_selection(self.texture_save_opt)
|
||||
|
||||
def general_get_use_compress(self) -> bool:
|
||||
return self.use_compress
|
||||
|
||||
def general_get_compress_level(self) -> int:
|
||||
return self.compress_level
|
||||
|
||||
class BallanceParams():
|
||||
successive_sector: bpy.props.BoolProperty(
|
||||
name="Successive Sector",
|
||||
description = "Whether order exporter to use document specified sector count to make sure sector is successive.",
|
||||
default = True,
|
||||
translation_context = 'BBP/UTIL_ioport_shared.BallanceParams/property'
|
||||
) # type: ignore
|
||||
|
||||
def draw_ballance_params(self, layout: bpy.types.UILayout, is_importer: bool) -> None:
|
||||
# ballance params only presented in exporter.
|
||||
# so if we are in impoerter, we skip the whole function
|
||||
# because we don't want to create an empty panel.
|
||||
if is_importer: return
|
||||
|
||||
header: bpy.types.UILayout
|
||||
body: bpy.types.UILayout
|
||||
header, body = layout.panel("BBP_PT_ioport_shared_ballance_params", default_closed=False)
|
||||
header.label(text='Ballance Parameters', text_ctxt='BBP/UTIL_ioport_shared.BallanceParams/draw')
|
||||
if body is None: return
|
||||
|
||||
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||
body.prop(self, 'successive_sector')
|
||||
tr_text: str = bpy.app.translations.pgettext_iface(
|
||||
'Map Sectors: {0}', 'BBP/UTIL_ioport_shared.BallanceParams/draw')
|
||||
body.label(text=tr_text.format(map_info.mSectorCount), translate=False)
|
||||
|
||||
def general_get_successive_sector(self) -> bool:
|
||||
return self.successive_sector
|
||||
|
||||
def general_get_successive_sector_count(self) -> int:
|
||||
# if user do not pick successive sector, return a random int directly.
|
||||
if not self.general_get_successive_sector():
|
||||
return 0
|
||||
|
||||
# otherwise fetch user specified sector number
|
||||
map_info: PROP_ballance_map_info.RawBallanceMapInfo
|
||||
map_info = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||
return map_info.mSectorCount
|
||||
|
@ -70,7 +70,10 @@ class RenameErrorReporter():
|
||||
def __enter__(self):
|
||||
# print console report header
|
||||
print('============')
|
||||
print('Rename Report')
|
||||
print(bpy.app.translations.pgettext_rpt(
|
||||
'Rename Report',
|
||||
'BBP/UTIL_naming_convension.RenameErrorReporter'
|
||||
))
|
||||
print('------------')
|
||||
# return self as context
|
||||
return self
|
||||
@ -78,7 +81,9 @@ class RenameErrorReporter():
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
# print console report tail
|
||||
print('------------')
|
||||
print(f'All / Failed - {self.mAllObjCounter} / {self.mFailedObjCounter}')
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt(
|
||||
'All / Failed - {0} / {1}', 'BBP/UTIL_naming_convension.RenameErrorReporter')
|
||||
print(tr_text.format(self.mAllObjCounter, self.mFailedObjCounter))
|
||||
print('============')
|
||||
# reset variables
|
||||
self.mAllObjCounter = 0
|
||||
@ -99,11 +104,14 @@ class RenameErrorReporter():
|
||||
|
||||
# output header
|
||||
# if new name is different with old name, output both of them
|
||||
tr_text: str
|
||||
new_name: str = obj.name
|
||||
if self.mOldName == new_name:
|
||||
print(f'For object "{new_name}"')
|
||||
tr_text = bpy.app.translations.pgettext_rpt('For object "{0}"', 'BBP/UTIL_naming_convension.RenameErrorReporter')
|
||||
print(tr_text.format(new_name))
|
||||
else:
|
||||
print(f'For object "{new_name}" (Old name: "{self.mOldName}")')
|
||||
tr_text = bpy.app.translations.pgettext_rpt('For object "{0}" (Old name: "{1}")', 'BBP/UTIL_naming_convension.RenameErrorReporter')
|
||||
print(tr_text.format(new_name, self.mOldName))
|
||||
|
||||
# output error list with indent
|
||||
for item in self.mErrList:
|
||||
@ -116,9 +124,12 @@ class RenameErrorReporter():
|
||||
@staticmethod
|
||||
def __errtype_to_string(err_v: _RenameErrorType) -> str:
|
||||
match(err_v):
|
||||
case _RenameErrorType.ERROR: return 'ERROR'
|
||||
case _RenameErrorType.WARNING: return 'WARN'
|
||||
case _RenameErrorType.INFO: return 'INFO'
|
||||
case _RenameErrorType.ERROR:
|
||||
return bpy.app.translations.pgettext_rpt('ERROR', 'BBP/UTIL_naming_convension.RenameErrorReporter')
|
||||
case _RenameErrorType.WARNING:
|
||||
return bpy.app.translations.pgettext_rpt('WARN', 'BBP/UTIL_naming_convension.RenameErrorReporter')
|
||||
case _RenameErrorType.INFO:
|
||||
return bpy.app.translations.pgettext_rpt('INFO', 'BBP/UTIL_naming_convension.RenameErrorReporter')
|
||||
case _: raise UTIL_functions.BBPException("Unknown error type.")
|
||||
@staticmethod
|
||||
def __erritem_to_string(item: _RenameErrorItem) -> str:
|
||||
@ -184,6 +195,42 @@ class BallanceObjectInfo():
|
||||
|
||||
#endregion
|
||||
|
||||
#region Sector Extractor
|
||||
|
||||
_g_RegexBlcSectorGroup: re.Pattern = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
|
||||
|
||||
def extract_sector_from_name(group_name: str) -> int | None:
|
||||
"""
|
||||
A convenient function to extract sector index from given group name.
|
||||
This function also supports 999 sector plugin.
|
||||
|
||||
Not only in this module, but also in outside modules, this function is vary used to extract sector index info.
|
||||
|
||||
Function return the index extracted, or None if given group name is not a valid sector group.
|
||||
The valid sector index is range from 1 to 999 (inclusive)
|
||||
"""
|
||||
regex_result = _g_RegexBlcSectorGroup.match(group_name)
|
||||
if regex_result is not None:
|
||||
return int(regex_result.group(1))
|
||||
else:
|
||||
return None
|
||||
|
||||
def build_name_from_sector_index(sector_index: int) -> str:
|
||||
"""
|
||||
A convenient function to build Ballance recognizable sector group name.
|
||||
This function also supports 999 sector plugin.
|
||||
|
||||
This function also is used in this module or other modules outside.
|
||||
|
||||
Function return a sector name string. It basically the reverse operation of `extract_sector_from_name`.
|
||||
"""
|
||||
if sector_index == 9:
|
||||
return 'Sector_9'
|
||||
else:
|
||||
return f'Sector_{sector_index:0>2d}'
|
||||
|
||||
#endregion
|
||||
|
||||
#region Naming Convention Declaration
|
||||
|
||||
_g_BlcNormalComponents: set[str] = set((
|
||||
@ -227,7 +274,6 @@ _g_BlcWood: set[str] = set((
|
||||
))
|
||||
|
||||
class VirtoolsGroupConvention():
|
||||
cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
|
||||
cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + ')_(0[1-9]|[1-9][0-9])_.*$')
|
||||
cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_TwoFlames_(0[1-7])$')
|
||||
cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_Resetpoint_(0[1-8])$')
|
||||
@ -245,7 +291,9 @@ class VirtoolsGroupConvention():
|
||||
int(regex_result.group(1))
|
||||
)
|
||||
|
||||
if reporter: reporter.add_error("PC_Checkpoints or PR_Resetpoints detected. But couldn't get sector from name.")
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt(
|
||||
"PC_Checkpoints or PR_Resetpoints detected. But couldn't get sector from name.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
|
||||
if reporter: reporter.add_error(tr_text)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@ -255,9 +303,9 @@ class VirtoolsGroupConvention():
|
||||
counter: int = 0
|
||||
last_matched_sector: int = 0
|
||||
for i in gps:
|
||||
regex_result = VirtoolsGroupConvention.cRegexGroupSector.match(i)
|
||||
regex_result: int | None = extract_sector_from_name(i)
|
||||
if regex_result is not None:
|
||||
last_matched_sector = int(regex_result.group(1))
|
||||
last_matched_sector = regex_result
|
||||
counter += 1
|
||||
|
||||
if counter != 1: return None
|
||||
@ -265,6 +313,9 @@ class VirtoolsGroupConvention():
|
||||
|
||||
@staticmethod
|
||||
def parse_from_object(obj: bpy.types.Object, reporter: RenameErrorReporter | None) -> BallanceObjectInfo | None:
|
||||
# declare translation string type
|
||||
tr_text: str
|
||||
|
||||
# create visitor
|
||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||
# if no group, we should consider it is decoration or skylayer
|
||||
@ -285,10 +336,14 @@ class VirtoolsGroupConvention():
|
||||
# these type's data should be gotten from its name
|
||||
return VirtoolsGroupConvention.__get_pcpr_from_name(obj.name, reporter)
|
||||
case _:
|
||||
if reporter: reporter.add_error("The match of Unique Component lost.")
|
||||
tr_text = bpy.app.translations.pgettext_rpt(
|
||||
"The match of Unique Component lost.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
|
||||
if reporter: reporter.add_error(tr_text)
|
||||
return None
|
||||
elif len(inter_gps) != 0:
|
||||
if reporter: reporter.add_error("A Multi-grouping Unique Component.")
|
||||
tr_text = bpy.app.translations.pgettext_rpt(
|
||||
"A Multi-grouping Unique Component.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
|
||||
if reporter: reporter.add_error(tr_text)
|
||||
return None
|
||||
|
||||
# distinguish normal elements
|
||||
@ -300,7 +355,9 @@ class VirtoolsGroupConvention():
|
||||
gotten_sector: int | None = VirtoolsGroupConvention.__get_sector_from_groups(gp.iterate_groups())
|
||||
if gotten_sector is None:
|
||||
# fail to get sector
|
||||
if reporter: reporter.add_error("Component detected. But couldn't get sector from CKGroup data.")
|
||||
tr_text = bpy.app.translations.pgettext_rpt(
|
||||
"Component detected. But couldn't get sector from CKGroup data.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
|
||||
if reporter: reporter.add_error(tr_text)
|
||||
return None
|
||||
return BallanceObjectInfo.create_from_component(
|
||||
gotten_elements,
|
||||
@ -308,7 +365,9 @@ class VirtoolsGroupConvention():
|
||||
)
|
||||
elif len(inter_gps) != 0:
|
||||
# must be a weird grouping, report it
|
||||
if reporter: reporter.add_error("A Multi-grouping Component.")
|
||||
tr_text = bpy.app.translations.pgettext_rpt(
|
||||
"A Multi-grouping Component.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
|
||||
if reporter: reporter.add_error(tr_text)
|
||||
return None
|
||||
|
||||
# distinguish road
|
||||
@ -324,7 +383,9 @@ class VirtoolsGroupConvention():
|
||||
elif len(floor_result) == 0 and len(rail_result) > 0:
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.WOOD)
|
||||
else:
|
||||
if reporter: reporter.add_warning("Can't distinguish object between Floors and Rails. Suppose it is Floors.")
|
||||
tr_text = bpy.app.translations.pgettext_rpt(
|
||||
"Can't distinguish object between Floors and Rails. Suppose it is Floors.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
|
||||
if reporter: reporter.add_warning(tr_text)
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.FLOOR)
|
||||
elif gp.contain_group('Phys_FloorStopper'):
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.STOPPER)
|
||||
@ -332,7 +393,9 @@ class VirtoolsGroupConvention():
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.DEPTH_CUBE)
|
||||
|
||||
# no matched
|
||||
if reporter: reporter.add_error("Group match lost.")
|
||||
tr_text = bpy.app.translations.pgettext_rpt(
|
||||
"Group match lost.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
|
||||
if reporter: reporter.add_error(tr_text)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@ -360,14 +423,16 @@ class VirtoolsGroupConvention():
|
||||
gp.add_group('Phys_Floors')
|
||||
gp.add_group('Sound_HitID_01')
|
||||
gp.add_group('Sound_RollID_01')
|
||||
# floor type also need group into shadow group.
|
||||
gp.add_group('Shadow')
|
||||
case BallanceObjectType.RAIL:
|
||||
gp.add_group('Phys_FloorRails')
|
||||
gp.add_group('Sound_HitID_02')
|
||||
gp.add_group('Sound_RollID_02')
|
||||
case BallanceObjectType.WOOD:
|
||||
gp.add_group('Phys_Floors')
|
||||
gp.add_group('Sound_HitID_03')
|
||||
gp.add_group('Sound_RollID_03')
|
||||
case BallanceObjectType.WOOD:
|
||||
gp.add_group('Phys_Floors')
|
||||
gp.add_group('Sound_HitID_02')
|
||||
gp.add_group('Sound_RollID_02')
|
||||
case BallanceObjectType.STOPPER:
|
||||
gp.add_group('Phys_FloorStopper')
|
||||
|
||||
@ -375,16 +440,13 @@ class VirtoolsGroupConvention():
|
||||
# group into component type
|
||||
# use typing.cast() to force linter accept it because None is impossible
|
||||
gp.add_group(typing.cast(str, info.mComponentType))
|
||||
|
||||
# group to sector
|
||||
if info.mSector == 9:
|
||||
gp.add_group('Sector_9')
|
||||
else:
|
||||
gp.add_group(f'Sector_{info.mSector:0>2d}')
|
||||
gp.add_group(build_name_from_sector_index(typing.cast(int, info.mSector)))
|
||||
|
||||
case _:
|
||||
if reporter is not None:
|
||||
reporter.add_error('No matched info.')
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt(
|
||||
"No matched info.", 'BBP/UTIL_naming_convension.VirtoolsGroupConvention')
|
||||
if reporter: reporter.add_error(tr_text)
|
||||
return False
|
||||
|
||||
return True
|
||||
@ -436,8 +498,9 @@ class YYCToolchainConvention():
|
||||
if name == 'SkyLayer':
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER)
|
||||
|
||||
if reporter is not None:
|
||||
reporter.add_error("Name match lost.")
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt(
|
||||
"Name match lost.", 'BBP/UTIL_naming_convension.YYCToolchainConvention')
|
||||
if reporter: reporter.add_error(tr_text)
|
||||
return None
|
||||
|
||||
|
||||
@ -458,9 +521,9 @@ class YYCToolchainConvention():
|
||||
case BallanceObjectType.LEVEL_END:
|
||||
return 'PE_Balloon_01'
|
||||
case BallanceObjectType.CHECKPOINT:
|
||||
return f'PR_Resetpoint_{info.mSector:0>2d}'
|
||||
case BallanceObjectType.RESETPOINT:
|
||||
return f'PC_TwoFlames_{info.mSector:0>2d}'
|
||||
case BallanceObjectType.RESETPOINT:
|
||||
return f'PR_Resetpoint_{info.mSector:0>2d}'
|
||||
|
||||
case BallanceObjectType.DEPTH_CUBE:
|
||||
return 'DepthCubes_'
|
||||
@ -479,8 +542,9 @@ class YYCToolchainConvention():
|
||||
info.mComponentType, info.mSector)
|
||||
|
||||
case _:
|
||||
if reporter is not None:
|
||||
reporter.add_error('No matched info.')
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt(
|
||||
"No matched info.", 'BBP/UTIL_naming_convension.YYCToolchainConvention')
|
||||
if reporter: reporter.add_error(tr_text)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@ -542,8 +606,9 @@ class ImengyuConvention():
|
||||
if name == 'SkyLayer':
|
||||
return BallanceObjectInfo.create_from_others(BallanceObjectType.SKYLAYER)
|
||||
|
||||
if reporter is not None:
|
||||
reporter.add_error("Name match lost.")
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt(
|
||||
"Name match lost.", 'BBP/UTIL_naming_convension.ImengyuConvention')
|
||||
if reporter: reporter.add_error(tr_text)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@ -587,8 +652,9 @@ class ImengyuConvention():
|
||||
)
|
||||
|
||||
case _:
|
||||
if reporter is not None:
|
||||
reporter.add_error('No matched info.')
|
||||
tr_text: str = bpy.app.translations.pgettext_rpt(
|
||||
"No matched info.", 'BBP/UTIL_naming_convension.ImengyuConvention')
|
||||
if reporter: reporter.add_error(tr_text)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
|
347
bbp_ng/UTIL_rail_creator.py
Normal file
@ -0,0 +1,347 @@
|
||||
import bpy, bmesh, mathutils, math
|
||||
import typing
|
||||
from . import UTIL_functions, UTIL_naming_convension
|
||||
from . import PROP_bme_material
|
||||
|
||||
#region BMesh Operations Helper
|
||||
|
||||
def _bmesh_extrude(
|
||||
bm: bmesh.types.BMesh,
|
||||
start_edges: list[bmesh.types.BMEdge],
|
||||
direction: mathutils.Vector) -> list[bmesh.types.BMEdge]:
|
||||
# extrude
|
||||
ret: dict[str, typing.Any] = bmesh.ops.extrude_edge_only(
|
||||
bm,
|
||||
edges = start_edges,
|
||||
use_normal_flip = True, # NOTE: flip normal according to test result.
|
||||
use_select_history = False
|
||||
)
|
||||
|
||||
# get end edges
|
||||
ret_geom = ret['geom']
|
||||
del ret
|
||||
end_verts: list[bmesh.types.BMVert] = list(filter(lambda x: isinstance(x, bmesh.types.BMVert), ret_geom))
|
||||
end_edges: list[bmesh.types.BMEdge] = list(filter(lambda x: isinstance(x, bmesh.types.BMEdge) and x.is_boundary, ret_geom))
|
||||
# and move it
|
||||
bmesh.ops.translate(
|
||||
bm,
|
||||
vec = direction, space = mathutils.Matrix.Identity(4),
|
||||
verts = end_verts,
|
||||
use_shapekey = False
|
||||
)
|
||||
|
||||
# return value
|
||||
return end_edges
|
||||
|
||||
def _bmesh_screw(
|
||||
bm: bmesh.types.BMesh,
|
||||
start_verts: list[bmesh.types.BMVert], start_edges: list[bmesh.types.BMEdge],
|
||||
angle: float, steps: int, iterations: int,
|
||||
center: mathutils.Vector, screw_per_iteration: float) -> list[bmesh.types.BMEdge]:
|
||||
"""
|
||||
Hints: Angle is input as degree unit.
|
||||
"""
|
||||
# screw
|
||||
ret: dict[str, typing.Any] = bmesh.ops.spin(
|
||||
bm,
|
||||
geom = start_edges,
|
||||
cent = center,
|
||||
axis = mathutils.Vector((0, 0, 1)), # default to +Z
|
||||
dvec = mathutils.Vector((0, 0, screw_per_iteration / steps)), # conv to step delta
|
||||
angle = math.radians(angle) * iterations,
|
||||
space = mathutils.Matrix.Identity(4),
|
||||
steps = steps * iterations,
|
||||
use_merge = False,
|
||||
use_normal_flip = True, # NOTE: flip normal according to test result.
|
||||
use_duplicate = False
|
||||
)
|
||||
|
||||
# return last segment
|
||||
geom_last = ret['geom_last']
|
||||
del ret
|
||||
return list(filter(lambda x: isinstance(x, bmesh.types.BMEdge), geom_last))
|
||||
|
||||
def _bmesh_cap(bm: bmesh.types.BMesh, edges: list[bmesh.types.BMEdge]) -> None:
|
||||
"""
|
||||
Cap given edges. And mark it as sharp edge.
|
||||
Please reset all edges to smooth one before calling this.
|
||||
"""
|
||||
# fill holes
|
||||
bmesh.ops.triangle_fill(
|
||||
bm,
|
||||
use_beauty = False, use_dissolve = False,
|
||||
edges = edges
|
||||
# no pass to normal.
|
||||
)
|
||||
|
||||
# and only set sharp for cap's edges
|
||||
for edge in edges:
|
||||
edge.smooth = False
|
||||
|
||||
def _bmesh_smooth_all_edges(bm: bmesh.types.BMesh) -> None:
|
||||
"""
|
||||
Reset all edges to smooth. Call this before calling edge cap function.
|
||||
"""
|
||||
# reset all edges to smooth
|
||||
edge: bmesh.types.BMEdge
|
||||
for edge in bm.edges:
|
||||
edge.smooth = True
|
||||
|
||||
def _bmesh_flip_all_faces(bm: bmesh.types.BMesh, flip_x: bool, flip_y: bool, flip_z: bool) -> None:
|
||||
"""
|
||||
Flip the whole geometry in given bmesh with given axis.
|
||||
"""
|
||||
# get mirror result
|
||||
scale_factor: mathutils.Vector = mathutils.Vector((
|
||||
(-1 if flip_x else 1),
|
||||
(-1 if flip_y else 1),
|
||||
(-1 if flip_z else 1)
|
||||
))
|
||||
bmesh.ops.scale(bm, vec = scale_factor, verts = bm.verts[:])
|
||||
|
||||
# check whether we need perform normal flip.
|
||||
# see UTIL_bme._is_mirror_matrix for more detail
|
||||
test_matrix: mathutils.Matrix = mathutils.Matrix.LocRotScale(None, None, scale_factor)
|
||||
if test_matrix.is_negative:
|
||||
bmesh.ops.reverse_faces(bm, faces = bm.faces[:])
|
||||
|
||||
#endregion
|
||||
|
||||
#region Real Rail Creators
|
||||
|
||||
def rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], None], extra_transform: mathutils.Matrix) -> bpy.types.Object:
|
||||
# create mesh first
|
||||
bm: bmesh.types.BMesh = bmesh.new()
|
||||
|
||||
# call cret fct
|
||||
fct_poly_cret(bm)
|
||||
|
||||
# finish up
|
||||
mesh: bpy.types.Mesh = bpy.data.meshes.new('Rail')
|
||||
bm.to_mesh(mesh)
|
||||
bm.free()
|
||||
|
||||
# setup smooth for mesh
|
||||
mesh.shade_smooth()
|
||||
|
||||
# setup default material
|
||||
with PROP_bme_material.BMEMaterialsHelper(bpy.context.scene) as bmemtl:
|
||||
mesh.materials.clear()
|
||||
mesh.materials.append(bmemtl.get_material('Rail'))
|
||||
mesh.validate_material_indices()
|
||||
|
||||
# create object and assoc with it
|
||||
# create info first
|
||||
rail_info: UTIL_naming_convension.BallanceObjectInfo = UTIL_naming_convension.BallanceObjectInfo.create_from_others(
|
||||
UTIL_naming_convension.BallanceObjectType.RAIL
|
||||
)
|
||||
# then get object name
|
||||
rail_name: str | None = UTIL_naming_convension.YYCToolchainConvention.set_to_name(rail_info, None)
|
||||
if rail_name is None: raise UTIL_functions.BBPException('impossible null name')
|
||||
# create object by name
|
||||
obj: bpy.types.Object = bpy.data.objects.new(rail_name, mesh)
|
||||
# assign virtools groups
|
||||
UTIL_naming_convension.VirtoolsGroupConvention.set_to_object(obj, rail_info, None)
|
||||
|
||||
# move to cursor
|
||||
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
||||
# add extra transform
|
||||
obj.matrix_world = obj.matrix_world @ extra_transform
|
||||
# select created object
|
||||
UTIL_functions.select_certain_objects((obj, ))
|
||||
|
||||
# return rail
|
||||
return obj
|
||||
|
||||
def create_rail_section(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
matrix: mathutils.Matrix = mathutils.Matrix.Identity(4)) -> None:
|
||||
"""
|
||||
Add a rail section.
|
||||
|
||||
If created is monorail, the original point locate at the center of section.
|
||||
Otherwise, the original point locate at the center point of the line connecting between left rail section and right rail section.
|
||||
The section will be placed in XZ panel.
|
||||
|
||||
If ordered is monorail, `rail_span` param will be ignored.
|
||||
"""
|
||||
if is_monorail:
|
||||
# create monorail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
None,
|
||||
mathutils.Euler((math.radians(90), math.radians(22.5), 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
else:
|
||||
# create rail
|
||||
# create left rail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector((-rail_span / 2, 0, 0)),
|
||||
mathutils.Euler((math.radians(90), 0, 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
# create right rail
|
||||
bmesh.ops.create_circle(
|
||||
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
|
||||
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
|
||||
mathutils.Vector((rail_span / 2, 0, 0)),
|
||||
mathutils.Euler((math.radians(90), 0, 0), 'XYZ'),
|
||||
None
|
||||
)),
|
||||
calc_uvs = False
|
||||
)
|
||||
|
||||
def create_transition_section(
|
||||
bm: bmesh.types.BMesh,
|
||||
rail_radius: float, rail_span: float) -> None:
|
||||
"""
|
||||
Create the transition section between rail and monorail.
|
||||
"""
|
||||
# create rail section
|
||||
create_rail_section(bm, False, rail_radius, rail_span)
|
||||
|
||||
# create monorail
|
||||
# calc sink first
|
||||
monorail_sink: float
|
||||
try:
|
||||
monorail_sink = math.sqrt((rail_radius + 2) ** 2 - (rail_span / 2) ** 2) - 2 - rail_radius
|
||||
except:
|
||||
monorail_sink = -2 # if sqrt(minus number) happended, it mean no triangle relation. the depth should always be -2.
|
||||
# create monorail with calculated sink
|
||||
create_rail_section(
|
||||
bm, True, rail_radius, rail_span,
|
||||
mathutils.Matrix.Translation((0, 0, monorail_sink))
|
||||
)
|
||||
|
||||
def create_straight_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
rail_length: float, rail_angle: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool) -> None:
|
||||
"""
|
||||
Add a straight rail.
|
||||
|
||||
The original point is same as `_add_rail_section()`.
|
||||
The start terminal of this straight will be placed in XZ panel.
|
||||
The expand direction is +Y.
|
||||
|
||||
If ordered is monorail, `rail_span` param will be ignored.
|
||||
|
||||
The rail angle is in degree unit and indicate how any angle this rail should rotated by its axis.
|
||||
It usually used to create side rail.
|
||||
"""
|
||||
# create section first
|
||||
create_rail_section(
|
||||
bm, is_monorail, rail_radius, rail_span,
|
||||
mathutils.Matrix.LocRotScale(
|
||||
None,
|
||||
mathutils.Euler((0, math.radians(rail_angle), 0), 'XYZ'),
|
||||
None
|
||||
)
|
||||
)
|
||||
|
||||
# get start edges
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
# extrude and get end edges
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_extrude(
|
||||
bm,
|
||||
start_edges,
|
||||
mathutils.Vector((0, rail_length, 0))
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
def create_transition_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
rail_radius: float, rail_span: float,
|
||||
rail_length: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool) -> None:
|
||||
"""
|
||||
Add a transition rail.
|
||||
|
||||
The original point is same as `_add_transition_section()`.
|
||||
The start terminal of this straight will be placed in XZ panel.
|
||||
The expand direction is +Y.
|
||||
"""
|
||||
# create section first
|
||||
create_transition_section(bm, rail_radius, rail_span)
|
||||
|
||||
# get start edges
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
# extrude and get end edges
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_extrude(
|
||||
bm,
|
||||
start_edges,
|
||||
mathutils.Vector((0, rail_length, 0))
|
||||
)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
def create_screw_rail(
|
||||
bm: bmesh.types.BMesh,
|
||||
is_monorail: bool, rail_radius: float, rail_span: float,
|
||||
rail_start_cap: bool, rail_end_cap: bool,
|
||||
rail_screw_angle: float, rail_screw_screw: float, rail_screw_iterations: int,
|
||||
rail_screw_steps: int, rail_screw_radius: float,
|
||||
rail_screw_flip_x: bool, rail_screw_flip_y: bool, rail_screw_flip_z: bool) -> None:
|
||||
"""
|
||||
Add a screw rail.
|
||||
|
||||
The original point is same as `_add_rail_section()`.
|
||||
The start terminal of this straight will be placed in XZ panel.
|
||||
The expand direction is +Y.
|
||||
|
||||
If ordered is monorail, `rail_span` param will be ignored.
|
||||
|
||||
Angle is input as degree unit.
|
||||
"""
|
||||
# create section first
|
||||
create_rail_section(bm, is_monorail, rail_radius, rail_span)
|
||||
|
||||
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
|
||||
end_edges: list[bmesh.types.BMEdge] = _bmesh_screw(
|
||||
bm,
|
||||
bm.verts[:], start_edges,
|
||||
rail_screw_angle,
|
||||
rail_screw_steps, rail_screw_iterations,
|
||||
mathutils.Vector((rail_screw_radius, 0, 0)),
|
||||
rail_screw_screw
|
||||
)
|
||||
|
||||
# flip geometry
|
||||
if rail_screw_flip_x or rail_screw_flip_y or rail_screw_flip_z:
|
||||
_bmesh_flip_all_faces(bm, rail_screw_flip_x, rail_screw_flip_y, rail_screw_flip_z)
|
||||
|
||||
# smooth geometry
|
||||
_bmesh_smooth_all_edges(bm)
|
||||
|
||||
# cap start and end edges if needed
|
||||
if rail_start_cap:
|
||||
_bmesh_cap(bm, start_edges)
|
||||
if rail_end_cap:
|
||||
_bmesh_cap(bm, end_edges)
|
||||
|
||||
#endregion
|
99
bbp_ng/UTIL_translation.py
Normal file
@ -0,0 +1,99 @@
|
||||
import bpy
|
||||
|
||||
#region Translation Contexts
|
||||
|
||||
## NOTE: Translation Priniciple
|
||||
# Due to the shitty design of Blender I18N tools (I can't specify context for every translation entries and its static analyse is so bad),
|
||||
# I will specify all context but I can't do anything if Blender throw my translation context away.
|
||||
#
|
||||
# BME module has its own context naming convention which make sure all configuration fields of prototypes are properly translated.
|
||||
# This module also provide a corresponding function to compute these context string from given BME prototype name and the index of configuration fields.
|
||||
#
|
||||
# For BBP plugin self, there is a list containing multiple priniciples which you should follow when providing translation context.
|
||||
# - For operator, menu, panel and etc.
|
||||
# * For themselves, fill `bl_translation_context` to their name, such as `BBP_OT_some_operator`.
|
||||
# * For properties located inside, add `/property` suffix, such as `BBP_OT_some_operator/property`.
|
||||
# * For draw function (any function callings requiring translation context inside it), add `/draw` suffix, such as `BBP_OT_some_operator/draw`.
|
||||
# * For execute function, or any functions mainly called by this execution function, add `/execute` suffix, such as `BBP_OT_some_operator/execute`.
|
||||
# - For shared class (usually shared by multiple operators).
|
||||
# * For themselves (usually not used because they don't have `bl_translation_context`), the default context is `BME/<MODULE_NAME>.<CLASS_NAME>`, such as `BBP/some_module.some_class`.
|
||||
# * For properties located inside, add `/property` suffix, such as `BBP/some_module.some_class/property`.
|
||||
# * For draw function (any function callings requiring translation context inside it), add `/draw` suffix, such as `BBP/some_module.some_class/draw`.
|
||||
# - For menu draw function (usually defined in __init__ module).
|
||||
# * For themselves (any calling inside them), the context is `BME/<MODULE_NAME>.<METHOD_NAME>()`, such as `BBP/some_module.some_method()`.
|
||||
#
|
||||
# Due to the shitty design, I can't find a way to add translation context for descrption field and report function used string.
|
||||
# So these description may collide with Blender official translation, thus they may not be put in result translation file.
|
||||
# I have no idea about this.
|
||||
#
|
||||
# Due to the shitty static analyse ability of Blender I18N plugin, all context should be written in literal,
|
||||
# not the reference to other fields or the return value of some function.
|
||||
# However for those strings, which originally should not be extracted by Blender I18N plugin, the way to get their context string is free.
|
||||
#
|
||||
#
|
||||
# For the string given to Python `print()` which will be output in console,
|
||||
# please use `bpy.app.translations.pgettext_rpt()` to get translation message because they are report.
|
||||
# For the string given to `UTIL_functions.message_box()`, please use `bpy.app.translations.pgettext_iface` because they are UI elements.
|
||||
#
|
||||
# `bpy.app.translations.pgettext` function family has fatal error when extracting message with context
|
||||
# (it will produce a correct one and a wrong one which just simply concat the message and its context. I don't know why).
|
||||
# This will happen if you put `bpy.app.translations.pgettext` calling inside `UILayout.label` or any UILayout functions like it.
|
||||
# `bpy.app.translations.pgettext` will produce the correct entry but Blender used "magic of `UILayout.label` will produce the wrong one.
|
||||
# It seems that Blender's magic just join the string provided in `text` argument as much as possible.
|
||||
# So the solution is simple:
|
||||
# - If we use `bpy.app.translations.pgettext` and `UILayout.label` together
|
||||
# * Create a variable holding the result of `bpy.app.translations.pgettext`.
|
||||
# * Format this gotten string if necessary.
|
||||
# * Call `UILayout.label` and use this variable as its `text` argument. Then set `translated` to False.
|
||||
# - If we use `bpy.app.translations.pgettext` with other non-Blender functions, such as `print`.
|
||||
# * Use it as a normal function.
|
||||
#
|
||||
# All translation annotation are started with `TR:`
|
||||
#
|
||||
|
||||
# The universal translation context prefix for BBP_NG plugin.
|
||||
CTX_BBP: str = 'BBP'
|
||||
|
||||
# The universal translation context prefix for BME module in BBP_NG plugin.
|
||||
CTX_BBP_BME: str = CTX_BBP + '/BME'
|
||||
def build_prototype_showcase_context(identifier: str) -> str:
|
||||
"""
|
||||
Build the context for getting the translation for BME prototype showcase title.
|
||||
@param[in] identifier The identifier of this prototype.
|
||||
@return The context for getting translation.
|
||||
"""
|
||||
return CTX_BBP_BME + '/' + identifier
|
||||
def build_prototype_showcase_cfg_context(identifier: str, cfg_index: int) -> str:
|
||||
"""
|
||||
Build the context for getting the translation for BME prototype showcase configuration title or description.
|
||||
@param[in] identifier The identifier of this prototype.
|
||||
@param[in] cfg_index The index of this configuration in this prototype showcase.
|
||||
@return The context for getting translation.
|
||||
"""
|
||||
return CTX_BBP_BME + f'/{identifier}/[{cfg_index}]'
|
||||
|
||||
#endregion
|
||||
|
||||
# ##### BEGIN AUTOGENERATED I18N SECTION #####
|
||||
# NOTE: You can safely move around this auto-generated block (with the begin/end markers!),
|
||||
# and edit the translations by hand.
|
||||
# Just carefully respect the format of the tuple!
|
||||
|
||||
# Tuple of tuples:
|
||||
# ((msgctxt, msgid), (sources, gen_comments), (lang, translation, (is_fuzzy, comments)), ...)
|
||||
translations_tuple = ()
|
||||
|
||||
translations_dict = {}
|
||||
for msg in translations_tuple:
|
||||
key = msg[0]
|
||||
for lang, trans, (is_fuzzy, comments) in msg[2:]:
|
||||
if trans and not is_fuzzy:
|
||||
translations_dict.setdefault(lang, {})[key] = trans
|
||||
|
||||
# ##### END AUTOGENERATED I18N SECTION #####
|
||||
|
||||
def register() -> None:
|
||||
bpy.app.translations.register(__package__, translations_dict)
|
||||
|
||||
def unregister() -> None:
|
||||
bpy.app.translations.unregister(__package__)
|
@ -1,5 +1,5 @@
|
||||
import mathutils
|
||||
import typing, sys
|
||||
import bpy, mathutils
|
||||
import typing, math
|
||||
from . import UTIL_functions
|
||||
|
||||
# extract all declarations in PyBMap
|
||||
@ -45,6 +45,7 @@ def vxmatrix_conv_co(self: VxMatrix) -> None:
|
||||
def vxmatrix_from_blender(self: VxMatrix, data_: mathutils.Matrix) -> None:
|
||||
"""
|
||||
Set matrix by blender matrix.
|
||||
Please note there is no corrdinate system convertion between
|
||||
"""
|
||||
# transposed first
|
||||
data: mathutils.Matrix = data_.transposed()
|
||||
@ -74,6 +75,39 @@ def vxmatrix_to_blender(self: VxMatrix) -> mathutils.Matrix:
|
||||
data.transpose()
|
||||
return data
|
||||
|
||||
## Hints about Light Matrix
|
||||
# There is a slight difference between Virtools and Blender.
|
||||
# In blender, the default direction of all directional light (spot and sun) are Down (-Z).
|
||||
# Hoewver, in Virtools, the default direction of all directional light (spot and directional) are Forward (+Z).
|
||||
#
|
||||
# As brief view, in Blender coordinate system, you can see that we got Blender default light direction
|
||||
# from Virtools default light direction by rotating it around X-axis with -90 degree
|
||||
# (the positive rotation direction is decided by right hand priniciple).
|
||||
#
|
||||
# So, if the object we importing is a light, we need add a special transform matrix ahead of world matrix
|
||||
# to correct the light default direction first.
|
||||
# When exporting, just do a reverse operation.
|
||||
|
||||
def bldmatrix_patch_light_obj(data: mathutils.Matrix) -> mathutils.Matrix:
|
||||
"""
|
||||
Add patch for light world matrix to correct its direction.
|
||||
This function is usually used when importing light.
|
||||
"""
|
||||
# we multiple a matrix which represent a 90 degree roration in X-axis.
|
||||
# right multiple means that it will apply to light first, before apply the matrix gotten from virtools.
|
||||
return data @ mathutils.Matrix.Rotation(math.radians(90), 4, 'X')
|
||||
|
||||
def bldmatrix_restore_light_obj(data: mathutils.Matrix) -> mathutils.Matrix:
|
||||
"""
|
||||
The reverse operation of bldmatrix_patch_light_mat().
|
||||
This function is usually used when exporting light.
|
||||
"""
|
||||
# as the reverse operation, we need right mutiple a reversed matrix of the matrix
|
||||
# which represent a 90 degree roration in X-axis to remove the matrix we added in patch step.
|
||||
# however, the reverse matrix of 90 degree X-axis rotation is just NEGATUIVE 90 degree X-axis rotation.
|
||||
# so we simply right multiple it.
|
||||
return data @ mathutils.Matrix.Rotation(math.radians(-90), 4, 'X')
|
||||
|
||||
#endregion
|
||||
|
||||
#region Blender EnumProperty Creation
|
||||
@ -189,6 +223,12 @@ _g_Annotation: dict[type, dict[int, EnumAnnotation]] = {
|
||||
VX_PIXELFORMAT._4_ABGR8888_CLUT.value: EnumAnnotation("4 Bits ABGR8888 CLUT", "4 bits indexed CLUT (ABGR) "),
|
||||
VX_PIXELFORMAT._4_ARGB8888_CLUT.value: EnumAnnotation("4 Bits ARGB8888 CLUT", "4 bits indexed CLUT (ARGB) "),
|
||||
},
|
||||
VXLIGHT_TYPE: {
|
||||
VXLIGHT_TYPE.VX_LIGHTPOINT.value: EnumAnnotation("Point", "The Light is a point of light "),
|
||||
VXLIGHT_TYPE.VX_LIGHTSPOT.value: EnumAnnotation("Spot", "The light is a spotlight "),
|
||||
VXLIGHT_TYPE.VX_LIGHTDIREC.value: EnumAnnotation("Directional", "The light is directional light : Lights comes from an infinite point so only direction of light can be given "),
|
||||
#VXLIGHT_TYPE.VX_LIGHTPARA.value: EnumAnnotation("Lightpara", "Obsolete, do not use "),
|
||||
},
|
||||
VXMESH_LITMODE: {
|
||||
VXMESH_LITMODE.VX_PRELITMESH.value: EnumAnnotation("Prelit", "Lighting use color information store with vertices "),
|
||||
VXMESH_LITMODE.VX_LITMESH.value: EnumAnnotation("Lit", "Lighting is done by renderer using normals and face material information. "),
|
||||
@ -223,16 +263,16 @@ class EnumPropHelper(UTIL_functions.EnumPropHelper):
|
||||
|
||||
def virtools_name_regulator(name: str | None) -> str:
|
||||
if name: return name
|
||||
else: return 'annoymous'
|
||||
else: return bpy.app.translations.pgettext_data('annoymous', 'BME/UTIL_virtools_types.virtools_name_regulator()')
|
||||
|
||||
## Default Encoding for PyBMap
|
||||
# Use semicolon split each encodings. Support Western European and Simplified Chinese in default.
|
||||
g_PyBMapDefaultEncoding: str
|
||||
if sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
|
||||
# See: https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
|
||||
g_PyBMapDefaultEncoding = "1252;936"
|
||||
else:
|
||||
# See: https://www.gnu.org/software/libiconv/
|
||||
g_PyBMapDefaultEncoding = "CP1252;CP936"
|
||||
# Since LibCmo 0.2, the encoding name of LibCmo become universal encoding which is platfoorm independent.
|
||||
# So no need set it according to different platform.
|
||||
# Use universal encoding name (like Python).
|
||||
g_PyBMapDefaultEncodings: tuple[str, ...] = (
|
||||
'cp1252',
|
||||
'gbk'
|
||||
)
|
||||
|
||||
#endregion
|
||||
|
@ -1,16 +1,3 @@
|
||||
bl_info = {
|
||||
"name": "Ballance Blender Plugin",
|
||||
"description": "Ballance mapping tools for Blender",
|
||||
"author": "yyc12345",
|
||||
"version": (4, 0),
|
||||
"blender": (3, 6, 0),
|
||||
"category": "Object",
|
||||
"support": "COMMUNITY",
|
||||
"warning": "Please read document before using this plugin.",
|
||||
"doc_url": "https://github.com/yyc12345/BallanceBlenderHelper",
|
||||
"tracker_url": "https://github.com/yyc12345/BallanceBlenderHelper/issues"
|
||||
}
|
||||
|
||||
#region Reload and Import
|
||||
|
||||
# import core lib
|
||||
@ -18,6 +5,8 @@ import bpy
|
||||
import typing, collections
|
||||
|
||||
# reload if needed
|
||||
# TODO: finish reload feature if needed.
|
||||
# (reload script raise too much exceptions so I usually restart blender to test my plugin.)
|
||||
if "bpy" in locals():
|
||||
import importlib
|
||||
|
||||
@ -29,37 +18,45 @@ from . import UTIL_icons_manager
|
||||
UTIL_icons_manager.register()
|
||||
|
||||
# then load other modules
|
||||
from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_group, PROP_ballance_element, PROP_bme_material
|
||||
from . import UTIL_translation
|
||||
from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_light, PROP_virtools_group
|
||||
from . import PROP_ballance_element, PROP_bme_material, PROP_ballance_map_info
|
||||
from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools
|
||||
from . import OP_UV_flatten_uv, OP_UV_rail_uv
|
||||
from . import OP_MTL_fix_material
|
||||
from . import OP_ADDS_component, OP_ADDS_bme, OP_ADDS_rail
|
||||
from . import OP_OBJECT_legacy_align, OP_OBJECT_virtools_group, OP_OBJECT_naming_convention
|
||||
from . import OP_OBJECT_legacy_align, OP_OBJECT_virtools_group, OP_OBJECT_snoop_group_then_to_mesh, OP_OBJECT_naming_convention
|
||||
|
||||
#region Menu
|
||||
|
||||
# ===== Menu Defines =====
|
||||
|
||||
class BBP_MT_View3DMenu(bpy.types.Menu):
|
||||
"""Ballance 3D Operators"""
|
||||
"""Ballance 3D related operators"""
|
||||
bl_idname = "BBP_MT_View3DMenu"
|
||||
bl_label = "Ballance"
|
||||
bl_translation_context = 'BBP_MT_View3DMenu'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text = 'UV', icon = 'UV')
|
||||
layout.label(text='UV', icon='UV', text_ctxt='BBP_MT_View3DMenu/draw')
|
||||
layout.operator(OP_UV_flatten_uv.BBP_OT_flatten_uv.bl_idname)
|
||||
layout.operator(OP_UV_rail_uv.BBP_OT_rail_uv.bl_idname)
|
||||
layout.separator()
|
||||
layout.label(text = 'Align', icon = 'SNAP_ON')
|
||||
layout.label(text='Align', icon='SNAP_ON', text_ctxt='BBP_MT_View3DMenu/draw')
|
||||
layout.operator(OP_OBJECT_legacy_align.BBP_OT_legacy_align.bl_idname)
|
||||
layout.separator()
|
||||
layout.label(text = 'Select', icon = 'SELECT_SET')
|
||||
layout.label(text='Select', icon='SELECT_SET', text_ctxt='BBP_MT_View3DMenu/draw')
|
||||
layout.operator(OP_OBJECT_virtools_group.BBP_OT_select_object_by_virtools_group.bl_idname)
|
||||
layout.separator()
|
||||
layout.label(text='Material', icon='MATERIAL', text_ctxt='BBP_MT_View3DMenu/draw')
|
||||
layout.operator(OP_MTL_fix_material.BBP_OT_fix_all_material.bl_idname)
|
||||
|
||||
class BBP_MT_AddBmeMenu(bpy.types.Menu):
|
||||
"""Add Ballance Floor"""
|
||||
bl_idname = "BBP_MT_AddBmeMenu"
|
||||
bl_label = "Floors"
|
||||
bl_translation_context = 'BBP_MT_AddBmeMenu'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
@ -69,48 +66,52 @@ class BBP_MT_AddRailMenu(bpy.types.Menu):
|
||||
"""Add Ballance Rail"""
|
||||
bl_idname = "BBP_MT_AddRailMenu"
|
||||
bl_label = "Rails"
|
||||
bl_translation_context = 'BBP_MT_AddRailMenu'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.label(text = "Sections", icon = 'MESH_CIRCLE')
|
||||
layout.label(text="Sections", icon='MESH_CIRCLE', text_ctxt='BBP_MT_AddRailMenu/draw')
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_rail_section.bl_idname)
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_transition_section.bl_idname)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Straight Rails", icon = 'IPO_CONSTANT')
|
||||
layout.label(text="Straight Rails", icon='IPO_CONSTANT', text_ctxt='BBP_MT_AddRailMenu/draw')
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_straight_rail.bl_idname)
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_transition_rail.bl_idname)
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_side_rail.bl_idname)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Curve Rails", icon = 'MOD_SCREW')
|
||||
layout.label(text="Curve Rails", icon='MOD_SCREW', text_ctxt='BBP_MT_AddRailMenu/draw')
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_arc_rail.bl_idname)
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_spiral_rail.bl_idname)
|
||||
layout.operator(OP_ADDS_rail.BBP_OT_add_side_spiral_rail.bl_idname)
|
||||
|
||||
class BBP_MT_AddComponentsMenu(bpy.types.Menu):
|
||||
"""Add Ballance Components"""
|
||||
"""Add Ballance Component"""
|
||||
bl_idname = "BBP_MT_AddComponentsMenu"
|
||||
bl_label = "Components"
|
||||
bl_translation_context = 'BBP_MT_AddComponentsMenu'
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.label(text = "Basic Components")
|
||||
layout.label(text="Basic Components", text_ctxt='BBP_MT_AddComponentsMenu/draw')
|
||||
OP_ADDS_component.BBP_OT_add_component.draw_blc_menu(layout)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Nong Components")
|
||||
layout.label(text="Nong Components", text_ctxt='BBP_MT_AddComponentsMenu/draw')
|
||||
OP_ADDS_component.BBP_OT_add_nong_extra_point.draw_blc_menu(layout)
|
||||
OP_ADDS_component.BBP_OT_add_nong_ventilator.draw_blc_menu(layout)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Series Components")
|
||||
layout.label(text="Series Components", text_ctxt='BBP_MT_AddComponentsMenu/draw')
|
||||
OP_ADDS_component.BBP_OT_add_tilting_block_series.draw_blc_menu(layout)
|
||||
OP_ADDS_component.BBP_OT_add_swing_series.draw_blc_menu(layout)
|
||||
OP_ADDS_component.BBP_OT_add_ventilator_series.draw_blc_menu(layout)
|
||||
|
||||
layout.separator()
|
||||
layout.label(text = "Components Pair")
|
||||
layout.label(text="Components Pair", text_ctxt='BBP_MT_AddComponentsMenu/draw')
|
||||
OP_ADDS_component.BBP_OT_add_sector_component_pair.draw_blc_menu(layout)
|
||||
|
||||
# ===== Menu Drawer =====
|
||||
@ -119,13 +120,29 @@ MenuDrawer_t = typing.Callable[[typing.Any, typing.Any], None]
|
||||
|
||||
def menu_drawer_import(self, context) -> None:
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
#layout.operator(OP_IMPORT_bmfile.BBP_OT_import_bmfile.bl_idname, text = "Ballance Map (.bmx)")
|
||||
layout.operator(OP_IMPORT_virtools.BBP_OT_import_virtools.bl_idname, text = "Virtools File (.nmo/.cmo/.vmo) (experimental)")
|
||||
# layout.operator(
|
||||
# OP_IMPORT_bmfile.BBP_OT_import_bmfile.bl_idname,
|
||||
# text="Ballance Map (.bmx)",
|
||||
# text_ctxt='BBP/__init__.menu_drawer_import()'
|
||||
# )
|
||||
layout.operator(
|
||||
OP_IMPORT_virtools.BBP_OT_import_virtools.bl_idname,
|
||||
text="Virtools File (.nmo/.cmo/.vmo) (experimental)",
|
||||
text_ctxt='BBP/__init__.menu_drawer_import()'
|
||||
)
|
||||
|
||||
def menu_drawer_export(self, context) -> None:
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
#layout.operator(OP_EXPORT_bmfile.BBP_OT_export_bmfile.bl_idname, text = "Ballance Map (.bmx)")
|
||||
layout.operator(OP_EXPORT_virtools.BBP_OT_export_virtools.bl_idname, text = "Virtools File (.nmo/.cmo/.vmo) (experimental)")
|
||||
# layout.operator(
|
||||
# OP_EXPORT_bmfile.BBP_OT_export_bmfile.bl_idname,
|
||||
# text="Ballance Map (.bmx)",
|
||||
# text_ctxt='BBP/__init__.menu_drawer_export()'
|
||||
# )
|
||||
layout.operator(
|
||||
OP_EXPORT_virtools.BBP_OT_export_virtools.bl_idname,
|
||||
text="Virtools File (.nmo/.cmo/.vmo) (experimental)",
|
||||
text_ctxt='BBP/__init__.menu_drawer_export()'
|
||||
)
|
||||
|
||||
def menu_drawer_view3d(self, context) -> None:
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
@ -134,7 +151,7 @@ def menu_drawer_view3d(self, context) -> None:
|
||||
def menu_drawer_add(self, context) -> None:
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
layout.separator()
|
||||
layout.label(text = "Ballance")
|
||||
layout.label(text="Ballance", text_ctxt='BBP/__init__.menu_drawer_add()')
|
||||
layout.menu(BBP_MT_AddBmeMenu.bl_idname, icon='MESH_CUBE')
|
||||
layout.menu(BBP_MT_AddRailMenu.bl_idname, icon='MESH_CIRCLE')
|
||||
layout.menu(BBP_MT_AddComponentsMenu.bl_idname, icon='MESH_ICOSPHERE')
|
||||
@ -150,10 +167,22 @@ def menu_drawer_grouping(self, context) -> None:
|
||||
col = layout.column()
|
||||
col.operator_context = 'INVOKE_DEFAULT'
|
||||
|
||||
col.label(text = "Virtools Group")
|
||||
col.operator(OP_OBJECT_virtools_group.BBP_OT_add_objects_virtools_group.bl_idname, icon = 'ADD', text = "Group into...")
|
||||
col.operator(OP_OBJECT_virtools_group.BBP_OT_rm_objects_virtools_group.bl_idname, icon = 'REMOVE', text = "Ungroup from...")
|
||||
col.operator(OP_OBJECT_virtools_group.BBP_OT_clear_objects_virtools_group.bl_idname, icon = 'TRASH', text = "Clear All Groups")
|
||||
col.label(text="Virtools Group", text_ctxt='BBP/__init__.menu_drawer_grouping()')
|
||||
col.operator(
|
||||
OP_OBJECT_virtools_group.BBP_OT_add_objects_virtools_group.bl_idname, icon='ADD', text="Group into...",
|
||||
text_ctxt='BBP/__init__.menu_drawer_grouping()')
|
||||
col.operator(
|
||||
OP_OBJECT_virtools_group.BBP_OT_rm_objects_virtools_group.bl_idname, icon='REMOVE', text="Ungroup from...",
|
||||
text_ctxt='BBP/__init__.menu_drawer_grouping()')
|
||||
col.operator(
|
||||
OP_OBJECT_virtools_group.BBP_OT_clear_objects_virtools_group.bl_idname, icon='TRASH', text="Clear All Groups",
|
||||
text_ctxt='BBP/__init__.menu_drawer_grouping()')
|
||||
|
||||
def menu_drawer_snoop_then_conv(self, context) -> None:
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
layout.separator()
|
||||
layout.label(text="Ballance", text_ctxt='BBP/__init__.menu_drawer_snoop_then_conv()')
|
||||
layout.operator(OP_OBJECT_snoop_group_then_to_mesh.BBP_OT_snoop_group_then_to_mesh.bl_idname, icon='OUTLINER_OB_MESH')
|
||||
|
||||
def menu_drawer_naming_convention(self, context) -> None:
|
||||
layout: bpy.types.UILayout = self.layout
|
||||
@ -163,10 +192,10 @@ def menu_drawer_naming_convention(self, context) -> None:
|
||||
col = layout.column()
|
||||
col.operator_context = 'INVOKE_DEFAULT'
|
||||
|
||||
col.label(text = "Ballance")
|
||||
col.operator(OP_OBJECT_naming_convention.BBP_OT_regulate_objects_name.bl_idname, icon = 'GREASEPENCIL')
|
||||
col.operator(OP_OBJECT_naming_convention.BBP_OT_auto_grouping.bl_idname, icon = 'GROUP')
|
||||
col.operator(OP_OBJECT_naming_convention.BBP_OT_convert_to_imengyu.bl_idname, icon = 'ARROW_LEFTRIGHT')
|
||||
col.label(text="Ballance", text_ctxt='BBP/__init__.menu_drawer_naming_convention()')
|
||||
col.operator(OP_OBJECT_naming_convention.BBP_OT_regulate_objects_name.bl_idname, icon='GREASEPENCIL')
|
||||
col.operator(OP_OBJECT_naming_convention.BBP_OT_auto_grouping.bl_idname, icon='GROUP')
|
||||
col.operator(OP_OBJECT_naming_convention.BBP_OT_convert_to_imengyu.bl_idname, icon='ARROW_LEFTRIGHT')
|
||||
|
||||
#endregion
|
||||
|
||||
@ -194,6 +223,8 @@ g_BldMenus: tuple[MenuEntry, ...] = (
|
||||
MenuEntry(bpy.types.TOPBAR_MT_file_export, True, menu_drawer_export),
|
||||
MenuEntry(bpy.types.VIEW3D_MT_add, True, menu_drawer_add),
|
||||
|
||||
MenuEntry(bpy.types.VIEW3D_MT_object_context_menu, True, menu_drawer_snoop_then_conv),
|
||||
|
||||
# register double (for 2 menus)
|
||||
MenuEntry(bpy.types.VIEW3D_MT_object_context_menu, True, menu_drawer_grouping),
|
||||
MenuEntry(bpy.types.OUTLINER_MT_object, True, menu_drawer_grouping),
|
||||
@ -203,15 +234,19 @@ g_BldMenus: tuple[MenuEntry, ...] = (
|
||||
|
||||
def register() -> None:
|
||||
# register module
|
||||
UTIL_translation.register()
|
||||
PROP_preferences.register()
|
||||
|
||||
PROP_ptrprop_resolver.register()
|
||||
|
||||
PROP_virtools_material.register()
|
||||
PROP_virtools_texture.register()
|
||||
PROP_virtools_mesh.register()
|
||||
PROP_virtools_light.register()
|
||||
PROP_virtools_group.register()
|
||||
PROP_ballance_element.register()
|
||||
PROP_bme_material.register()
|
||||
PROP_ballance_map_info.register()
|
||||
|
||||
OP_IMPORT_bmfile.register()
|
||||
OP_EXPORT_bmfile.register()
|
||||
@ -220,12 +255,16 @@ def register() -> None:
|
||||
|
||||
OP_UV_rail_uv.register()
|
||||
OP_UV_flatten_uv.register()
|
||||
|
||||
OP_MTL_fix_material.register()
|
||||
|
||||
OP_ADDS_component.register()
|
||||
OP_ADDS_bme.register()
|
||||
OP_ADDS_rail.register()
|
||||
|
||||
OP_OBJECT_legacy_align.register()
|
||||
OP_OBJECT_virtools_group.register()
|
||||
OP_OBJECT_snoop_group_then_to_mesh.register()
|
||||
OP_OBJECT_naming_convention.register()
|
||||
|
||||
# register other classes
|
||||
@ -250,12 +289,16 @@ def unregister() -> None:
|
||||
|
||||
# unregister modules
|
||||
OP_OBJECT_naming_convention.unregister()
|
||||
OP_OBJECT_snoop_group_then_to_mesh.unregister()
|
||||
OP_OBJECT_virtools_group.unregister()
|
||||
OP_OBJECT_legacy_align.unregister()
|
||||
|
||||
OP_ADDS_rail.unregister()
|
||||
OP_ADDS_bme.unregister()
|
||||
OP_ADDS_component.unregister()
|
||||
|
||||
OP_MTL_fix_material.unregister()
|
||||
|
||||
OP_UV_flatten_uv.unregister()
|
||||
OP_UV_rail_uv.unregister()
|
||||
|
||||
@ -264,15 +307,19 @@ def unregister() -> None:
|
||||
OP_EXPORT_bmfile.unregister()
|
||||
OP_IMPORT_bmfile.unregister()
|
||||
|
||||
PROP_ballance_map_info.unregister()
|
||||
PROP_bme_material.unregister()
|
||||
PROP_ballance_element.unregister()
|
||||
PROP_virtools_group.unregister()
|
||||
PROP_virtools_light.unregister()
|
||||
PROP_virtools_mesh.unregister()
|
||||
PROP_virtools_texture.unregister()
|
||||
PROP_virtools_material.unregister()
|
||||
|
||||
PROP_ptrprop_resolver.unregister()
|
||||
|
||||
PROP_preferences.unregister()
|
||||
UTIL_translation.unregister()
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
82
bbp_ng/blender_manifest.toml
Normal file
@ -0,0 +1,82 @@
|
||||
# Full context are copied from https://docs.blender.org/manual/en/dev/extensions/getting_started.html
|
||||
# Please note any update of this manifest
|
||||
|
||||
schema_version = "1.0.0"
|
||||
|
||||
# Example of manifest file for a Blender extension
|
||||
# Change the values according to your extension
|
||||
id = "bbp_ng"
|
||||
version = "4.2.0"
|
||||
name = "Ballance Blender Plugin"
|
||||
tagline = "The specialized add-on served for creating game map of Ballance"
|
||||
maintainer = "yyc12345 <yyc12321@outlook.com>"
|
||||
# Supported types: "add-on", "theme"
|
||||
type = "add-on"
|
||||
|
||||
# Optional link to documentation, support, source files, etc
|
||||
website = "https://github.com/yyc12345/BallanceBlenderHelper"
|
||||
|
||||
# Optional list defined by Blender and server, see:
|
||||
# https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html
|
||||
tags = ["Object", "Mesh", "UV", "Import-Export"]
|
||||
|
||||
blender_version_min = "4.2.0"
|
||||
# # Optional: Blender version that the extension does not support, earlier versions are supported.
|
||||
# # This can be omitted and defined later on the extensions platform if an issue is found.
|
||||
# blender_version_max = "5.1.0"
|
||||
|
||||
# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix)
|
||||
# https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html
|
||||
license = [
|
||||
"SPDX:GPL-3.0-or-later",
|
||||
]
|
||||
# Optional: required by some licenses.
|
||||
# copyright = [
|
||||
# "2002-2024 Developer Name",
|
||||
# "1998 Company Name",
|
||||
# ]
|
||||
|
||||
# Optional list of supported platforms. If omitted, the extension will be available in all operating systems.
|
||||
platforms = ["windows-x64", "linux-x64"]
|
||||
# Supported platforms: "windows-x64", "macos-arm64", "linux-x64", "windows-arm64", "macos-x64"
|
||||
|
||||
# Optional: bundle 3rd party Python modules.
|
||||
# https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html
|
||||
# wheels = [
|
||||
# "./wheels/hexdump-3.3-py3-none-any.whl",
|
||||
# "./wheels/jsmin-3.0.1-py3-none-any.whl",
|
||||
# ]
|
||||
|
||||
# Optional: add-ons can list which resources they will require:
|
||||
# * files (for access of any filesystem operations)
|
||||
# * network (for internet access)
|
||||
# * clipboard (to read and/or write the system clipboard)
|
||||
# * camera (to capture photos and videos)
|
||||
# * microphone (to capture audio)
|
||||
#
|
||||
# If using network, remember to also check `bpy.app.online_access`
|
||||
# https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access
|
||||
#
|
||||
# For each permission it is important to also specify the reason why it is required.
|
||||
# Keep this a single short sentence without a period (.) at the end.
|
||||
# For longer explanations use the documentation or detail page.
|
||||
|
||||
[permissions]
|
||||
# network = "Need to sync motion-capture data to server"
|
||||
files = "Import/export Virtools file from/to disk"
|
||||
# clipboard = "Copy and paste bone transforms"
|
||||
|
||||
# Optional: build settings.
|
||||
# https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build
|
||||
[build]
|
||||
paths_exclude_pattern = [
|
||||
"__pycache__/", # Python runtime cache
|
||||
".style.yapf", # Python code style
|
||||
"*.gitkeep", # Git directory keeper
|
||||
".gitignore", # Git Ignore File
|
||||
"*.md", # Useless document.
|
||||
"/raw_jsons", # Raw JSONs.
|
||||
"/raw_icons", # Raw Icons.
|
||||
"/tools", # Assistant tools.
|
||||
"/i18n", # GNU gettext Translation.
|
||||
]
|
5269
bbp_ng/i18n/zh_HANS.po
Normal file
@ -22,52 +22,61 @@
|
||||
"faces": [],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"identifier": "cv_triangle_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"edge_length": "5",
|
||||
"tip_offset": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[4], False, face[3], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "False",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, False, face[3], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(5, 0, 0) @ rot(0, 0, -270)"
|
||||
"transform": "move(5, 0, 0) @ rot(0, 0, 90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"identifier": "cv_triangle_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"edge_length": "5",
|
||||
"tip_offset": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[3], False, face[5], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "False",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, False, face[5], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(5, 5, 0) @ rot(0, 0, -180)"
|
||||
"transform": "move(5, 5, 0) @ rot(0, 0, 180)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"identifier": "cv_triangle_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"edge_length": "5",
|
||||
"tip_offset": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[5], False, face[2], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "False",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, False, face[2], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(0, 5, 0) @ rot(0, 0, -90)"
|
||||
"transform": "move(0, 5, 0) @ rot(0, 0, 270)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"identifier": "cv_triangle_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"edge_length": "5",
|
||||
"tip_offset": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[2], False, face[4], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "False",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, False, face[4], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(0, 0, 0)"
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "5",
|
||||
"width": "5"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -25,105 +25,23 @@
|
||||
}
|
||||
],
|
||||
"skip": "length == 0.0",
|
||||
"vars": [
|
||||
{
|
||||
"field": "uv_length",
|
||||
"data": "length / 5.0"
|
||||
},
|
||||
{
|
||||
"field": "uv_border_texture",
|
||||
"data": "1.0 if is_ribbon else 0.5"
|
||||
},
|
||||
{
|
||||
"field": "sink",
|
||||
"data": "0.7 if is_sink else 0.0"
|
||||
}
|
||||
],
|
||||
"vertices": [
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(0, 0, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(0, 2.5, -sink)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(length, 0, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(length, 2.5, -sink)"
|
||||
}
|
||||
],
|
||||
"faces": [
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"texture": "(\"FloorTopFlat\" if is_ribbon else \"FloorTopProfil\") if is_sink else \"FloorTopBorder\"",
|
||||
"indices": [0, 2, 3, 1],
|
||||
"uvs": [
|
||||
"(0, 0)",
|
||||
"(0, uv_length)",
|
||||
"(uv_border_texture, uv_length)",
|
||||
"(uv_border_texture, 0)"
|
||||
],
|
||||
"normals": null
|
||||
}
|
||||
],
|
||||
"vars": [],
|
||||
"vertices": [],
|
||||
"faces": [],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[4]",
|
||||
"identifier": "cv_trapezoid_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"long_edge_length": "length",
|
||||
"short_edge_offset": "0",
|
||||
"short_edge_length": "length",
|
||||
"height": "height",
|
||||
"length": "length",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "False"
|
||||
"face": "face",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "is_ribbon"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[2]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "2.5",
|
||||
"is_left_sink": "is_sink",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "move(0, 2.5, 0) @ rot(0, 0, -90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[3]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "2.5",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(length, 0, 0) @ rot(0, 0, 90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[5]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "length",
|
||||
"is_left_sink": "is_sink",
|
||||
"is_right_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(length, 2.5, 0) @ rot(0, 0, 180)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "length",
|
||||
"width": "2.5"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
414
bbp_ng/raw_jsons/chris_vanilla.json
Normal file
@ -0,0 +1,414 @@
|
||||
[
|
||||
{
|
||||
"identifier": "cv_trapezoid_side",
|
||||
"showcase": null,
|
||||
"params": [
|
||||
{
|
||||
"field": "long_edge_length",
|
||||
"data": "5.0"
|
||||
},
|
||||
{
|
||||
"field": "short_edge_offset",
|
||||
"data": "0.0"
|
||||
},
|
||||
{
|
||||
"field": "short_edge_length",
|
||||
"data": "5.0"
|
||||
},
|
||||
{
|
||||
"field": "height",
|
||||
"data": "5.0"
|
||||
},
|
||||
{
|
||||
"field": "face",
|
||||
"data": "(True, False, False, False, True, False)"
|
||||
},
|
||||
{
|
||||
"field": "is_sink",
|
||||
"data": "False"
|
||||
},
|
||||
{
|
||||
"field": "is_ribbon",
|
||||
"data": "False"
|
||||
}
|
||||
],
|
||||
"skip": "long_edge_length == 0.0 or short_edge_length == 0.0",
|
||||
"vars": [
|
||||
{
|
||||
"field": "long_edge_uv_length",
|
||||
"data": "long_edge_length / 5.0"
|
||||
},
|
||||
{
|
||||
"field": "short_edge_uv_offset",
|
||||
"data": "short_edge_offset / 5.0"
|
||||
},
|
||||
{
|
||||
"field": "short_edge_uv_length",
|
||||
"data": "short_edge_length / 5.0"
|
||||
},
|
||||
{
|
||||
"field": "short_edge_total",
|
||||
"data": "short_edge_offset + short_edge_length"
|
||||
},
|
||||
{
|
||||
"field": "short_edge_uv_total",
|
||||
"data": "short_edge_uv_offset + short_edge_uv_length"
|
||||
},
|
||||
{
|
||||
"field": "uv_border_texture",
|
||||
"data": "1.0 if is_ribbon else 0.5"
|
||||
},
|
||||
{
|
||||
"field": "sink",
|
||||
"data": "0.7 if is_sink else 0.0"
|
||||
}
|
||||
],
|
||||
"vertices": [
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(0, 0, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(short_edge_offset, 2.5, -sink)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(long_edge_length, 0, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(short_edge_total, 2.5, -sink)"
|
||||
}
|
||||
],
|
||||
"faces": [
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"texture": "(\"FloorTopFlat\" if is_ribbon else \"FloorTopProfil\") if is_sink else \"FloorTopBorder\"",
|
||||
"indices": [0, 2, 3, 1],
|
||||
"uvs": [
|
||||
"(0, 0)",
|
||||
"(0, long_edge_uv_length)",
|
||||
"(uv_border_texture, short_edge_uv_total)",
|
||||
"(uv_border_texture, short_edge_uv_offset)"
|
||||
],
|
||||
"normals": null
|
||||
}
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[4]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "long_edge_length",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[2]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "distance(0, 0, short_edge_offset, 2.5)",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "is_sink"
|
||||
},
|
||||
"transform": "rot(0, 0, angle(0, 0, short_edge_offset, 2.5)) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[3]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "distance(long_edge_length, 0, short_edge_total, 2.5)",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(long_edge_length, 0, 0) @ rot(0, 0, angle(long_edge_length, 0, short_edge_total, 2.5))"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[5]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "short_edge_length",
|
||||
"is_left_sink": "is_sink",
|
||||
"is_right_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(short_edge_offset, 2.5, 0) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_4edges_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"top_left_pos": "(0, 0)",
|
||||
"top_right_pos": "(short_edge_offset, 2.5)",
|
||||
"bottom_left_pos": "(long_edge_length, 0)",
|
||||
"bottom_right_pos": "(short_edge_total, 2.5)"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "cv_triangle_side",
|
||||
"showcase": null,
|
||||
"params": [
|
||||
{
|
||||
"field": "edge_length",
|
||||
"data": "5.0"
|
||||
},
|
||||
{
|
||||
"field": "tip_offset",
|
||||
"data": "2.5"
|
||||
},
|
||||
{
|
||||
"field": "height",
|
||||
"data": "5.0"
|
||||
},
|
||||
{
|
||||
"field": "face",
|
||||
"data": "(True, False, False, False, True, None)"
|
||||
},
|
||||
{
|
||||
"field": "is_sink",
|
||||
"data": "False"
|
||||
}
|
||||
],
|
||||
"skip": "edge_length == 0.0",
|
||||
"vars": [
|
||||
{
|
||||
"field": "edge_uv_length",
|
||||
"data": "edge_length / 5.0"
|
||||
},
|
||||
{
|
||||
"field": "tip_uv_offset",
|
||||
"data": "tip_offset / 5.0"
|
||||
},
|
||||
{
|
||||
"field": "sink",
|
||||
"data": "0.7 if is_sink else 0.0"
|
||||
}
|
||||
],
|
||||
"vertices": [
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(0, 0, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(tip_offset, 2.5, -sink)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(edge_length, 0, 0)"
|
||||
}
|
||||
],
|
||||
"faces": [
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"texture": "\"FloorTopProfil\" if is_sink else \"FloorTopBorder\"",
|
||||
"indices": [0, 2, 1],
|
||||
"uvs": [
|
||||
"(0, 0)",
|
||||
"(0, edge_uv_length)",
|
||||
"(0.5, tip_uv_offset)"
|
||||
],
|
||||
"normals": null
|
||||
}
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[4]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "edge_length",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[2]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "distance(0, 0, tip_offset, 2.5)",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "is_sink"
|
||||
},
|
||||
"transform": "rot(0, 0, angle(0, 0, tip_offset, 2.5)) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[3]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "distance(edge_length, 0, tip_offset, 2.5)",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(edge_length, 0, 0) @ rot(0, 0, angle(edge_length, 0, tip_offset, 2.5))"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_triangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "edge_length",
|
||||
"width": "2.5",
|
||||
"tip_offset": "tip_offset"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "cv_triangle_center",
|
||||
"showcase": null,
|
||||
"params": [
|
||||
{
|
||||
"field": "height",
|
||||
"data": "5.0"
|
||||
},
|
||||
{
|
||||
"field": "face",
|
||||
"data": "(True, False, False, False, True, None)"
|
||||
},
|
||||
{
|
||||
"field": "is_sink",
|
||||
"data": "False"
|
||||
}
|
||||
],
|
||||
"skip": "False",
|
||||
"vars": [
|
||||
{
|
||||
"field": "sink",
|
||||
"data": "0.7 if is_sink else 0.0"
|
||||
}
|
||||
],
|
||||
"vertices": [
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(0, 0, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(5, 0, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(2.5, 2.5, -sink)"
|
||||
},
|
||||
{
|
||||
"skip": "(not face[0]) or (not is_sink)",
|
||||
"data": "(2.5, 0, -sink)"
|
||||
}
|
||||
],
|
||||
"faces": [
|
||||
{
|
||||
"skip": "(not face[0]) or is_sink",
|
||||
"texture": "\"FloorTopFlat\"",
|
||||
"indices": [0, 1, 2],
|
||||
"uvs": [
|
||||
"(0, 0)",
|
||||
"(1, 0)",
|
||||
"(0.5, 0.5)"
|
||||
],
|
||||
"normals": null
|
||||
},
|
||||
{
|
||||
"skip": "(not face[0]) or (not is_sink)",
|
||||
"texture": "\"FloorTopProfil\"",
|
||||
"indices": [0, 3, 2],
|
||||
"uvs": [
|
||||
"(0, 0)",
|
||||
"(0.5, 0)",
|
||||
"(0.5, 0.5)"
|
||||
],
|
||||
"normals": null
|
||||
},
|
||||
{
|
||||
"skip": "(not face[0]) or (not is_sink)",
|
||||
"texture": "\"FloorTopProfil\"",
|
||||
"indices": [3, 1, 2],
|
||||
"uvs": [
|
||||
"(0.5, 0)",
|
||||
"(1, 0)",
|
||||
"(0.5, 0.5)"
|
||||
],
|
||||
"normals": null
|
||||
}
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[2]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "distance(0, 0, 2.5, 2.5)",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "is_sink"
|
||||
},
|
||||
"transform": "rot(0, 0, 45)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[3]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "distance(5, 0, 2.5, 2.5)",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(5, 0, 0) @ rot(0, 0, 135)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "(not face[4]) or (not is_sink)",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "2.5",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "True"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "(not face[4]) or (not is_sink)",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "2.5",
|
||||
"is_left_sink": "True",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "move(2.5, 0, 0)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "(not face[4]) or is_sink",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "5",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_triangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "5",
|
||||
"width": "2.5",
|
||||
"tip_offset": "2.5"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -135,7 +135,7 @@
|
||||
"transform": "move(2.5, 2.5, 0) @ rot(0, 0, 180)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_bottom",
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "2.5",
|
||||
|
@ -22,52 +22,57 @@
|
||||
"faces": [],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"identifier": "cv_triangle_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"edge_length": "5",
|
||||
"tip_offset": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[2], False, face[4], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "False",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, False, face[4], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_border",
|
||||
"identifier": "cv_triangle_center",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"length": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[3], face[4], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, False, face[3], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(2.5, 0, 0)"
|
||||
"transform": "move(5, 0, 0) @ rot(0, 0, 90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_border",
|
||||
"identifier": "cv_triangle_center",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"length": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[5], False, face[2], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, False, face[5], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(5, 5, 0) @ rot(0, 0, 180)"
|
||||
},
|
||||
{
|
||||
"identifier": "cv_triangle_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"edge_length": "5",
|
||||
"tip_offset": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], False, False, False, face[2], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(0, 5, 0) @ rot(0, 0, -90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"skip": "False",
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[3], False, face[5])",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "True",
|
||||
"is_ribbon": "False"
|
||||
"length": "5",
|
||||
"width": "5"
|
||||
},
|
||||
"transform": "move(2.5, 2.5, 0)"
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -94,40 +99,55 @@
|
||||
"faces": [],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_border",
|
||||
"identifier": "cv_triangle_center",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"length": "5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[5], face[4], face[2], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, False, face[4], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "cv_triangle_center",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], False, False, False, face[3], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(5, 0, 0) @ rot(0, 0, 90)"
|
||||
},
|
||||
{
|
||||
"identifier": "cv_triangle_center",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], False, False, False, face[5], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(5, 5, 0) @ rot(0, 0, 180)"
|
||||
},
|
||||
{
|
||||
"identifier": "cv_triangle_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"edge_length": "5",
|
||||
"tip_offset": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], False, False, False, face[2], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(0, 5, 0) @ rot(0, 0, -90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"skip": "False",
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[4], False, face[3])",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "True",
|
||||
"is_ribbon": "False"
|
||||
"length": "5",
|
||||
"width": "5"
|
||||
},
|
||||
"transform": "move(2.5, 2.5, 0) @ rot(0, 0, -90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[3], False, face[5])",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "True",
|
||||
"is_ribbon": "False"
|
||||
},
|
||||
"transform": "move(2.5, 2.5, 0)"
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -154,52 +174,53 @@
|
||||
"faces": [],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"identifier": "cv_triangle_center",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[5], False, face[2])",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "True",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, False, face[4], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(2.5, 2.5, 0) @ rot(0, 0, -270)"
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"identifier": "cv_triangle_center",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[2], False, face[4])",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "True",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, False, face[3], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(2.5, 2.5, 0) @ rot(0, 0, -180)"
|
||||
"transform": "move(5, 0, 0) @ rot(0, 0, 90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"identifier": "cv_triangle_center",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[4], False, face[3])",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "True",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, False, face[5], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(2.5, 2.5, 0) @ rot(0, 0, -90)"
|
||||
"transform": "move(5, 5, 0) @ rot(0, 0, 180)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"identifier": "cv_triangle_center",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[3], False, face[5])",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "True",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, False, face[2], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(2.5, 2.5, 0)"
|
||||
"transform": "move(0, 5, 0) @ rot(0, 0, -90)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "5",
|
||||
"width": "5"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -158,7 +158,7 @@
|
||||
"transform": "move(length, width, 0) @ rot(0, 0, 180)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_bottom",
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "length",
|
||||
|
@ -34,53 +34,61 @@
|
||||
"faces": [],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"identifier": "cv_trapezoid_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"long_edge_length": "length + 5",
|
||||
"short_edge_offset": "2.5",
|
||||
"short_edge_length": "length",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[4], False, face[3], False)",
|
||||
"face": "(face[0], False, False, False, face[4], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "False",
|
||||
"is_ribbon": "is_ribbon"
|
||||
},
|
||||
"transform": "move(5 + length, 0, 0) @ rot(0, 0, -270)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[3], False, face[5], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "False",
|
||||
"is_ribbon": "is_ribbon"
|
||||
},
|
||||
"transform": "move(5 + length, 5 + width, 0) @ rot(0, 0, -180)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[5], False, face[2], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "False",
|
||||
"is_ribbon": "is_ribbon"
|
||||
},
|
||||
"transform": "move(0, 5 + width, 0) @ rot(0, 0, -90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[2], False, face[4], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "False",
|
||||
"is_ribbon": "is_ribbon"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "cv_trapezoid_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"long_edge_length": "length + 5",
|
||||
"short_edge_offset": "2.5",
|
||||
"short_edge_length": "length",
|
||||
"height": "height",
|
||||
"face": "(face[0], False, False, False, face[5], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "is_ribbon"
|
||||
},
|
||||
"transform": "move(0, 5 + width, 0) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "cv_trapezoid_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"long_edge_length": "width + 5",
|
||||
"short_edge_offset": "2.5",
|
||||
"short_edge_length": "width",
|
||||
"height": "height",
|
||||
"face": "(face[0], False, False, False, face[3], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "is_ribbon"
|
||||
},
|
||||
"transform": "move(5 + length, 0, 0) @ rot(0, 0, 90)"
|
||||
},
|
||||
{
|
||||
"identifier": "cv_trapezoid_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"long_edge_length": "width + 5",
|
||||
"short_edge_offset": "2.5",
|
||||
"short_edge_length": "width",
|
||||
"height": "height",
|
||||
"face": "(face[0], False, False, False, face[2], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "is_ribbon"
|
||||
},
|
||||
"transform": "rot(0, 0, 90) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_flat",
|
||||
"skip": "False",
|
||||
@ -88,58 +96,19 @@
|
||||
"height": "height",
|
||||
"length": "length",
|
||||
"width": "width",
|
||||
"face": "(face[0], face[1], False, False, False, False)",
|
||||
"face": "(face[0], False, False, False, False, False)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(2.5, 2.5, 0)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_border",
|
||||
"skip": "False",
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "length",
|
||||
"face": "(face[0], face[1], False, False, face[4], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "is_ribbon"
|
||||
"length": "5 + length",
|
||||
"width": "5 + width"
|
||||
},
|
||||
"transform": "move(2.5, 0, 0)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_border",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "width",
|
||||
"face": "(face[0], face[1], False, False, face[2], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "is_ribbon"
|
||||
},
|
||||
"transform": "move(0, 2.5 + width, 0) @ rot(0, 0, -90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_border",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "length",
|
||||
"face": "(face[0], face[1], False, False, face[5], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "is_ribbon"
|
||||
},
|
||||
"transform": "move(2.5 + length, 5 + width, 0) @ rot(0, 0, -180)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_border",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "width",
|
||||
"face": "(face[0], face[1], False, False, face[3], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "is_ribbon"
|
||||
},
|
||||
"transform": "move(5 + length, 2.5, 0) @ rot(0, 0, -270)"
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -122,7 +122,142 @@
|
||||
"instances": []
|
||||
},
|
||||
{
|
||||
"identifier": "floor_bottom",
|
||||
"identifier": "floor_4edges_bottom",
|
||||
"showcase": null,
|
||||
"params": [
|
||||
{
|
||||
"field": "top_left_pos",
|
||||
"data": "(0.0, 0.0)"
|
||||
},
|
||||
{
|
||||
"field": "top_right_pos",
|
||||
"data": "(0.0, 5.0)"
|
||||
},
|
||||
{
|
||||
"field": "bottom_left_pos",
|
||||
"data": "(5.0, 0.0)"
|
||||
},
|
||||
{
|
||||
"field": "bottom_right_pos",
|
||||
"data": "(5.0, 5.0)"
|
||||
}
|
||||
],
|
||||
"skip": "False",
|
||||
"vars": [
|
||||
{
|
||||
"field": "top_left_uv",
|
||||
"data": "tuple(map(lambda x: x / 5.0, top_left_pos))"
|
||||
},
|
||||
{
|
||||
"field": "top_right_uv",
|
||||
"data": "tuple(map(lambda x: x / 5.0, top_right_pos))"
|
||||
},
|
||||
{
|
||||
"field": "bottom_left_uv",
|
||||
"data": "tuple(map(lambda x: x / 5.0, bottom_left_pos))"
|
||||
},
|
||||
{
|
||||
"field": "bottom_right_uv",
|
||||
"data": "tuple(map(lambda x: x / 5.0, bottom_right_pos))"
|
||||
}
|
||||
],
|
||||
"vertices": [
|
||||
{
|
||||
"skip": "False",
|
||||
"data": "top_left_pos + (0, )"
|
||||
},
|
||||
{
|
||||
"skip": "False",
|
||||
"data": "top_right_pos + (0, )"
|
||||
},
|
||||
{
|
||||
"skip": "False",
|
||||
"data": "bottom_left_pos + (0, )"
|
||||
},
|
||||
{
|
||||
"skip": "False",
|
||||
"data": "bottom_right_pos + (0, )"
|
||||
}
|
||||
],
|
||||
"faces": [
|
||||
{
|
||||
"skip": "False",
|
||||
"texture": "\"FloorTopBorderless\"",
|
||||
"indices": [0, 1, 3, 2],
|
||||
"uvs": [
|
||||
"top_left_uv",
|
||||
"top_right_uv",
|
||||
"bottom_right_uv",
|
||||
"bottom_left_uv"
|
||||
],
|
||||
"normals": null
|
||||
}
|
||||
],
|
||||
"instances": []
|
||||
},
|
||||
{
|
||||
"identifier": "floor_3edges_bottom",
|
||||
"showcase": null,
|
||||
"params": [
|
||||
{
|
||||
"field": "top_pos",
|
||||
"data": "(0.0, 0.0)"
|
||||
},
|
||||
{
|
||||
"field": "bottom_pos",
|
||||
"data": "(5.0, 0.0)"
|
||||
},
|
||||
{
|
||||
"field": "tip_pos",
|
||||
"data": "(2.5, 2.5)"
|
||||
}
|
||||
],
|
||||
"skip": "False",
|
||||
"vars": [
|
||||
{
|
||||
"field": "top_uv",
|
||||
"data": "tuple(map(lambda x: x / 5.0, top_pos))"
|
||||
},
|
||||
{
|
||||
"field": "bottom_uv",
|
||||
"data": "tuple(map(lambda x: x / 5.0, bottom_pos))"
|
||||
},
|
||||
{
|
||||
"field": "tip_uv",
|
||||
"data": "tuple(map(lambda x: x / 5.0, tip_pos))"
|
||||
}
|
||||
],
|
||||
"vertices": [
|
||||
{
|
||||
"skip": "False",
|
||||
"data": "top_pos + (0, )"
|
||||
},
|
||||
{
|
||||
"skip": "False",
|
||||
"data": "bottom_pos + (0, )"
|
||||
},
|
||||
{
|
||||
"skip": "False",
|
||||
"data": "tip_pos + (0, )"
|
||||
}
|
||||
],
|
||||
"faces": [
|
||||
{
|
||||
"skip": "False",
|
||||
"texture": "\"FloorTopBorderless\"",
|
||||
"indices": [0, 2, 1],
|
||||
"uvs": [
|
||||
"top_uv",
|
||||
"tip_uv",
|
||||
"bottom_uv"
|
||||
],
|
||||
"normals": null
|
||||
}
|
||||
],
|
||||
"instances": []
|
||||
},
|
||||
{
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"showcase": null,
|
||||
"params": [
|
||||
{
|
||||
@ -135,48 +270,55 @@
|
||||
}
|
||||
],
|
||||
"skip": "length == 0.0 or width == 0.0",
|
||||
"vars": [
|
||||
"vars": [],
|
||||
"vertices": [],
|
||||
"faces": [],
|
||||
"instances": [
|
||||
{
|
||||
"field": "uv_length",
|
||||
"data": "length / 5.0"
|
||||
"identifier": "floor_4edges_bottom",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"top_left_pos": "(0, 0)",
|
||||
"top_right_pos": "(0, width)",
|
||||
"bottom_left_pos": "(length, 0)",
|
||||
"bottom_right_pos": "(length, width)"
|
||||
},
|
||||
"transform": "ident()"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"field": "uv_width",
|
||||
"data": "width / 5.0"
|
||||
"identifier": "floor_triangle_bottom",
|
||||
"showcase": null,
|
||||
"params": [
|
||||
{
|
||||
"field": "length",
|
||||
"data": "5.0"
|
||||
},
|
||||
{
|
||||
"field": "width",
|
||||
"data": "2.5"
|
||||
},
|
||||
{
|
||||
"field": "tip_offset",
|
||||
"data": "2.5"
|
||||
}
|
||||
],
|
||||
"vertices": [
|
||||
"skip": "length == 0.0",
|
||||
"vars": [],
|
||||
"vertices": [],
|
||||
"faces": [],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "floor_3edges_bottom",
|
||||
"skip": "False",
|
||||
"data": "(0, 0, 0)"
|
||||
"params": {
|
||||
"top_pos": "(0, 0)",
|
||||
"bottom_pos": "(length, 0)",
|
||||
"tip_pos": "(tip_offset, width)"
|
||||
},
|
||||
{
|
||||
"skip": "False",
|
||||
"data": "(0, width, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "False",
|
||||
"data": "(length, 0, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "False",
|
||||
"data": "(length, width, 0)"
|
||||
"transform": "ident()"
|
||||
}
|
||||
],
|
||||
"faces": [
|
||||
{
|
||||
"skip": "False",
|
||||
"texture": "\"FloorTopBorderless\"",
|
||||
"indices": [0, 1, 3, 2],
|
||||
"uvs": [
|
||||
"(0, 0)",
|
||||
"(uv_width, 0)",
|
||||
"(uv_width, uv_length)",
|
||||
"(0, uv_length)"
|
||||
],
|
||||
"normals": null
|
||||
}
|
||||
],
|
||||
"instances": []
|
||||
]
|
||||
}
|
||||
]
|
@ -1,56 +1,4 @@
|
||||
[
|
||||
{
|
||||
"identifier": "raw_floor_straight",
|
||||
"showcase": null,
|
||||
"params": [
|
||||
{
|
||||
"field": "height",
|
||||
"data": "5.0"
|
||||
},
|
||||
{
|
||||
"field": "length",
|
||||
"data": "5.0"
|
||||
},
|
||||
{
|
||||
"field": "face",
|
||||
"data": "(True, False, False, False, True, True)"
|
||||
},
|
||||
{
|
||||
"field": "is_sink",
|
||||
"data": "False"
|
||||
}
|
||||
],
|
||||
"skip": "length == 0.0",
|
||||
"vars": [],
|
||||
"vertices": [],
|
||||
"faces": [],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_border",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"length": "length",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[2], face[3], face[4], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "False"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_border",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"length": "length",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[3], face[2], face[5], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_ribbon": "False"
|
||||
},
|
||||
"transform": "move(length, 5, 0) @ rot(0, 0, 180)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"identifier": "floor_normal_straight",
|
||||
"showcase": {
|
||||
@ -95,21 +43,98 @@
|
||||
"data": "face_"
|
||||
}
|
||||
],
|
||||
"skip": "False",
|
||||
"vars": [],
|
||||
"vertices": [],
|
||||
"faces": [],
|
||||
"skip": "length == 0",
|
||||
"vars": [
|
||||
{
|
||||
"field": "length_uv",
|
||||
"data": "length / 5.0"
|
||||
}
|
||||
],
|
||||
"vertices": [
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(0, 0, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(length, 0, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(length, 5, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(0, 5, 0)"
|
||||
}
|
||||
],
|
||||
"faces": [
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"texture": "\"FloorTopFlat\"",
|
||||
"indices": [0, 1, 2, 3],
|
||||
"uvs": [
|
||||
"(0, 0)",
|
||||
"(0, length_uv)",
|
||||
"(1, length_uv)",
|
||||
"(1, 0)"
|
||||
],
|
||||
"normals": null
|
||||
}
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_straight",
|
||||
"skip": "False",
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[2]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "5",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "rot(0, 0, 90) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[3]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "5",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "move(length, 0, 0) @ rot(0, 0, 90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[4]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "length",
|
||||
"face": "face",
|
||||
"is_sink": "False"
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[5]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "length",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "move(0, 5, 0) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "length",
|
||||
"width": "5"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -157,21 +182,140 @@
|
||||
"data": "face_"
|
||||
}
|
||||
],
|
||||
"skip": "False",
|
||||
"vars": [],
|
||||
"vertices": [],
|
||||
"faces": [],
|
||||
"skip": "length == 0",
|
||||
"vars": [
|
||||
{
|
||||
"field": "length_uv",
|
||||
"data": "length / 5.0"
|
||||
}
|
||||
],
|
||||
"vertices": [
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(0, 0, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(length, 0, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(length, 2.5, -0.7)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(0, 2.5, -0.7)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(length, 5, 0)"
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"data": "(0, 5, 0)"
|
||||
}
|
||||
],
|
||||
"faces": [
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"texture": "\"FloorTopProfil\"",
|
||||
"indices": [0, 1, 2, 3],
|
||||
"uvs": [
|
||||
"(0, 0)",
|
||||
"(0, length_uv)",
|
||||
"(0.5, length_uv)",
|
||||
"(0.5, 0)"
|
||||
],
|
||||
"normals": null
|
||||
},
|
||||
{
|
||||
"skip": "not face[0]",
|
||||
"texture": "\"FloorTopProfil\"",
|
||||
"indices": [3, 2, 4, 5],
|
||||
"uvs": [
|
||||
"(0.5, 0)",
|
||||
"(0.5, length_uv)",
|
||||
"(1, length_uv)",
|
||||
"(1, 0)"
|
||||
],
|
||||
"normals": null
|
||||
}
|
||||
],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_straight",
|
||||
"skip": "False",
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[2]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "2.5",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "True"
|
||||
},
|
||||
"transform": "rot(0, 0, 90) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[2]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "2.5",
|
||||
"is_left_sink": "True",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "move(0, 2.5, 0) @ rot(0, 0, 90) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[3]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "2.5",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "True"
|
||||
},
|
||||
"transform": "move(length, 0, 0) @ rot(0, 0, 90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[3]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "2.5",
|
||||
"is_left_sink": "True",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "move(length, 2.5, 0) @ rot(0, 0, 90)"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[4]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "length",
|
||||
"face": "face",
|
||||
"is_sink": "True"
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_side",
|
||||
"skip": "not face[5]",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"length": "length",
|
||||
"is_left_sink": "False",
|
||||
"is_right_sink": "False"
|
||||
},
|
||||
"transform": "move(0, 5, 0) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "length",
|
||||
"width": "5"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -22,28 +22,49 @@
|
||||
"faces": [],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"identifier": "cv_triangle_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"edge_length": "2.5",
|
||||
"tip_offset": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[2], face[3], face[4], False)",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "False",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, face[3], face[4], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "raw_floor_corner",
|
||||
"identifier": "cv_triangle_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"edge_length": "2.5",
|
||||
"tip_offset": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[5], False, face[2], face[3])",
|
||||
"is_sink": "is_sink",
|
||||
"is_inner": "False",
|
||||
"is_ribbon": "False"
|
||||
"face": "(face[0], False, False, face[3], face[5], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(0, 5, 0) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "cv_triangle_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"edge_length": "5",
|
||||
"tip_offset": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], False, False, False, face[2], None)",
|
||||
"is_sink": "is_sink"
|
||||
},
|
||||
"transform": "move(0, 5, 0) @ rot(0, 0, -90)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "2.5",
|
||||
"width": "5"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -177,7 +177,7 @@
|
||||
"transform": "move(5, 5, 0) @ rot(0, 0, 180)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_bottom",
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "5",
|
||||
@ -257,7 +257,7 @@
|
||||
"transform": "move(2.5, 5, 0) @ rot(0, 0, 180)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_bottom",
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "2.5",
|
||||
|
@ -54,7 +54,7 @@
|
||||
"data": "face_"
|
||||
}
|
||||
],
|
||||
"skip": "False",
|
||||
"skip": "length == 0.0 or width == 0.0",
|
||||
"vars": [],
|
||||
"vertices": [],
|
||||
"faces": [],
|
||||
@ -65,7 +65,7 @@
|
||||
"params": {
|
||||
"length": "length",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[2], face[3], face[4], False)"
|
||||
"face": "(face[0], False, face[2], face[3], face[4], False)"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
@ -75,9 +75,9 @@
|
||||
"params": {
|
||||
"length": "length",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[3], face[2], face[5], False)"
|
||||
"face": "(face[0], False, face[2], face[3], face[5], False)"
|
||||
},
|
||||
"transform": "move(length, 5 + width, 0) @ rot(0, 0, 180)"
|
||||
"transform": "move(0, 5 + width, 0) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_flat",
|
||||
@ -86,10 +86,19 @@
|
||||
"height": "height",
|
||||
"length": "length",
|
||||
"width": "width",
|
||||
"face": "(face[0], face[1], face[2], face[3], False, False)",
|
||||
"face": "(face[0], False, face[2], face[3], False, False)",
|
||||
"is_sink": "True"
|
||||
},
|
||||
"transform": "move(0, 2.5, 0)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "length",
|
||||
"width": "5 + width"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -137,38 +146,57 @@
|
||||
"data": "face_"
|
||||
}
|
||||
],
|
||||
"skip": "False",
|
||||
"skip": "width == 0.0",
|
||||
"vars": [],
|
||||
"vertices": [],
|
||||
"faces": [],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "floor_sink_outter_corner",
|
||||
"identifier": "cv_trapezoid_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"long_edge_length": "width + 5",
|
||||
"short_edge_offset": "2.5",
|
||||
"short_edge_length": "width",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[5], False, face[2], face[5])"
|
||||
"face": "(face[0], False, False, False, face[2], face[3])",
|
||||
"is_sink": "True",
|
||||
"is_ribbon": "False"
|
||||
},
|
||||
"transform": "move(0, 5 + width, 0) @ rot(0, 0, -90)"
|
||||
"transform": "rot(0, 0, 90) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_sink_outter_corner",
|
||||
"identifier": "cv_triangle_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"edge_length": "2.5",
|
||||
"tip_offset": "2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[2], face[5], face[4], False)"
|
||||
"face": "(face[0], False, False, face[3], face[4], None)",
|
||||
"is_sink": "True"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_sink_border",
|
||||
"identifier": "cv_triangle_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"edge_length": "2.5",
|
||||
"tip_offset": "2.5",
|
||||
"height": "height",
|
||||
"length": "width",
|
||||
"face": "(face[0], face[1], False, False, face[2], face[5])"
|
||||
"face": "(face[0], False, False, face[3], face[5], None)",
|
||||
"is_sink": "True"
|
||||
},
|
||||
"transform": "move(0, 2.5 + width, 0) @ rot(0, 0, -90)"
|
||||
"transform": "move(0, width + 5, 0) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "2.5",
|
||||
"width": "5 + width"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -227,46 +255,45 @@
|
||||
"data": "face_"
|
||||
}
|
||||
],
|
||||
"skip": "False",
|
||||
"skip": "width_h == 0.0 or width_v == 0.0",
|
||||
"vars": [],
|
||||
"vertices": [],
|
||||
"faces": [],
|
||||
"instances": [
|
||||
{
|
||||
"identifier": "floor_sink_outter_corner",
|
||||
"identifier": "cv_trapezoid_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"long_edge_length": "width_h + 5",
|
||||
"short_edge_offset": "2.5",
|
||||
"short_edge_length": "width_h + 2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[2], False, face[4], False)"
|
||||
"face": "(face[0], False, False, face[3], face[4], False)",
|
||||
"is_sink": "True",
|
||||
"is_ribbon": "False"
|
||||
},
|
||||
"transform": "ident()"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_sink_border",
|
||||
"identifier": "cv_trapezoid_side",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"length": "2.5 + width_h",
|
||||
"long_edge_length": "width_v + 5",
|
||||
"short_edge_offset": "2.5",
|
||||
"short_edge_length": "width_v + 2.5",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[3], face[4], False)"
|
||||
"face": "(face[0], False, False, face[5], face[2], False)",
|
||||
"is_sink": "True",
|
||||
"is_ribbon": "False"
|
||||
},
|
||||
"transform": "move(2.5, 0, 0)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_sink_border",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"length": "2.5 + width_v",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[5], False, face[2], False)"
|
||||
},
|
||||
"transform": "move(0, 5 + width_v, 0) @ rot(0, 0, -90)"
|
||||
"transform": "rot(0, 0, 90) @ scale(1, -1, 1)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_sink_inner_corner",
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[3], False, face[5])"
|
||||
"face": "(face[0], False, False, face[3], False, face[5])"
|
||||
},
|
||||
"transform": "move(2.5 + width_h, 2.5 + width_v, 0)"
|
||||
},
|
||||
@ -277,7 +304,7 @@
|
||||
"height": "height",
|
||||
"length": "width_h + 2.5",
|
||||
"width": "width_v",
|
||||
"face": "(face[0], face[1], False, face[3], False, False)",
|
||||
"face": "(face[0], False, False, face[3], False, False)",
|
||||
"is_sink": "True"
|
||||
},
|
||||
"transform": "move(2.5, 2.5, 0)"
|
||||
@ -289,10 +316,19 @@
|
||||
"height": "height",
|
||||
"length": "width_h",
|
||||
"width": "2.5",
|
||||
"face": "(face[0], face[1], False, False, False, face[5])",
|
||||
"face": "(face[0], False, False, False, False, face[5])",
|
||||
"is_sink": "True"
|
||||
},
|
||||
"transform": "move(2.5, 2.5 + width_v, 0)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "width_h + 5",
|
||||
"width": "width_v + 5"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -351,7 +387,7 @@
|
||||
"data": "face_"
|
||||
}
|
||||
],
|
||||
"skip": "False",
|
||||
"skip": "width_h == 0.0 or width_v == 0.0",
|
||||
"vars": [],
|
||||
"vertices": [],
|
||||
"faces": [],
|
||||
@ -362,7 +398,7 @@
|
||||
"params": {
|
||||
"length": "5 + width_v",
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], face[5], face[4], face[2], False)"
|
||||
"face": "(face[0], False, face[5], face[4], face[2], False)"
|
||||
},
|
||||
"transform": "move(0, 5 + width_v, 0) @ rot(0, 0, -90)"
|
||||
},
|
||||
@ -371,7 +407,7 @@
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[4], False, face[3])"
|
||||
"face": "(face[0], False, False, face[4], False, face[3])"
|
||||
},
|
||||
"transform": "move(2.5 + width_h, 2.5, 0) @ rot(0, 0, -90)"
|
||||
},
|
||||
@ -380,7 +416,7 @@
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[3], False, face[5])"
|
||||
"face": "(face[0], False, False, face[3], False, face[5])"
|
||||
},
|
||||
"transform": "move(2.5 + width_h, 2.5 + width_v, 0)"
|
||||
},
|
||||
@ -391,7 +427,7 @@
|
||||
"height": "height",
|
||||
"length": "width_h",
|
||||
"width": "5 + width_v",
|
||||
"face": "(face[0], face[1], False, False, face[4], face[5])",
|
||||
"face": "(face[0], False, False, False, face[4], face[5])",
|
||||
"is_sink": "True"
|
||||
},
|
||||
"transform": "move(2.5, 0, 0)"
|
||||
@ -403,10 +439,19 @@
|
||||
"height": "height",
|
||||
"length": "2.5",
|
||||
"width": "width_v",
|
||||
"face": "(face[0], face[1], False, face[3], False, False)",
|
||||
"face": "(face[0], False, False, face[3], False, False)",
|
||||
"is_sink": "True"
|
||||
},
|
||||
"transform": "move(2.5 + width_h, 2.5, 0)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "width_h + 5",
|
||||
"width": "width_v + 5"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -465,7 +510,7 @@
|
||||
"data": "face_"
|
||||
}
|
||||
],
|
||||
"skip": "False",
|
||||
"skip": "width_h == 0.0 or width_v == 0.0",
|
||||
"vars": [],
|
||||
"vertices": [],
|
||||
"faces": [],
|
||||
@ -475,7 +520,7 @@
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[5], False, face[2])"
|
||||
"face": "(face[0], False, False, face[5], False, face[2])"
|
||||
},
|
||||
"transform": "move(2.5, 2.5 + width_v, 0) @ rot(0, 0, -270)"
|
||||
},
|
||||
@ -484,7 +529,7 @@
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[2], False, face[4])"
|
||||
"face": "(face[0], False, False, face[2], False, face[4])"
|
||||
},
|
||||
"transform": "move(2.5, 2.5, 0) @ rot(0, 0, -180)"
|
||||
},
|
||||
@ -493,7 +538,7 @@
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[4], False, face[3])"
|
||||
"face": "(face[0], False, False, face[4], False, face[3])"
|
||||
},
|
||||
"transform": "move(2.5 + width_h, 2.5, 0) @ rot(0, 0, -90)"
|
||||
},
|
||||
@ -502,7 +547,7 @@
|
||||
"skip": "False",
|
||||
"params": {
|
||||
"height": "height",
|
||||
"face": "(face[0], face[1], False, face[3], False, face[5])"
|
||||
"face": "(face[0], False, False, face[3], False, face[5])"
|
||||
},
|
||||
"transform": "move(2.5 + width_h, 2.5 + width_v, 0)"
|
||||
},
|
||||
@ -513,7 +558,7 @@
|
||||
"height": "height",
|
||||
"length": "width_h",
|
||||
"width": "5 + width_v",
|
||||
"face": "(face[0], face[1], False, False, face[4], face[5])",
|
||||
"face": "(face[0], False, False, False, face[4], face[5])",
|
||||
"is_sink": "True"
|
||||
},
|
||||
"transform": "move(2.5, 0, 0)"
|
||||
@ -525,7 +570,7 @@
|
||||
"height": "height",
|
||||
"length": "2.5",
|
||||
"width": "width_v",
|
||||
"face": "(face[0], face[1], False, face[3], False, False)",
|
||||
"face": "(face[0], False, False, face[3], False, False)",
|
||||
"is_sink": "True"
|
||||
},
|
||||
"transform": "move(2.5 + width_h, 2.5, 0)"
|
||||
@ -537,10 +582,19 @@
|
||||
"height": "height",
|
||||
"length": "2.5",
|
||||
"width": "width_v",
|
||||
"face": "(face[0], face[1], face[2], False, False, False)",
|
||||
"face": "(face[0], False, face[2], False, False, False)",
|
||||
"is_sink": "True"
|
||||
},
|
||||
"transform": "move(0, 2.5, 0)"
|
||||
},
|
||||
{
|
||||
"identifier": "floor_rectangle_bottom",
|
||||
"skip": "not face[1]",
|
||||
"params": {
|
||||
"length": "width_h + 5",
|
||||
"width": "width_v + 5"
|
||||
},
|
||||
"transform": "move(0, 0, -height)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
8
bbp_ng/tools/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# Tools
|
||||
|
||||
These tool scripts located in this directory require following dependencies (version list in content is not mandatory. I just tell you that this version definitely works. You can upgrade or downgrade it as you wish if script still works.):
|
||||
|
||||
```
|
||||
pillow=10.2.0
|
||||
termcolor=2.2.0
|
||||
```
|
433
bbp_ng/tools/bme_relatives.py
Normal file
@ -0,0 +1,433 @@
|
||||
import typing
|
||||
import simple_po, bme_utils
|
||||
|
||||
#region Translation Constant
|
||||
|
||||
## TODO:
|
||||
# This translation context string prefix is cpoied from UTIL_translation.py.
|
||||
# If the context string of translation changed, please synchronize it.
|
||||
|
||||
CTX_TRANSLATION: str = 'BBP/BME'
|
||||
|
||||
#endregion
|
||||
|
||||
#region BME Tokens
|
||||
|
||||
## TODO:
|
||||
# These token are copied from UTIL_bme.py.
|
||||
# If anything changed, such as BME standard, these tokens should be synchronized between these 2 modules.
|
||||
|
||||
TOKEN_IDENTIFIER: str = 'identifier'
|
||||
|
||||
TOKEN_SHOWCASE: str = 'showcase'
|
||||
TOKEN_SHOWCASE_TITLE: str = 'title'
|
||||
TOKEN_SHOWCASE_ICON: str = 'icon'
|
||||
TOKEN_SHOWCASE_TYPE: str = 'type'
|
||||
TOKEN_SHOWCASE_CFGS: str = 'cfgs'
|
||||
TOKEN_SHOWCASE_CFGS_FIELD: str = 'field'
|
||||
TOKEN_SHOWCASE_CFGS_TYPE: str = 'type'
|
||||
TOKEN_SHOWCASE_CFGS_TITLE: str = 'title'
|
||||
TOKEN_SHOWCASE_CFGS_DESC: str = 'desc'
|
||||
TOKEN_SHOWCASE_CFGS_DEFAULT: str = 'default'
|
||||
|
||||
TOKEN_SKIP: str = 'skip'
|
||||
|
||||
TOKEN_PARAMS: str = 'params'
|
||||
TOKEN_PARAMS_FIELD: str = 'field'
|
||||
TOKEN_PARAMS_DATA: str = 'data'
|
||||
|
||||
TOKEN_VARS: str = 'vars'
|
||||
TOKEN_VARS_FIELD: str = 'field'
|
||||
TOKEN_VARS_DATA: str = 'data'
|
||||
|
||||
TOKEN_VERTICES: str = 'vertices'
|
||||
TOKEN_VERTICES_SKIP: str = 'skip'
|
||||
TOKEN_VERTICES_DATA: str = 'data'
|
||||
|
||||
TOKEN_FACES: str = 'faces'
|
||||
TOKEN_FACES_SKIP: str = 'skip'
|
||||
TOKEN_FACES_TEXTURE: str = 'texture'
|
||||
TOKEN_FACES_INDICES: str = 'indices'
|
||||
TOKEN_FACES_UVS: str = 'uvs'
|
||||
TOKEN_FACES_NORMALS: str = 'normals'
|
||||
|
||||
TOKEN_INSTANCES: str = 'instances'
|
||||
TOKEN_INSTANCES_IDENTIFIER: str = 'identifier'
|
||||
TOKEN_INSTANCES_SKIP: str = 'skip'
|
||||
TOKEN_INSTANCES_PARAMS: str = 'params'
|
||||
TOKEN_INSTANCES_TRANSFORM: str = 'transform'
|
||||
|
||||
#endregion
|
||||
|
||||
# TODO: finish BME validator
|
||||
|
||||
# class ReporterWithHierarchy():
|
||||
# """
|
||||
# BME validator and extractor specifically used reporter
|
||||
# which auotmatically use hierarchy as its context when outputing.
|
||||
# """
|
||||
|
||||
# __mReporter: bme_utils.Reporter
|
||||
# __mHierarchy: bme_utils.Hierarchy
|
||||
|
||||
# def __init__(self, reporter: bme_utils.Reporter, hierarchy: bme_utils.Hierarchy):
|
||||
# self.__mReporter = reporter
|
||||
# self.__mHierarchy = hierarchy
|
||||
|
||||
# def error(self, msg: str) -> None:
|
||||
# self.__mReporter.error(msg, self.__mHierarchy.build_hierarchy_string())
|
||||
# def warning(self, msg: str) -> None:
|
||||
# self.__mReporter.warning(msg, self.__mHierarchy.build_hierarchy_string())
|
||||
# def info(self, msg: str) -> None:
|
||||
# self.__mReporter.info(msg, self.__mHierarchy.build_hierarchy_string())
|
||||
|
||||
# class UniqueField():
|
||||
# """
|
||||
# Some BME prototype fields should be unique in globl scope.
|
||||
# So BME validator should check this. That's the feature this class provided.
|
||||
|
||||
# This class is an abstract class and should not be used directly.
|
||||
# Use child class please.
|
||||
# """
|
||||
|
||||
# __mUniques: set[str]
|
||||
# __mReporter: ReporterWithHierarchy
|
||||
|
||||
# def __init__(self, reporter: ReporterWithHierarchy):
|
||||
# self.__mUniques = set()
|
||||
# self.__mReporter = reporter
|
||||
|
||||
# def register(self, entry: str) -> bool:
|
||||
# """
|
||||
# @brief Try to register given entry in unique.
|
||||
# @details
|
||||
# If given entry is not presented in unique set, given entry will be inserted and return True.
|
||||
# If given entry is already available in unique set, this function will use reporter to output an error message and return False.
|
||||
# @param[in] entry The entry to be checked and inserted.
|
||||
# @return True if entry is unique, otherwise false.
|
||||
# """
|
||||
# if entry in self.__mUniques:
|
||||
# self.__mReporter.error(self._get_error_msg(entry))
|
||||
# return False
|
||||
# else:
|
||||
# self.__mUniques.add(entry)
|
||||
# return True
|
||||
|
||||
# def clear(self) -> None:
|
||||
# """
|
||||
# @brief Clear this unique set for further using.
|
||||
# """
|
||||
# self.__mUniques.clear()
|
||||
|
||||
# def _get_error_msg(self, err_entry: str) -> str:
|
||||
# """
|
||||
# @brief Get the error message when error occurs.
|
||||
# @details
|
||||
# This is internal used function to get the error message which will be passed to reporter.
|
||||
# This message is generated by given entry which cause the non-unique issue.
|
||||
# Outer caller should not call this function and every child class should override this function.
|
||||
# @param[in] err_entry The entry cause the error.
|
||||
# @return The error message generated from given error entry.
|
||||
# """
|
||||
# raise NotImplementedError()
|
||||
|
||||
# class UniqueIdentifier(UniqueField):
|
||||
# """Specific UniqueField for unique prototype identifier."""
|
||||
# def _get_error_msg(self, err_entry: str) -> str:
|
||||
# return f'Trying to register multiple prototype with same name: "{err_entry}".'
|
||||
# class UniqueVariable(UniqueField):
|
||||
# """Specific UniqueField for unique variable names within prototype."""
|
||||
# def _get_error_msg(self, err_entry: str) -> str:
|
||||
# return f'Trying to define multiple variable with same name: "{err_entry}" in the same prototype.'
|
||||
|
||||
# class BMEValidator():
|
||||
# """
|
||||
# The validator for BME prototype declarartions.
|
||||
# This validator will validate given prototype declaration JSON structure,
|
||||
# to check then whether have all essential fields BME standard required and whether have any unknown fields.
|
||||
# """
|
||||
|
||||
# __mHierarchy: bme_utils.Hierarchy
|
||||
# __mReporter: ReporterWithHierarchy
|
||||
|
||||
# __mUniqueIdentifier: UniqueIdentifier
|
||||
# __mUniqueVariable: UniqueVariable
|
||||
|
||||
# def __init__(self, reporter: bme_utils.Reporter):
|
||||
# self.__mHierarchy = bme_utils.Hierarchy()
|
||||
# self.__mReporter = ReporterWithHierarchy(reporter, self.__mHierarchy)
|
||||
|
||||
# self.__mUniqueIdentifier = UniqueIdentifier(self.__mReporter)
|
||||
# self.__mUniqueVariable = UniqueVariable(self.__mReporter)
|
||||
|
||||
# _TCheckKey = typing.TypeVar('_TCheckKey')
|
||||
# def __check_key(self, data: dict[str, typing.Any], key: str, expected_type: type[_TCheckKey]) -> _TCheckKey | None:
|
||||
# """
|
||||
# @brief Check the existance and tyoe of value stored in given dict and key.
|
||||
# @param[in] data The dict need to be checked
|
||||
# @param[in] key The key for fetching value.
|
||||
# @param[in] expected_type The expected type of fetched value.
|
||||
# @return None if error occurs, otherwise the value stored in given dict and key.
|
||||
# """
|
||||
# gotten_value = data[key]
|
||||
# if gotten_value is None:
|
||||
# # report no key error
|
||||
# self.__mReporter.error(f'Can not find key "{key}". Did you forget it?')
|
||||
# elif not isinstance(gotten_value, expected_type):
|
||||
# # get the type of value
|
||||
# value_type = type(gotten_value)
|
||||
# # format normal error message
|
||||
# err_msg: str = f'The type of value stored inside key "{key}" is incorrect. '
|
||||
# err_msg += f'Expect "{expected_type.__name__}" got "{value_type.__name__}". '
|
||||
# # add special note for easily confusing types
|
||||
# # e.g. forget quote number (number literal are recognise as number accidently)
|
||||
# if issubclass(expected_type, str) and issubclass(type(data), (int, float)):
|
||||
# err_msg += 'Did you forgot quote the number?'
|
||||
# # report type error
|
||||
# self.__mReporter.error(err_msg)
|
||||
# else:
|
||||
# # no error, return value
|
||||
# return gotten_value
|
||||
# # error occurs, return null
|
||||
# return None
|
||||
|
||||
# def __check_self(self, data: typing.Any, expected_type: type) -> bool:
|
||||
# """
|
||||
# @brief Check the type of given data.
|
||||
# @return True if type matched, otherwise false.
|
||||
# """
|
||||
# if data is None:
|
||||
# self.__mReporter.error('Data is unexpected null.')
|
||||
# elif not isinstance(data, expected_type):
|
||||
# # usually this function is checking list or dict, so no scenario that user forget quote literal number.
|
||||
# self.__mReporter.error(f'The type of given data is not expected. Expect "{expected_type.__name__}" got "{type(data).__name__}".')
|
||||
# else:
|
||||
# # no error, return okey
|
||||
# return True
|
||||
# # error occurs, return failed
|
||||
# return False
|
||||
|
||||
# # 按层次递归调用检查。
|
||||
# # 每个层次只负责当前层次的检查。
|
||||
# # 如果值为列表,字典,则在当前层次检查完其类型(容器本身,对每一项不检查),然后对每一项调用对应层次检查。
|
||||
# # 如果值不是上述类型(例如整数,浮点数,字符串等),在当前层次检查。
|
||||
|
||||
# def validate(self, assoc_file: str, prototypes: typing.Any) -> None:
|
||||
# # reset hierarchy
|
||||
# self.__mHierarchy.clear()
|
||||
# # start to validate
|
||||
# with self.__mHierarchy.safe_push(assoc_file):
|
||||
# self.__validate_prototypes(prototypes)
|
||||
|
||||
# def __validate_prototypes(self, prototypes: typing.Any) -> None:
|
||||
# # the most outer structure must be a list
|
||||
# if not self.__check_self(prototypes, list): return
|
||||
# cast_prototypes = typing.cast(list[typing.Any], prototypes)
|
||||
# # iterate prototype
|
||||
# for prototype_index, prototype in enumerate(cast_prototypes):
|
||||
# with self.__mHierarchy.safe_push(prototype_index) as layer:
|
||||
# self.__validate_prototype(layer, prototype)
|
||||
|
||||
# def __validate_prototype(self, layer: bme_utils.HierarchyLayer, prototype: typing.Any) -> None:
|
||||
# # check whether self is a dict
|
||||
# if not self.__check_self(prototype, dict): return
|
||||
# cast_prototype = typing.cast(dict[str, typing.Any], prototype)
|
||||
|
||||
# # clear unique field for each prototype
|
||||
# self.__mUniqueVariable.clear()
|
||||
|
||||
# # check identifier
|
||||
# identifier = self.__check_key(cast_prototype, TOKEN_IDENTIFIER, str)
|
||||
# if identifier is not None:
|
||||
# # replace hierarchy
|
||||
# layer.emplace(identifier)
|
||||
# # check unique
|
||||
# self.__mUniqueIdentifier.register(identifier)
|
||||
|
||||
# # check showcase but don't use check function
|
||||
# # because it is optional.
|
||||
# showcase = cast_prototype[TOKEN_SHOWCASE]
|
||||
# if showcase is not None:
|
||||
# # we only check non-template prototype
|
||||
# with self.__mHierarchy.safe_push(TOKEN_SHOWCASE):
|
||||
# self.__validate_showcase(typing.cast(dict[str, typing.Any], showcase))
|
||||
|
||||
# # check params, vars, vertices, faces, instances
|
||||
# # they are all list
|
||||
# params = self.__check_key(cast_prototype, TOKEN_PARAMS, list)
|
||||
# if params is not None:
|
||||
# cast_params = typing.cast(list[typing.Any], params)
|
||||
# with self.__mHierarchy.safe_push(TOKEN_PARAMS):
|
||||
# for param_index, param in enumerate(cast_params):
|
||||
# with self.__mHierarchy.safe_push(param_index):
|
||||
# self.__validate_param(param)
|
||||
|
||||
# vars = self.__check_key(cast_prototype, TOKEN_VARS, list)
|
||||
# if vars is not None:
|
||||
# cast_vars = typing.cast(list[typing.Any], vars)
|
||||
# with self.__mHierarchy.safe_push(TOKEN_VARS):
|
||||
# for var_index, var in enumerate(cast_vars):
|
||||
# with self.__mHierarchy.safe_push(var_index):
|
||||
# self.__validate_var(var)
|
||||
|
||||
# vertices = self.__check_key(cast_prototype, TOKEN_VERTICES, list)
|
||||
# if vertices is not None:
|
||||
# cast_vertices = typing.cast(list[typing.Any], vertices)
|
||||
# with self.__mHierarchy.safe_push(TOKEN_VERTICES):
|
||||
# for vertex_index, vertex in enumerate(cast_vertices):
|
||||
# with self.__mHierarchy.safe_push(vertex_index):
|
||||
# self.__validate_vertex(vertex)
|
||||
|
||||
# faces = self.__check_key(cast_prototype, TOKEN_FACES, list)
|
||||
# if faces is not None:
|
||||
# cast_faces = typing.cast(list[typing.Any], faces)
|
||||
# with self.__mHierarchy.safe_push(TOKEN_FACES):
|
||||
# for face_index, face in enumerate(cast_faces):
|
||||
# with self.__mHierarchy.safe_push(face_index):
|
||||
# self.__validate_face(face)
|
||||
|
||||
# instances = self.__check_key(cast_prototype, TOKEN_INSTANCES, list)
|
||||
# if instances is not None:
|
||||
# cast_instances = typing.cast(list[typing.Any], instances)
|
||||
# with self.__mHierarchy.safe_push(TOKEN_INSTANCES):
|
||||
# for instance_index, instance in enumerate(cast_instances):
|
||||
# with self.__mHierarchy.safe_push(instance_index):
|
||||
# self.__validate_instance(instance)
|
||||
|
||||
# def __validate_showcase(self, showcase: dict[str, typing.Any]) -> None:
|
||||
# pass
|
||||
|
||||
# def __validate_param(self, param: typing.Any) -> None:
|
||||
# # check whether self is a dict
|
||||
# if not self.__check_self(param, dict): return
|
||||
# cast_param = typing.cast(dict[str, typing.Any], param)
|
||||
|
||||
# # check field
|
||||
# field = self.__check_key(cast_param, TOKEN_PARAMS_FIELD, str)
|
||||
# if field is not None:
|
||||
# self.__mUniqueVariable.register(field)
|
||||
|
||||
# # check data
|
||||
# self.__check_key(cast_param, TOKEN_PARAMS_DATA, str)
|
||||
|
||||
# def __validate_var(self, var: typing.Any) -> None:
|
||||
# # check whether self is a dict
|
||||
# if not self.__check_self(var, dict): return
|
||||
# cast_var = typing.cast(dict[str, typing.Any], var)
|
||||
|
||||
# # check field
|
||||
# field = self.__check_key(cast_var, TOKEN_VARS_FIELD, str)
|
||||
# if field is not None:
|
||||
# self.__mUniqueVariable.register(field)
|
||||
|
||||
# # check data
|
||||
# self.__check_key(cast_var, TOKEN_VARS_DATA, str)
|
||||
|
||||
# def __validate_vertex(self, vertex: typing.Any) -> None:
|
||||
# # check whether self is a dict
|
||||
# if not self.__check_self(vertex, dict): return
|
||||
# cast_vertex = typing.cast(dict[str, typing.Any], vertex)
|
||||
|
||||
# # check fields
|
||||
# self.__check_key(cast_vertex, TOKEN_VERTICES_SKIP, str)
|
||||
# self.__check_key(cast_vertex, TOKEN_VERTICES_DATA, str)
|
||||
|
||||
# def __validate_face(self, face: typing.Any) -> None:
|
||||
# pass
|
||||
|
||||
# def __validate_instance(self, instance: typing.Any) -> None:
|
||||
# pass
|
||||
|
||||
|
||||
class BMEExtractor():
|
||||
"""
|
||||
A GetText extractor for BME prototype declarations.
|
||||
This extractor can extract all UI infomations which will be shown on Blender first.
|
||||
Then write them into caller given PO file. So that translator can translate them.
|
||||
|
||||
Blender default I18N plugin can not recognise these dynamic loaded content,
|
||||
so that's the reason why this class invented.
|
||||
|
||||
Please note all data should be validate first, then pass to this class.
|
||||
Otherwise it is undefined behavior.
|
||||
"""
|
||||
|
||||
__mAssocFile: str
|
||||
__mHierarchy: bme_utils.Hierarchy
|
||||
__mReporter: bme_utils.Reporter
|
||||
__mPoWriter: simple_po.PoWriter
|
||||
|
||||
def __init__(self, reporter: bme_utils.Reporter, po_writer: simple_po.PoWriter):
|
||||
self.__mAssocFile = ''
|
||||
self.__mHierarchy = bme_utils.Hierarchy()
|
||||
self.__mReporter = reporter
|
||||
self.__mPoWriter = po_writer
|
||||
|
||||
def __add_translation(self, msg: str) -> None:
|
||||
"""
|
||||
@brief Convenient internal translation adder.
|
||||
@details Add given message into PO file with auto generated hierarchy for translation context.
|
||||
@param[in] msg The message for translating.
|
||||
"""
|
||||
self.__mPoWriter.add_entry(
|
||||
msg,
|
||||
CTX_TRANSLATION + '/' + self.__mHierarchy.build_hierarchy_string(),
|
||||
# use associated file as extracted message to tell user where we extract it.
|
||||
# put file name in hierarchy is not proper (file path may be changed when moving prototype between them).
|
||||
self.__mAssocFile
|
||||
)
|
||||
|
||||
def __report_duplication_error(self) -> None:
|
||||
"""
|
||||
@brief Convenient internal function to report duplicated translation message issue.
|
||||
@details
|
||||
A convenient internal used function to report issue that
|
||||
the "title" field and "desc" field of the same showcase configuration entry have same content
|
||||
which may cause that generated PO file is illegal.
|
||||
"""
|
||||
self.__mReporter.error(
|
||||
'The content of "title" and "desc" can not be the same in one entry. Please modify one of them.',
|
||||
self.__mAssocFile + '/' + self.__mHierarchy.build_hierarchy_string()
|
||||
)
|
||||
|
||||
def extract(self, assoc_file: str, prototypes: list[dict[str, typing.Any]]) -> None:
|
||||
self.__mAssocFile = assoc_file
|
||||
for prototype in prototypes:
|
||||
self.__extract_prototype(prototype)
|
||||
|
||||
def __extract_prototype(self, prototype: dict[str, typing.Any]) -> None:
|
||||
# get identifier first
|
||||
identifier: str = prototype[TOKEN_IDENTIFIER]
|
||||
with self.__mHierarchy.safe_push(identifier):
|
||||
# get showcase node and only write PO file if it is not template prototype
|
||||
showcase: dict[str, typing.Any] | None = prototype[TOKEN_SHOWCASE]
|
||||
if showcase is not None:
|
||||
self.__extract_showcase(showcase)
|
||||
|
||||
def __extract_showcase(self, showcase: dict[str, typing.Any]) -> None:
|
||||
# export self name first
|
||||
self.__add_translation(showcase[TOKEN_SHOWCASE_TITLE])
|
||||
|
||||
# iterate cfgs
|
||||
cfgs: list[dict[str, typing.Any]] = showcase[TOKEN_SHOWCASE_CFGS]
|
||||
for cfg_index, cfg in enumerate(cfgs):
|
||||
self.__extract_showcase_cfg(cfg_index, cfg)
|
||||
|
||||
def __extract_showcase_cfg(self, index: int, cfg: dict[str, typing.Any]) -> None:
|
||||
# push cfg index
|
||||
with self.__mHierarchy.safe_push(index):
|
||||
# extract field title and description
|
||||
title: str = cfg[TOKEN_SHOWCASE_CFGS_TITLE]
|
||||
desc: str = cfg[TOKEN_SHOWCASE_CFGS_DESC]
|
||||
|
||||
# check duplication error
|
||||
# if "title" is equal to "desc" and they are not blank
|
||||
if title == desc and title != "":
|
||||
self.__report_duplication_error()
|
||||
|
||||
# export them respectively if they are not blank
|
||||
if title != "":
|
||||
self.__add_translation(title)
|
||||
if desc!= "":
|
||||
self.__add_translation(desc)
|
||||
|
144
bbp_ng/tools/bme_utils.py
Normal file
@ -0,0 +1,144 @@
|
||||
import typing
|
||||
import collections
|
||||
import termcolor
|
||||
|
||||
class Reporter():
|
||||
"""
|
||||
General reporter with context support for convenient logging.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def __report(self, type: str, msg: str, context: str | None, color: str) -> None:
|
||||
# build message
|
||||
strl: str = f'[{type}]'
|
||||
if context is not None:
|
||||
strl += f'[{context}]'
|
||||
strl += ' ' + msg
|
||||
# output with color
|
||||
termcolor.cprint(strl, color)
|
||||
|
||||
def error(self, msg: str, context: str | None = None) -> None:
|
||||
"""
|
||||
@brief Report an error.
|
||||
@param[in] msg The message to show.
|
||||
@param[in] context The context of this message, e.g. the file path. None if no context.
|
||||
"""
|
||||
self.__report('Error', msg, context, 'red')
|
||||
|
||||
def warning(self, msg: str, context: str | None = None) -> None:
|
||||
"""
|
||||
@brief Report a warning.
|
||||
@param[in] msg The message to show.
|
||||
@param[in] context The context of this message, e.g. the file path. None if no context.
|
||||
"""
|
||||
self.__report('Warning', msg, context, 'yellow')
|
||||
|
||||
def info(self, msg: str, context: str | None = None) -> None:
|
||||
"""
|
||||
@brief Report a info.
|
||||
@param[in] msg The message to show.
|
||||
@param[in] context The context of this message, e.g. the file path. None if no context.
|
||||
"""
|
||||
self.__report('Info', msg, context, 'white')
|
||||
|
||||
class Hierarchy():
|
||||
"""
|
||||
The hierarchy for BME validator and BME extractor.
|
||||
In BME validator, it build human-readable string representing the location where error happen.
|
||||
In BME extractor, it build the string used as the context of translation.
|
||||
"""
|
||||
|
||||
__mStack: collections.deque[str]
|
||||
|
||||
def __init__(self):
|
||||
self.__mStack = collections.deque()
|
||||
|
||||
def push(self, item: str | int) -> None:
|
||||
"""
|
||||
@brief Add an item into the top of this hierarchy.
|
||||
@details
|
||||
If given item is string, it will be push into hierarchy directly.
|
||||
If given item is integer, this function will treat it as a special case, the index.
|
||||
Function will push it into hierarchy after formatting it (add a pair of bracket around it).
|
||||
@param[in] item New added item.
|
||||
"""
|
||||
if isinstance(item, str):
|
||||
self.__mStack.append(item)
|
||||
elif isinstance(item, int):
|
||||
self.__mStack.append(f'[{item}]')
|
||||
else:
|
||||
raise Exception('Unexpected type of item when pushing into hierarchy.')
|
||||
|
||||
def pop(self) -> None:
|
||||
"""
|
||||
@brief Remove the top item from hierarchy
|
||||
"""
|
||||
self.__mStack.pop()
|
||||
|
||||
def safe_push(self, item: str | int) -> 'HierarchyLayer':
|
||||
"""
|
||||
@brief The safe version of push function.
|
||||
@return A with-context-supported instance which can make sure pushed item popped when leaving scope.
|
||||
"""
|
||||
return HierarchyLayer(self, item)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""
|
||||
@brief Clear this hierarchy.
|
||||
"""
|
||||
self.__mStack.clear()
|
||||
|
||||
def depth(self) -> int:
|
||||
"""
|
||||
@brief Return the depth of this hierarchy.
|
||||
@return The depth of this hierarchy.
|
||||
"""
|
||||
return len(self.__mStack)
|
||||
|
||||
def build_hierarchy_string(self) -> str:
|
||||
"""
|
||||
@brief Build the string which can represent this hierarchy.
|
||||
@details It just join every items with `/` as separator.
|
||||
@return The built string representing this hierarchy.
|
||||
"""
|
||||
return '/'.join(self.__mStack)
|
||||
|
||||
class HierarchyLayer():
|
||||
"""
|
||||
An with-context-supported class for Hierarchy which can automatically pop item when leaving scope.
|
||||
This is convenient for keeping the balance of Hierarchy (avoid programmer accidently forgetting to pop item).
|
||||
"""
|
||||
|
||||
__mHasPop: bool
|
||||
__mAssocHierarchy: Hierarchy
|
||||
|
||||
def __init__(self, assoc_hierarchy: Hierarchy, item: str | int):
|
||||
self.__mAssocHierarchy = assoc_hierarchy
|
||||
self.__mHasPop = False
|
||||
self.__mAssocHierarchy.push(item)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
def close(self) -> None:
|
||||
if not self.__mHasPop:
|
||||
self.__mAssocHierarchy.pop()
|
||||
self.__mHasPop = True
|
||||
|
||||
def emplace(self, new_item: str | int) -> None:
|
||||
"""
|
||||
@brief Replace the content of top item in-place.
|
||||
@details
|
||||
In some cases, caller need to replace the content of top item.
|
||||
For example, at the beginning, we only have index info.
|
||||
After validating something, we can fetching a more human-readable info, such as name,
|
||||
now we need replace the content of top item.
|
||||
@param[in] new_item The new content of top item.
|
||||
"""
|
||||
self.__mAssocHierarchy.pop()
|
||||
self.__mAssocHierarchy.push(new_item)
|
@ -1,33 +1,36 @@
|
||||
import os
|
||||
import bme_utils
|
||||
import common
|
||||
import PIL, PIL.Image
|
||||
|
||||
# the config for thumbnail
|
||||
g_ThumbnailSize: int = 16
|
||||
|
||||
def resize_image(src_file: str, dst_file: str) -> None:
|
||||
# open image
|
||||
src_image: PIL.Image.Image = PIL.Image.open(src_file)
|
||||
# create thumbnail
|
||||
src_image.thumbnail((g_ThumbnailSize, g_ThumbnailSize))
|
||||
# save to new file
|
||||
src_image.save(dst_file)
|
||||
class ThumbnailCreator():
|
||||
|
||||
def create_thumbnails() -> None:
|
||||
__mReporter: bme_utils.Reporter
|
||||
|
||||
def __init__(self):
|
||||
self.__mReporter = bme_utils.Reporter()
|
||||
|
||||
def run(self) -> None:
|
||||
self.__create_thumbnails()
|
||||
|
||||
def __create_thumbnails(self) -> None:
|
||||
# get folder path
|
||||
root_folder: str = os.path.dirname(os.path.dirname(__file__))
|
||||
root_folder: str = common.get_plugin_folder()
|
||||
|
||||
# prepare handler
|
||||
def folder_handler(src_folder: str, dst_folder: str) -> None:
|
||||
def folder_handler(rel_name: str, src_folder: str, dst_folder: str) -> None:
|
||||
# just create folder
|
||||
print(f'Creating Folder: {src_folder} -> {dst_folder}')
|
||||
self.__mReporter.info(f'Creating Folder: {src_folder} -> {dst_folder}')
|
||||
os.makedirs(dst_folder, exist_ok = True)
|
||||
def file_handler(src_file: str, dst_file: str) -> None:
|
||||
def file_handler(rel_name: str, src_file: str, dst_file: str) -> None:
|
||||
# skip non-image
|
||||
if not src_file.endswith('.png'): return
|
||||
# call thumbnail func
|
||||
print(f'Processing Thumbnail: {src_file} -> {dst_file}')
|
||||
resize_image(src_file, dst_file)
|
||||
self.__mReporter.info(f'Processing Thumbnail: {src_file} -> {dst_file}')
|
||||
self.__resize_image(src_file, dst_file)
|
||||
|
||||
# call common processor
|
||||
common.common_file_migrator(
|
||||
@ -37,7 +40,16 @@ def create_thumbnails() -> None:
|
||||
file_handler
|
||||
)
|
||||
|
||||
print('Done.')
|
||||
self.__mReporter.info('Building thumbnail done.')
|
||||
|
||||
def __resize_image(self, src_file: str, dst_file: str) -> None:
|
||||
# open image
|
||||
src_image: PIL.Image.Image = PIL.Image.open(src_file)
|
||||
# create thumbnail
|
||||
src_image.thumbnail((g_ThumbnailSize, g_ThumbnailSize))
|
||||
# save to new file
|
||||
src_image.save(dst_file)
|
||||
|
||||
if __name__ == '__main__':
|
||||
create_thumbnails()
|
||||
thumbnail_creator = ThumbnailCreator()
|
||||
thumbnail_creator.run()
|
||||
|
@ -1,32 +1,50 @@
|
||||
import os, json
|
||||
import os, json, typing
|
||||
import bme_utils, bme_relatives, simple_po
|
||||
import common
|
||||
|
||||
def compress_json(src_file: str, dst_file: str) -> None:
|
||||
with open(src_file, 'r', encoding = 'utf-8') as fr:
|
||||
with open(dst_file, 'w', encoding = 'utf-8') as fw:
|
||||
json.dump(
|
||||
json.load(fr), # load from src file
|
||||
fw,
|
||||
indent = None, # no indent. the most narrow style.
|
||||
separators = (',', ':'), # also for narrow style.
|
||||
sort_keys = False, # do not sort key
|
||||
)
|
||||
class JsonCompressor():
|
||||
|
||||
def create_compressed_jsons() -> None:
|
||||
__mReporter: bme_utils.Reporter
|
||||
__mPoWriter: simple_po.PoWriter
|
||||
# __mValidator: bme_relatives.BMEValidator
|
||||
__mExtractor: bme_relatives.BMEExtractor
|
||||
|
||||
def __init__(self):
|
||||
self.__mReporter = bme_utils.Reporter()
|
||||
self.__mPoWriter = simple_po.PoWriter(
|
||||
os.path.join(common.get_plugin_folder(), 'i18n', 'bme.pot'),
|
||||
'BME Prototypes'
|
||||
)
|
||||
# self.__mValidator = bme_relatives.BMEValidator(self.__mReporter)
|
||||
self.__mExtractor = bme_relatives.BMEExtractor(self.__mReporter, self.__mPoWriter)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
def close(self) -> None:
|
||||
self.__mPoWriter.close()
|
||||
|
||||
def run(self) -> None:
|
||||
self.__compress_jsons()
|
||||
|
||||
def __compress_jsons(self) -> None:
|
||||
# get folder path
|
||||
root_folder: str = os.path.dirname(os.path.dirname(__file__))
|
||||
root_folder: str = common.get_plugin_folder()
|
||||
|
||||
# prepare handler
|
||||
def folder_handler(src_folder: str, dst_folder: str) -> None:
|
||||
def folder_handler(rel_name: str, src_folder: str, dst_folder: str) -> None:
|
||||
# just create folder
|
||||
print(f'Creating Folder: {src_folder} -> {dst_folder}')
|
||||
self.__mReporter.info(f'Creating Folder: {src_folder} -> {dst_folder}')
|
||||
os.makedirs(dst_folder, exist_ok = True)
|
||||
def file_handler(src_file: str, dst_file: str) -> None:
|
||||
def file_handler(rel_name: str, src_file: str, dst_file: str) -> None:
|
||||
# skip non-json
|
||||
if not src_file.endswith('.json'): return
|
||||
# call compress func
|
||||
print(f'Processing Json: {src_file} -> {dst_file}')
|
||||
compress_json(src_file, dst_file)
|
||||
self.__mReporter.info(f'Processing JSON: {src_file} -> {dst_file}')
|
||||
self.__compress_json(rel_name, src_file, dst_file)
|
||||
|
||||
# call common processor
|
||||
common.common_file_migrator(
|
||||
@ -36,8 +54,31 @@ def create_compressed_jsons() -> None:
|
||||
file_handler
|
||||
)
|
||||
|
||||
print('Done.')
|
||||
self.__mReporter.info('Building JSON done.')
|
||||
|
||||
def __compress_json(self, rel_name: str, src_file: str, dst_file: str) -> None:
|
||||
# load data first
|
||||
loaded_prototypes: typing.Any
|
||||
with open(src_file, 'r', encoding = 'utf-8') as fr:
|
||||
loaded_prototypes = json.load(fr)
|
||||
|
||||
# validate loaded data
|
||||
# self.__mValidator.validate(rel_name, loaded_prototypes)
|
||||
|
||||
# extract translation
|
||||
self.__mExtractor.extract(rel_name, loaded_prototypes)
|
||||
|
||||
# save result
|
||||
with open(dst_file, 'w', encoding = 'utf-8') as fw:
|
||||
json.dump(
|
||||
loaded_prototypes, # loaded data
|
||||
fw,
|
||||
indent = None, # no indent. the most narrow style.
|
||||
separators = (',', ':'), # also for narrow style.
|
||||
sort_keys = False, # do not sort key
|
||||
)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
create_compressed_jsons()
|
||||
|
||||
with JsonCompressor() as json_compressor:
|
||||
json_compressor.run()
|
||||
|
@ -1,4 +1,12 @@
|
||||
import os, typing
|
||||
import os, typing, fnmatch, shutil
|
||||
|
||||
def get_plugin_folder() -> str:
|
||||
"""
|
||||
Get the absolute path to plugin root folder.
|
||||
|
||||
@return The absolute path to plugin root folder.
|
||||
"""
|
||||
return os.path.dirname(os.path.dirname(__file__))
|
||||
|
||||
def relative_to_folder(abs_path: str, src_parent: str, dst_parent: str) -> str:
|
||||
"""
|
||||
@ -19,8 +27,8 @@ def relative_to_folder(abs_path: str, src_parent: str, dst_parent: str) -> str:
|
||||
|
||||
def common_file_migrator(
|
||||
from_folder: str, to_folder: str,
|
||||
fct_proc_folder: typing.Callable[[str, str], None],
|
||||
fct_proc_file: typing.Callable[[str, str], None]) -> None:
|
||||
fct_proc_folder: typing.Callable[[str, str, str], None],
|
||||
fct_proc_file: typing.Callable[[str, str, str], None]) -> None:
|
||||
"""
|
||||
Common file migrator used by some build script.
|
||||
|
||||
@ -29,7 +37,9 @@ def common_file_migrator(
|
||||
`fct_proc_folder` is a function pointer from caller which handle folder migration in detail.
|
||||
`fct_proc_file` is same but handle file migration.
|
||||
|
||||
`fct_proc_folder` will receive 2 args. First is the source folder. Second is expected dest folder.
|
||||
`fct_proc_folder` will receive 2 args.
|
||||
First is a relative path presenting the folder we are processing which is usually used for printing to user.
|
||||
Second is the source folder. Third is expected dest folder.
|
||||
`fct_proc_file` is same, but receive the file path instead.
|
||||
Both of these function pointer should do the migration in detail. This function will only just iterate
|
||||
folder and give essential args and will not do any migration operations such as copying or moving.
|
||||
@ -47,11 +57,11 @@ def common_file_migrator(
|
||||
src_folder: str = os.path.join(root, name)
|
||||
dst_folder: str = relative_to_folder(src_folder, from_folder, to_folder)
|
||||
# call handler
|
||||
fct_proc_folder(src_folder, dst_folder)
|
||||
fct_proc_folder(name, src_folder, dst_folder)
|
||||
# iterate files
|
||||
for name in files:
|
||||
# prepare handler args
|
||||
src_file: str = os.path.join(root, name)
|
||||
dst_file: str = relative_to_folder(src_file, from_folder, to_folder)
|
||||
# call handler
|
||||
fct_proc_file(src_file, dst_file)
|
||||
fct_proc_file(name, src_file, dst_file)
|
||||
|
99
bbp_ng/tools/simple_po.py
Normal file
@ -0,0 +1,99 @@
|
||||
import typing
|
||||
import io
|
||||
import datetime
|
||||
|
||||
class PoWriter():
|
||||
"""
|
||||
The simple PO file writer.
|
||||
This class is just served for writing POT files.
|
||||
It may be convenient when exporting PO file for thoese whose format can not be parsed by formal tools.
|
||||
"""
|
||||
|
||||
__cEscapeCharsDict: typing.ClassVar[dict[str, str]] = {
|
||||
'\\': '\\\\',
|
||||
'"': '\\"',
|
||||
'\n': '\\n',
|
||||
'\t': '\\t',
|
||||
}
|
||||
__cEscapeCharsTable: typing.ClassVar[dict] = str.maketrans(__cEscapeCharsDict)
|
||||
__mPoFile: io.TextIOWrapper
|
||||
|
||||
def __init__(self, po_file_path: str, project_name: str):
|
||||
# open file
|
||||
self.__mPoFile = open(po_file_path, 'w', encoding = 'utf-8')
|
||||
# add default header
|
||||
self.__add_header(project_name)
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
self.close()
|
||||
|
||||
def close(self) -> None:
|
||||
self.__mPoFile.close()
|
||||
|
||||
def __write_line(self, val: str) -> None:
|
||||
self.__mPoFile.write(val)
|
||||
self.__mPoFile.write('\n')
|
||||
|
||||
def __escape_str(self, val: str) -> str:
|
||||
"""
|
||||
This function escapes a given string to make it safe to use as a C++ string literal.
|
||||
@param[in] val Original string
|
||||
@return Escaped string
|
||||
"""
|
||||
return val.translate(PoWriter.__cEscapeCharsTable)
|
||||
|
||||
def __add_header(self, project_name: str) -> None:
|
||||
"""
|
||||
Add default header for PO file.
|
||||
@param[in] project_name The project name written in file.
|
||||
"""
|
||||
now_datetime = datetime.datetime.now()
|
||||
self.__write_line('# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.')
|
||||
self.__write_line('msgid ""')
|
||||
self.__write_line('msgstr ""')
|
||||
self.__write_line(f'"Project-Id-Version: {self.__escape_str(project_name)}\\n"')
|
||||
self.__write_line(f'"POT-Creation-Date: {now_datetime.strftime("%Y-%m-%d %H:%M%Z")}\\n"')
|
||||
self.__write_line('"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n"')
|
||||
self.__write_line('"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n"')
|
||||
self.__write_line('"Language-Team: LANGUAGE <LL@li.org>\\n"')
|
||||
self.__write_line('"Language: __POT__\\n"')
|
||||
self.__write_line('"MIME-Version: 1.0\\n"')
|
||||
self.__write_line('"Content-Type: text/plain; charset=UTF-8\\n"')
|
||||
self.__write_line('"Content-Transfer-Encoding: 8bit\\n"')
|
||||
self.__write_line('"X-Generator: simple_po.PoWriter\\n"')
|
||||
|
||||
def add_entry(self, msg: str, msg_context: str | None = None, extracted_comment: str | None = None, reference: str | None = None) -> None:
|
||||
"""
|
||||
@brief Write an entry into PO file with given arguments.
|
||||
@details
|
||||
Please note this function will NOT check whether there already is a duplicated entry which has been written.
|
||||
You must check this on your own.
|
||||
@param[in] msg The message string need to be translated.
|
||||
@param[in] msg_context The context of this message.
|
||||
@param[in] extracted_comment The extracted comment of this message. None if no reference. Line breaker is not allowed.
|
||||
@param[in] reference The code refernece of this message. None if no reference. Line breaker is not allowed.
|
||||
"""
|
||||
# empty string will not be translated
|
||||
if msg == '': return
|
||||
|
||||
# write blank line first
|
||||
self.__write_line('')
|
||||
if extracted_comment:
|
||||
self.__write_line(f'#. {extracted_comment}')
|
||||
if reference:
|
||||
self.__write_line(f'#: {reference}')
|
||||
if msg_context:
|
||||
self.__write_line(f'msgctxt "{self.__escape_str(msg_context)}"')
|
||||
self.__write_line(f'msgid "{self.__escape_str(msg)}"')
|
||||
self.__write_line('msgstr ""')
|
||||
|
||||
def build_code_reference(self, code_file_path: str, code_line_number: int) -> str:
|
||||
"""
|
||||
A convenient function to build code reference string used when adding entry.
|
||||
@param[in] code_file_path The path to associated code file.
|
||||
@param[in] code_line_number The line number of associated code within given file.
|
||||
"""
|
||||
return f'{code_file_path}:{code_line_number}'
|
2
docs/.gitignore
vendored
@ -1,2 +1,2 @@
|
||||
# mkdocs
|
||||
# ===== MkDocs =====
|
||||
site/
|
||||
|
29
docs/docs/en/ballance-properties.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Ballance Properties
|
||||
|
||||
The Ballance attributes are distinct from the Virtools attributes, which are a set of attributes dedicated to Ballance mapping. These properties are hosted in the scene and do not change within the same scene (mapping does not involve scene switching in Blender). The panels related to the Ballance property can be found in the `Scene` properties panel, as shown below, and they are:
|
||||
|
||||
* `Ballance Elements` panel (red arrows), corresponding to Ballance elements
|
||||
* `BME Materials` panel (green arrow), corresponds to BME materials.
|
||||
* `Ballance Map` panel (blue arrow), which corresponds to the Ballance map information.
|
||||
|
||||
Among these, only the Ballance Map information is the one you need to focus on, the other attributes are not normally of interest, except in the case of errors in some of the materials or meshes in the map.
|
||||
|
||||

|
||||
|
||||
## Ballance Map Information
|
||||
|
||||
Ballance Map infomation currently has only one option, Sector (map sector count). This attribute indicates the final desired number of sectors for the current map. This attribute is mainly used to work around the bug of exporting map, see the [Import and Export Virtools Document](./import-export-virtools.md) section for details on the bug.
|
||||
|
||||
The only thing you need to do is to check if this field is the number of sectors you expect before exporting the final map. Note that although you can set this field at the beginning of creating your map, there are other features in BBP that may modify this field, such as adding elements, importing Virtools documents, etc. For example, if you have a map with sector 3 specified and you are adding an element that belongs to sector 4, this value will automatically increase to 4. Similarly, when importing a Ballance map with a total of 4 sectors, this value will also increase to 4 (if the previous value was less than 4). The main reason for doing this is so that users can use this plugin without perceiving this value, especially when making slight modifications to some existing maps. Doing so, however, may not meet the user's needs in some cases, so it is still recommended that you check this field before exporting.
|
||||
|
||||
## Ballance Elements
|
||||
|
||||
Ballance Elements keep a record of the meshes of all the elements you have added using the BBP Add Elements feature. Meshes generally take up the largest amount of data in a 3D file, so reducing the number of meshes, i.e. by sharing meshes between objects of the same shape, can drastically reduce the size of the map file. When you use BBP to add an element-related function, it will first try to get the element's mesh from here, and if there is no corresponding mesh, it will be loaded and recorded here.
|
||||
|
||||
This panel is for viewing only, not editing. If you have accidentally modified the meshes of a BBP-added element (which should not have been modified in the first place) and want to restore its original shape, just click `Reset Ballance Elements` to reset all the element meshes in the list to their correct state.
|
||||
|
||||
## BME Materials
|
||||
|
||||
Similar to the Ballance Elements, records the materials used when adding floor using BME. This is also designed so that materials can be reused, so that you don't need to create a set of materials associated with each object you create, greatly reducing the number of duplicate materials.
|
||||
|
||||
This panel is for viewing only, not editing. When you accidentally modify BME related materials (which should not have been modified) and want to restore them, just click `Reset BME Materials` to reset all materials in the list to their correct state.
|
36
docs/docs/en/bme-adder.md
Normal file
@ -0,0 +1,36 @@
|
||||
# Add Floor
|
||||
|
||||
!!! info "Not latest version"
|
||||
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
|
||||
|
||||
## Start Generating
|
||||
|
||||
In the 3D view, click `Add - Floors` to expand the Add Floors menu. The menu is shown below.
|
||||
|
||||

|
||||
|
||||
Click on the menu to see all supported floor types in the submenu that pops up. Their names and icons hint at the style and shape of the floor it is intended to create.
|
||||
|
||||
!!! info "BME is extensible"
|
||||
BME's floor adder is extensible, each item in the menu is actually described by a set of JSON data. You can read the [Technical Information](./tech-infos.md) section to learn how we write this JSON, and you can even expand the types of floors that BME can create to suit your needs.
|
||||
|
||||
## Configure Floor
|
||||
|
||||
Clicking on one of the floor types will open the floor creation dialog, here we are showing a Normal Platform as shown below. In the dialog, we can configure various properties of this floor type, such as the length, width, height, distance, and whether the surface is displayed or not, to customize the geometry it generates so that it meets our requirements.
|
||||
|
||||

|
||||
|
||||
In the Normal Platform dialog, we can first see that it asks us to provide the length and width of the floor, which determines the size of our platform, and there is a text description to help you understand what this property controls.
|
||||
|
||||
Then it also asks us to provide the height of the platform, which defaults to 5, which is the default height of the floor in Ballance. Anything less than 5 creates a thin floor similar to the one in the "The Devil Dragon" map, and anything greater than 5 creates a very high floor wall similar to the one in the "Exaggeratedly Dense Space Station" map.
|
||||
|
||||
Finally, it tells us which sides of the floor we need to configure to display. Note that Top and Bottom are the top and bottom surfaces along the height direction (Z axis), while Front, Back, Left, and Right are the front, back, left, and right surfaces when looking down with your head on the -X axis and your eyes on the -Z axis. You may notice that there is a perspective cube in the center of these six face buttons, and in fact the positions of these six face options correspond to the positions of the six faces of this perspective cube.
|
||||
|
||||
## Tips
|
||||
|
||||
Each floor type has a different number of configuration entries, so for different floor types, you will need to follow the configuration hint text to understand what the corresponding configuration does. Some floor types may have a large number of configuration entries, while others may have no configuration entries at all.
|
||||
|
||||
The default values for the floor type configuration are set to the values that were most commonly used when the floor was created. The values are reset to the defaults each time the floor type is switched or recreated.
|
||||
|
||||
|
||||
|
44
docs/docs/en/compile-distribute-plugin.md
Normal file
@ -0,0 +1,44 @@
|
||||
# Compile and Distribute Plugin
|
||||
|
||||
!!! info "Not latest version"
|
||||
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
|
||||
|
||||
This page will guide you in compiling the plugin as well as distributing it.
|
||||
|
||||
## Compiling LibCmo with BMap
|
||||
|
||||
BBP's Virtools file native import/export functionality relies on BMap and its Python binding PyBMap. In order to distribute the plugin, we need to first compile BMap and its predecessor LibCmo, and before doing so, you need to check the version of BMap you need. Because BBP doesn't always use the latest version of BMap, e.g. if you're compiling an older version of BBP, it's obviously not possible to rely on the latest version of BMap. BMap is constantly being upgraded, and the functionality it provides is constantly changing, and different versions of BMap are incompatible. BBP usually states the version of BMap it uses at the time of release, but if BBP doesn't point it out, you may need to look for the most recent version of BMap that compiles with the version of BBP at the time of its release.
|
||||
|
||||
After specifying the version, you need to visit [LibCmo GitHub repository](https://github.com/yyc12345/libcmo21). Then clone the project and use the Git command to go to the corresponding version (or just download the source code of the corresponding version). Then follow LibCmo's compilation manual to compile to get BMap. on Windows, you'll usually get the files `BMap.dll` and `BMap.pdb`. On Linux, it will be `BMap.so`.
|
||||
|
||||
Then we need to configure PyBMap, which comes with LibCmo. Please follow the manual of PyBMap to combine the compiled binary BMap library with PyBMap. That is to complete the PyBMap configuration.
|
||||
|
||||
Then we need to copy the configured PyBMap to our project under `bbp_ng/PyBMap` to complete this step.
|
||||
|
||||
## Generate Thumbnails and Compress JSON
|
||||
|
||||
BBP comes with a built-in set of custom icons, as well as the JSON files needed by its component BME to describe the structure. By batch generating thumbnails and compressing JSON operations, the size of these parts can be reduced, making them suitable for loading in Blender and easier to distribute.
|
||||
|
||||
Go to the `bbp_ng/tools` folder and run `python3 build_icons.py` which will batch generate thumbnails (this requires the PIL library, please install it via pip in advance). It actually generates thumbnails from the original images in the `bbp_ng/raw_icons` directory and stores them in the `bbp_ng/icons` folder. Running `python3 build_jsons.py` will compress the JSON, which actually reads, compresses, and writes the raw JSON files from the `bbp_ng/raw_jsons` directory into the `bbp_ng/jsons` folder.
|
||||
|
||||
## Packaging
|
||||
|
||||
Starting from Blender 4.2 LTS, plugins are packaged using Blender's own packaging feature.
|
||||
|
||||
Assuming that the final output file is `redist/bbp_ng.zip`. If you are in the root directory of the project, execute the `blender --command extension build --source-dir bbp_ng --output-filepath redist/bbp_ ng.zip` command in a command line window to finish packaging. Please note `blender` is the executable Blender program.
|
||||
|
||||
Blender will package the plugin according to the instructions in `blender_manifest.toml` with the following files excluded:
|
||||
|
||||
* `bbp_ng/raw_icons`: raw thumbnail folder.
|
||||
* `bbp_ng/raw_jsons`: raw JSON folder.
|
||||
* `bbp_ng/tools`: tools for compiling.
|
||||
* `bbp_ng/.style.yapf`: code style description file.
|
||||
* `bbp_ng/.gitignore`: gitignore
|
||||
* `bbp_ng/icons/.gitkeep`: folder placeholder
|
||||
* `bbp_ng/jsons/.gitkeep`: folder placeholder
|
||||
|
||||
## Generating Help Documentation
|
||||
|
||||
Although this project will utilize the GitHub Page feature to provide help documentation, sometimes you may need to provide an offline version of the help documentation, this section will explain how to generate an offline version of the help documentation.
|
||||
|
||||
First you need to install `mkdocs` and `pymdown-extensions` via pip. Then go to the `docs` folder and run `mkdocs build --no-directory-urls`. After running the command you get a folder called `site`, which is the help documentation that can be viewed offline.
|
59
docs/docs/en/component-adder.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Add Component
|
||||
|
||||
In the 3D view, click `Add - Components` to expand the Add Components menu. The menu is shown on the left side of the image below.
|
||||
|
||||

|
||||
|
||||
The right side of the image above shows the interface for adding some components, which will be described in turn, from top to bottom on the right side are: Add Checkpoint, Add Nong Extra Point, Add Nong Ventilator, Add Ventilator Series, Add Sector Pair.
|
||||
|
||||
## General Components
|
||||
|
||||
In the Add Components menu, under the `Basic Components` category, you can add general components. For most components, adding an component requires specifying the sector it belongs to, indicating that the component is only active in that sector. However, there are some exceptions:
|
||||
|
||||
* PS_FourFlame: the 4-flame platform at the start of the level, which is globally unique and therefore has no sector attribute.
|
||||
* PE_Ballon: the ship at the end of the level, globally unique and therefore has no sector properties.
|
||||
* PC_TwoFlames: the checkpoint for the sector, with a sector attribute. However, it should be noted that its sector attribute refers to which sector it is going to check, for example, specifying a sector attribute of 1 means that it is the checkpoint of the first sector, i.e. the start of the second sector, and the second sector will be opened after passing it.
|
||||
* PR_Resetpoint: the reset point of the sector with the sector attribute. However, it is important to note that the sector attribute indicates which sector it is the respawn point of. It follows that when PC_TwoFlames and PR_Resetpoint appear in pairs, PR_Resetpoint is always labeled 1 greater than PC_TwoFlames.
|
||||
|
||||
!!! info "Automatic name conflict detection"
|
||||
A portion of the components have unique names in a Ballance map, e.g., there is and can only be one start point and one ending ship, only one checkpoint and one respawn point can exist in the same sector, etc.
|
||||
|
||||
BBP provides a name detection function when creating these components, and if the name already exists, it will be shown in text below when creating to remind users not to create duplicates. As shown in the upper right corner of the display image above as an example, it is trying to add a PC_TwoFlames that already exists and receives a warning.
|
||||
|
||||
## Add Nong Components
|
||||
|
||||
In the Add Components menu, under the `Nong Components` category, you can add nong components. We only provide two common types of nong components: Nong Extra Point and Nong Ventilator.
|
||||
|
||||
### Nong Extra Point
|
||||
|
||||
To add a nong extra point, you need to specify the number of nong extra point and the sector number of it. It also will automatically rotate the nong extra point with a slight degree one by one to make the nong extra point look better in the game.
|
||||
|
||||
### Nong Ventilator
|
||||
|
||||
Nong ventilator are also added by specifying the number of nong ventilators and the sector number of ventilators. The difference is that we have provided some preset values for constructing nong ventilator that will just blow up wood or stone balls, and if you are not satisfied with these preset values, you can still enter the number yourself.
|
||||
|
||||
!!! info "Ventilator arrays are also possible"
|
||||
Did you know that nong ventilators are also possible by setting the offset to 0 when adding a ventilator series? The nong ventilator creation here is just providing some preset values.
|
||||
|
||||
## Add Series Components
|
||||
|
||||
In the Add Components menu, under the `Series Components` category, you can add series components (aka. component array). We only provide two types of commonly used series: fTilting Block Series and Ventilator Series.
|
||||
|
||||
### Tilting Block Series
|
||||
|
||||
Tilting Block Series requires you to provide the number of tilting blocks and the number of tilting blocks, and you can also adjust the spacing between adjacent tilting blocks, the default spacing is taken from the game.
|
||||
|
||||
### Ventilator Series
|
||||
|
||||
The ventilator series also requires sector number and count of ventilators, however it provides a 3D offset so that you can build a vertical or horizontal ventilator series. The default offset values are taken from the in-game values for vertical ventilator series.
|
||||
|
||||
## Add Components Pair
|
||||
|
||||
In the Add Components menu, under the `Components Pair` category, you can add pairs of components. Currently, only one type of pairs can be added: Sector Pair.
|
||||
|
||||
### Sector Pair
|
||||
|
||||
To add a Sector Pair, you need to enter a Sector number and it will automatically generate a pair of the checkpoint and respawn point components for you. For example, if you enter 1, it will automatically generate a 4-flame start point and a respawn point for Sector 1, if you enter 2, it will generate a checkpoint for Sector 1 and a respawn point for Sector 2, and so on.
|
||||
|
||||
!!! info "Automatic name conflict detection"
|
||||
Similar to normal component additions, sector pair additions have the same name conflict detection. As an example, the lower right corner of the image above shows that the sector pair for sector 1 already exists and does not need to be added.
|
29
docs/docs/en/configure-plugin.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Configure Plugin
|
||||
|
||||
!!! info "The plugin must be configured first"
|
||||
Some of the configurations of the BBP plugin are closely related to the use of the plugin, and only when the BBP plugin is correctly configured can the full functionality of the BBP plugin be used.
|
||||
|
||||
**Whether installing for the first time or updating**, reconfiguring the plugin is essential to ensure that the settings are correct.
|
||||
|
||||
## Open the Configuration Panel
|
||||
|
||||
Open Blender, select `Edit - Preferences`, in the window that opens go to the `Add-ons` tab and find the BBP plugin in the list. Its name is `Ballance Blender Plugin`. Make sure that the checkbox next to the name is checked, which means that the plugin is enabled. Click on the triangular arrow to the left of the checkbox to expand the plugin details to enter the configuration panel as shown in the figure.
|
||||
|
||||

|
||||
|
||||
## Start to Configure
|
||||
|
||||
The BBP plugin currently has 2 settings to configure.
|
||||
|
||||
### External Texture Folder
|
||||
|
||||
Please fill in the `Texture` directory of Ballance, from which the plugin will use the external texture files (i.e. the ones Ballance originally came with). Click on the folder button on the right to browse the folders and select it.
|
||||
|
||||
This is crucial for BBP to work properly, and only if it is filled out correctly will BBP not make errors during operation.
|
||||
|
||||
### No Component Collection
|
||||
|
||||
When importing and exporting BM files, objects that are in a collection with this name will be forced to be specified as No Component. leaving this blank means that this feature is not needed. This feature is usually used for forced element model replacement.
|
||||
|
||||
!!! warning "This setting is not required at this time"
|
||||
Since BBP 4.0 supports native import/export of Virtools files, the BM file import/export function is no longer used. Therefore, this field is no longer useful and does not need to be filled in.
|
28
docs/docs/en/group-operations.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Group Operation
|
||||
|
||||
## Select by Group
|
||||
|
||||
`Ballance - Select by Virtools Group` provides a feature to filter by Virtools grouping data.
|
||||
|
||||
The feature starts with 5 different selection strategies that match Blender's selection methods exactly (Set, Extend, Subtract, Invert, Intersect). Simply use it like a Blender selection. Then, select the name of the group you need and start a selection or filter.
|
||||
|
||||
!!! note "About pattern selection"
|
||||
If you can, use the Subtract or Intersect modes whenever possible. Because this avoids analyzing too many objects. For example, selecting a general range first and then filtering using Intersect mode is more efficient than using Set mode directly.
|
||||
|
||||
## Quick Grouping
|
||||
|
||||
The BBP plugin adds the ability to quickly group objects in 2 places. The first is the object context menu: you can select a series of objects and right click to find the quick grouping feature in the object context menu. Second is the Objects menu in Outline view: you can right-click on a selection of objects in the Outline view to find the quick grouping feature. Both menus are shown below.
|
||||
|
||||

|
||||
|
||||
### Group into...
|
||||
|
||||
Groups the selection into the group of your choice.
|
||||
|
||||
### Ungroup from...
|
||||
|
||||
Ungroups the selection from the group of your choice.
|
||||
|
||||
### Clear All Groups
|
||||
|
||||
Clear all groups for the selection. You will be asked to confirm this before executing to avoid misbehavior.
|
61
docs/docs/en/import-export-virtools.md
Normal file
@ -0,0 +1,61 @@
|
||||
# Import and Export Virtools Document
|
||||
|
||||
!!! info "Not latest version"
|
||||
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
|
||||
|
||||
!!! warning "This is experimental content"
|
||||
Native importing and exporting of Virtools documents is experimental content for the BBP plugin, it may have many problems, see the [Report Issue](./report-bugs.md) section to learn more. When problems are encountered, please report them. the authors of the BBP plugin are not responsible for any consequences resulting from problems with the BBP plugin.
|
||||
|
||||
## Import Virtools File
|
||||
|
||||
Virtools files can be imported by clicking `File - Import - Virtools File`. Importing supports CMO, VMO and NMO files. Clicking on it will bring up the file opening window and show the import settings in the sidebar. First of all, you need to select the Virtools file to be imported, and then configure the import settings in the sidebar. After configuring the import settings, you can click Import to start the import, and wait for the status bar at the bottom of Blender to indicate that the import is complete.
|
||||
|
||||
### Conflict Options
|
||||
|
||||
The Conflict Options section indicates what to do when the importer encounters duplicate object names. There are 4 levels, for Object, Mesh, Material and Texture. There are 2 ways to handle it: Rename and Use Current. When Rename is selected and a duplicate name is encountered, a suffix will be added to the name to make it unique. By choosing Use Current, the import of the item from the file will be ignored and the item with the same name will be used instead, which already exists in the Blender document.
|
||||
|
||||
!!! info "Differences from Virtools conflict resolution"
|
||||
Compared to the conflict resolution dialog in Virtools, the conflict resolution options provided by the BBP plugin do not support replacement, and the granularity is not fine-tuned to individual instances, but only for an entire type. So you can't set a different conflict resolution for each instance of a conflict individually. However, this setting is sufficient for most scenarios.
|
||||
|
||||
The default values for the options in the Conflict Options section are the solutions that are usually selected for import. Of course, special settings are needed for special import situations, e.g. if you are importing an externally exported element model from the original version, you may be able to use Use Current option in material options instead of making a copy. The correct use of the conflict options is a matter of mapping experience and is not taught in this manual.
|
||||
|
||||
### Virtools Params
|
||||
|
||||
It is well known that Virtools uses a system-based multi-byte character encoding to process documents, and is therefore prone to what is known as garbling; Blender itself does not suffer from garbling, however, if we do not read a Virtools document with the correct encoding, the characters stored in it may still appear garbled when the Virtools document is imported into Blender. The Encodings property in the Virtools Params section specifies the encodings for reading Virtools documents. Multiple encodings can be specified, separated by a `;` (semicolon). Some common encodings are listed below:
|
||||
|
||||
* cp1252: Western European encoding used by Ballance.
|
||||
* gbk: The default encoding for Chinese Windows system.
|
||||
|
||||
The encoding attribute is very important. If the wrong encoding is set, the names of the various objects imported into Blender will be unrecognizable, or will cause the program to make an error.
|
||||
|
||||
!!! info "What encodings are available?"
|
||||
Since BBP version 4.1, the names of the encodings we use are basically just copied from the Python encoding names. Most of the commonly used encoding names in Most of the commonly used encoding names in Python are mapped, with only a few particularly rare encodings unsupported, and for specific supported encodings it is necessary to check the source code. See [Python documentation](https://docs.python.org/3/library/codecs.html#standard-encodings) for information on Python's supported encodings. Encodings are not case-sensitive.
|
||||
|
||||
!!! warning "Warning about migration from older versions"
|
||||
Starting with BBP version 4.1, the version number of LibCmo, the underlying library used by BBP's Virtools document import module, has been bumped to 0.2. Before this version, the encoding attribute was a platform-dependent setting. Under Windows, the [Windows Code Page](https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers) number is required here. Under other operating systems, LibCmo uses iconv for character encoding decoding, so the legal [iconv encoding identifier](hhttps://www.gnu.org/software/libiconv/) is required.
|
||||
|
||||
This all changed with LibCmo 0.2, from which LibCmo uses Python-like universal encoding names. It is platform-independent, you no longer need to check whether the operating system you are using is Windows or Linux, the encoded characters are the same string for all platforms. This also means that if you have customized your encoding settings before, you need to be careful to convert them to the new universal encoding name, because the old encoding name may not have a corresponding mapping under the universal encoding name system, for example, `1252` specified on Windows before should be written as `cp1252` under the new universal encoding name, and the original encoding name won't be recognized correctly on the new system.
|
||||
|
||||
## Export Virtools File
|
||||
|
||||
Virtools files can be exported by clicking `File - Export - Virtools File`. Clicking on it will bring up the file opening window and show you the export settings in the sidebar. First of all, you need to select the location of the exported Virtools file, then configure the export settings in the sidebar, after configuring the export settings, you can click Export to start the export, and wait for the status bar at the bottom of Blender to indicate that the export is complete.
|
||||
|
||||
### Export Target
|
||||
|
||||
The Export Target section is used to determine which objects you need to export to a Virtools document. You can choose to export a collection or an object and select the corresponding collection or object below. Note that selecting a collection will export the objects in the internal collection as well, i.e. exporting nested collections is supported.
|
||||
|
||||
### Virtools Params
|
||||
|
||||
The Virtools Params section is similar to the one in the importing Virtools document; the Encodings property determines the encoding used when exporting a Virtools document.
|
||||
|
||||
The Global Texture Save Option determines how textures that are set to Use Global are actually saved. In general, setting it to Raw Data will 100% guarantee that the saved Virtools document will contain the correct texture, but it may be larger, while setting it to External will minimize the size of the file, but there may be problems with the exported document not finding the texture file. We recommend that you specify how each material should be saved individually when you set it up, rather than relying on the global option to set it up. This option is for re-editing old maps that rely on the Global Texture Saving Option. It should also be noted that even though there is a Use Global option in this option, please **don't** select it or it will result in an error, because obviously you can't have a global option that then uses the global option's settings.
|
||||
|
||||
The Use Compress property specifies whether saved documents are stored compressed. Compression can significantly reduce the size of a document, and on modern computer platforms, the performance loss caused by compression is almost negligible. When Use Compress is selected, an additional Compress Level attribute is displayed, which specifies the level of compression; the higher the value, the greater the compression rate and the smaller the file.
|
||||
|
||||
### Ballance Params
|
||||
|
||||
The Ballance Params section contains parameters that optimize the export process for Ballance-specific content.
|
||||
|
||||
Successive Sector is an option to work around a bug that occurs when exporting groups of sectors. For some reason, if there are no elements in a sector (actually, no objects are grouped in a sector group), the export plugin thinks that the sector group doesn't exist and misses the export. And since Ballance determines the final sector, i.e. the sector where the spaceships appears, by incrementing the number of sectors from 1 to the last sector group that exists, the combination of the two causes Ballance to incorrectly determine the number of sectors in the map, and thus display the spaceships in the wrong sector, which is an export bug. when this option is checked, the exported document will be pre-defined according to the number of sectors specified in the Ballance Map information in the current Blender file. When this option is checked, the exported document will pre-create all of the sectors according to the number of sectors specified in the Ballance map information in the current Blender file before exporting, so that you don't miss creating some sector groups, and the spaceships will be displayed in the correct sectors.
|
||||
|
||||
This option is usually checked when exporting playable maps, if you just want to export some models then you need to turn this option off, otherwise it will create a lot of useless sector groups in the final file.
|
@ -1,7 +1,7 @@
|
||||
# Ballance Blender Plugin User Manual
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
||||
!!! info "May be Outdated"
|
||||
This document is translated from other languages and may not always be up to date.
|
||||
|
||||
Welcome to the Ballance Blender Plugin, the user manual for the free and open source Ballance map creation suite.
|
||||
|
||||
@ -13,22 +13,27 @@ Therefore, choosing Blender with BBP for mapping is not only choosing freedom an
|
||||
|
||||
## Getting Started
|
||||
|
||||
* Installing Plugin
|
||||
* Configuring Plugin
|
||||
* [Install Plugin](./install-plugin.md)
|
||||
* [Configure Plugin](./configure-plugin.md)
|
||||
|
||||
## Basics
|
||||
## Features
|
||||
|
||||
* Virtools Properties
|
||||
* Importing and Exporting Virtools Documents
|
||||
* Operating by Groups
|
||||
* Advanced UV
|
||||
* Adding Prefabricated Structures
|
||||
* Adding Rails
|
||||
* Adding Components
|
||||
* [Virtools Properties](./virtools-properties.md)
|
||||
* [Ballance Properties](./ballance-properties.md)
|
||||
* [Import and Export Virtools Document](./import-export-virtools.md)
|
||||
* [Group Operation](./group-operations.md)
|
||||
* [Legacy Alignment](./legacy-align.md)
|
||||
* [Naming Convention](./naming-convention.md)
|
||||
* [UV Mapping](./uv-mapping.md)
|
||||
* [Add Floor](./bme-adder.md)
|
||||
* [Add Rail](./rail-adder.md)
|
||||
* [Add Component](./component-adder.md)
|
||||
|
||||
## Advanced
|
||||
## Misc
|
||||
|
||||
* Compiling and distributing plugins
|
||||
* [Compile and Distribute Plugin](./compile-distribute-plugin.md)
|
||||
* [Report Issue](./report-bugs.md)
|
||||
* [Technical Information](./tech-infos.md)
|
||||
|
||||
!!! info "These are not all"
|
||||
This manual only documents the relevant operations regarding this plugin and does not explain how to make a Ballance map here. For detailed information on how to make a Ballance map with Blender and BBP, please search for content on Bilibili or YouTube.
|
||||
|
57
docs/docs/en/install-plugin.md
Normal file
@ -0,0 +1,57 @@
|
||||
# Install Plugin
|
||||
|
||||
## Determining the Version
|
||||
|
||||
The principle of BBP's Blender support is to support the latest **LTS** version, and to spend some time migrating the plugin after the latest LTS version is released. The current plugin version **4.0** is based on Blender version **4.2.x**.
|
||||
|
||||
Theoretically, BBP will work fine on other versions of Blender if no major changes have been made. For example you can try to run BBP plugin based on Blender 3.6 LTS on Blender 4.0. However, the developers of BBP do not deal with bugs that only appear in non-LTS versions. before installing the plugin, please select the appropriate version.
|
||||
|
||||
## Uninstall the Old Plugin
|
||||
|
||||
If you have used BBP before then you need to uninstall it first. Older versions of BBP are usually installed in the following locations:
|
||||
|
||||
* `<Blender>/3.6/scripts/addons/ballance_blender_plugin`: BBP 3.0 or lower version.
|
||||
* `<Blender>/3.6/scripts/addons_contrib/ballance_blender_plugin`: BBP 3.0 or lower version.
|
||||
* `<Blender>/3.6/scripts/addons/bbp_ng`: BBP 4.0 internal test version
|
||||
* `%APPDATA%/Blender Foundation/Blender/3.6/scripts/addons/bbp_ng`: BBP 4.0 internal test version
|
||||
* `%APPDATA%/Blender Foundation/Blender/4.2/extensions/user_default/bbp_ng`: BBP 4.0 or higher version
|
||||
|
||||
You just need to disable the plugin in Blender first (uncheck the box in front of the plugin name) and then delete these folders (if they exist) to uninstall the plugin completely. The `<Blender>` in the path refers to the location of your Blender installation. The `3.6` and `4.2` in the path are the version numbers of your Blender installation, which need to be adjusted according to the version you have installed, and subsequent occurrences of version numbers should be understood as the same meanings.
|
||||
|
||||
!!! warning "Should not use Blender's plugin uninstall feature"
|
||||
It is not possible to uninstall BBP using the plugin uninstall function on the Blender plugins page, because BBP loads the Virtools file read/write library BMap into Blender as soon as it is loaded by Blender (whether it is enabled or not). if you remove it while Blender is running, you will get an access denied error. Therefore you must manually delete the plugin directory after closing Blender.
|
||||
|
||||
If you are really not sure where the plugin is installed, you can find the `File` property in the Addons page of Blender's Preferences, and the folder it points to where the file is located is the folder to be deleted.
|
||||
|
||||
!!! info "`ballance_blender_plugin` and `bbp_ng`"
|
||||
`ballance_blender_plugin` is the module name of the old version of the BBP plugin (before version 4.0) and `bbp_ng` is the module name of the new version of the BBP plugin (after and including version 4.0). Both are provided in order to ensure that the user actually deleted the old version of the plugin.
|
||||
|
||||
!!! info "`addons` and `addons_contrib`"
|
||||
After Blender version 3.6 LTS, i.e. BBP version 3.3, Blender no longer supports Testing type plugins. As a result, the `addons_contrib` folder, which was dedicated to installing Testing plugins, is no longer used, and plugins need to be installed uniformly in `addons`. Both are provided to ensure that the user actually deletes the old version of the plugin.
|
||||
|
||||
!!! info "`addons` and `extensions`"
|
||||
In Blender version 4.2 LTS, Blender uses Extensions instead of Addons to describe plugins. This has resulted in a change in where plugins are installed. Both are provided in order to ensure that the user actually removes the old version of the plugin.
|
||||
|
||||
## Download Plugin
|
||||
|
||||
You can download the latest plugin via [the Release page of the GitHub codebase for this project](https://github.com/yyc12345/BallanceBlenderHelper/releases). Plugins are provided as ZIP archives.
|
||||
|
||||
In addition, you can also get this plugin in the mapping tutorial web disk provided by yyc12345:
|
||||
|
||||
* Overworld: [Mega](https://mega.nz/#F!CV5SyapR!LbduTW51xmkDO4EDxMfH9w) (located in `Mapping` directory)
|
||||
* Chinese Region Only: [Baidu Web Disk](https://pan.baidu.com/s/1QgWz7A7TEit09nPUeQtL7w?pwd=hf2u) (Extract code: hf2u, located under `制图插件(新)`)
|
||||
|
||||
!!! warning “Do not download this repository directly for use”
|
||||
Please do not download this project's repository directly for use. First of all, because the latest commit is not guaranteed to be stable and available. The second reason is that this project contains some C++ code that needs to be compiled, and must be compiled before it can be used. See [Compile and Distribute Plugin](./compile-distribute-plugin.md) for more information.
|
||||
|
||||
## Install Plugin
|
||||
|
||||
Open Blender, click `Edit - Preferences`, in the window that opens go to the `Add-ons` tab, click on the arrow at the top right of the window and then click on the `Install from Disk.... ` button, select the ZIP archive you just downloaded, and the installation will be completed. If you don't see it in the list you can click the Refresh button or restart Blender.
|
||||
|
||||
You can also choose to install the plugin manually (if the above installation method fails), go to `%APPDATA%/Blender Foundation/Blender/4.2/extensions/user_default`, create a folder named `bbp_ng` and go inside it, extract the downloaded ZIP archive to this folder, start Blender and you will find BBP in the list of addons.
|
||||
|
||||
The name of BBP plugin in the list is `Ballance Blender Plugin`, when you find it, you can enable the plugin by checking the box on the left side of the name. The Preferences window after the plugin is installed is shown below.
|
||||
|
||||

|
||||
|
||||
After **installing or updating** the plugin, be sure to [configure plugin](./configure-plugin.md) before using it, see the next section for details.
|
28
docs/docs/en/legacy-align.md
Normal file
@ -0,0 +1,28 @@
|
||||
# Legacy Alignment
|
||||
|
||||
`Ballance - 3ds Max Align` provides an alignment function similar to the way alignment works in 3ds Max.
|
||||
|
||||
The so-called legacy alignment feature is a perfect reimplementation of the 3ds Max alignment operations in Blender. It makes it possible for many mappers who switch from 3ds Max to Blender to get up to speed faster and provides some convenient alignment operations. The image below shows legacy alignment in action.
|
||||
|
||||

|
||||
|
||||
## Usgae
|
||||
|
||||
Legacy alignment supports aligning multiple objects to a single object by first selecting the objects to be aligned in turn, then selecting the reference object at the end of the alignment (i.e. making it the active object), and then clicking `Ballance - 3ds Max Align` to bring up the legacy alignment panel, after which you can start the alignment operation.
|
||||
|
||||
## Introduction of the Panel
|
||||
|
||||
In the panel, `Align Axis` specifies the axis you want to align to, you can multi-select here to specify more than one axis, without specifying any axis you will not be able to do the alignment operation, and thus you will not be able to click the `Apply` button.
|
||||
|
||||
`Current Object` is the alignment reference object, which is the active object in the scene, usually the last object you selected. This option specifies what value you need to reference for alignment, with `Min` (minimum value on axis), `Center (Bounding Box)` (center of the bounding box), `Center (Axis)` (origin of the object), and `Max` (maximum value on axis) available. These options are consistent with the alignment options in 3ds Max.
|
||||
|
||||
The `Target Objects` are the objects that are being aligned, there may be many of them, in this option it is also specified what values you need to refer to them for alignment. The options have the same meaning as `Current Object`.
|
||||
|
||||
The `Apply` button, when clicked, will press the current page's configuration into the operation stack and reset the settings above, allowing you to start a new round of alignment operations without having to perform a legacy alignment again. The number of operations in the stack is shown below the `Apply` button.
|
||||
|
||||
!!! info "What the Apply button does"
|
||||
Understanding this part is not useful for mapping, and you don't need to read what's in this box unless you're interested.
|
||||
|
||||
By design, Blender doesn't support so-called "operations inside the Operator", but with a few tricks we simulated Apply effect similar to the one in 3ds Max.
|
||||
|
||||
The Apply button is actually a specially displayed BoolProperty that listens to its value change event and, while avoiding recursive calls, records the current setting in a hidden CollectionProperty and resets its own value and displayed properties to make a visual "apply". Operator processes the alignment requirements accumulated in the CollectionProperty in turn when executing.
|
29
docs/docs/en/naming-convention.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Naming Convention
|
||||
|
||||
## Auto Grouping and Renaming
|
||||
|
||||
In the outline view, right-click on any collection to get the Auto Grouping and Renaming menu.
|
||||
|
||||

|
||||
|
||||
This plugin currently supports two naming convention.
|
||||
One is the Mapping Toolchain Standards described in the [Technical Information](./tech-infos.md) section, which is named `YYC Tools Chains` in this plugin.
|
||||
The second is the naming standard used by [Imengyu/Ballance](https://github.com/imengyu/Ballance), named `Imengyu Ballance` in this plugin.
|
||||
|
||||
These functions will ultimately only show a generalized message of success or failure. If you need to see in detail why a certain object is not converted, click `Window - Toggle System Console` and the plugin has more detailed output there.
|
||||
|
||||
### Rename by Group
|
||||
|
||||
Renames an object to an appropriate name based on its current grouping information.
|
||||
This is often used when migrating original maps. Some Ballance-derived programs do not have a Virtools Group concept and therefore rely on names for grouping information.
|
||||
|
||||
### Convert Name
|
||||
|
||||
Switch between different naming standards.
|
||||
Typically used to convert between different Ballance-derived programs.
|
||||
|
||||
### Auto Grouping
|
||||
|
||||
Auto-fill the grouping information for an object according to a given naming convention.
|
||||
Note that the original grouping information will be overwritten.
|
||||
If you follow certain naming conventions during the mapping process, this feature can do the grouping for you automatically.
|
84
docs/docs/en/rail-adder.md
Normal file
@ -0,0 +1,84 @@
|
||||
# Add Rail
|
||||
|
||||
!!! info "Not latest version"
|
||||
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
|
||||
|
||||
In the 3D view, click `Add - Rails` to expand the Add Rails menu. The menu is shown on the left side of the image below.
|
||||
|
||||

|
||||
|
||||
The right side of the picture above shows the interface for adding some rails, and we will introduce them in turn later, from top to bottom on the right side are: Add Rail Section, Add Straight Rail, Add Side Rail, Add Arc Rail, and Add Spiral Rail.
|
||||
|
||||
!!! info "Rails with non-standard data"
|
||||
BBP's rail adding menu is designed for new players to add rails quickly, and is not designed for skilled mapper to add rails. For cases where you need rails with non-standard data, such as rails with non-standard spacing or non-standard section, you need to build the rail section with Blender's own Circle operation, and then generate the entire rail with the Extrude, Bridge, or Spiral modifiers. In this way, you can control all the parameters of each step to meet your specific needs for rail parameters.
|
||||
|
||||
!!! info "Source of the rail parameters"
|
||||
The various parameters of rails used in the Rails Add menu are derived from actual in-game measurements and from the experience of several mappers in the Ballance community over more than a decade.
|
||||
|
||||
The rail section radius and gauge are derived from years of mapping experience and measurements. Side rail tilt degree data is calculated by BallanceBug. Mono and double rail transition sinking data from the calculation by Imbalanced Dream. Spiral rail spacing is measured from Level 9 and Level 13.
|
||||
|
||||
## Rail Sections
|
||||
|
||||
In the Add Rails menu, under the `Sections` category, you can add rail sections. A rail section is the outline of a rail, and the creation of a rail section is usually the first step in the creation of various types of shaped rails, e.g. rails made by lofting, extruding etc.
|
||||
|
||||
### Rail Section
|
||||
|
||||
To create a rail section, you can choose in the panel to create a mono or double rail section.
|
||||
|
||||
When creating a mono rail section, the octagonal rail section is automatically created with the flat side edge facing up, while the double rail section is not (vertex tip facing up). If you need to change this behavior, you need to go into edit mode after creation and manually rotate the vertices of the rail section so that the flat side or vertex tip of the rail section faces up.
|
||||
|
||||
### Transition Section
|
||||
|
||||
The mono and double rail transition section will create a rail section that is suitable for mono and double rail transitions. This rail section is created without specifying any parameters.
|
||||
|
||||
## Straight Rails
|
||||
|
||||
In the Add Rails menu, under the `Straight Rails` category, you can add straight rails.
|
||||
|
||||
### Straight Rail
|
||||
|
||||
A Straight Rail is a straight rail of rail. To create a straight rail you need to specify a Length. You can also choose to create a straight double rail or a mono rail.
|
||||
|
||||
When creating a mono rail, similar to a section, the flat side of the rail section is automatically turned upwards, and this behavior can be modified by entering edit mode after creation and then rotating.
|
||||
|
||||
The creation of straight rails also supports the capping properties, which are controlled by the Start Cap and End Cap options located under Rail Cap, which are checked to cap the corresponding end. Capping is the process of automatically adding face for the end of a rail and correctly handling its normal. This is usually done to ensure that the rail is aesthetically pleasing where it comes into contact with other surfaces or objects, rail-to-rail joints do not need to be capped.
|
||||
|
||||
### Transition Rail
|
||||
|
||||
Mono and double transition rail can often be seen as an advanced use of Transition Section creation, where the section created by the transition section is extruded and the normals are taken care of to produce the result created by this option. To create a mono or double transition rail you need to specify a Length, which is also supported by the capping property.
|
||||
|
||||
### Side Rail
|
||||
|
||||
Side rails are created by specifying a Side Type which can be either Normal (for paper or wood balls) or Stone Specific (for stone ball specific). Side rails for paper and wood balls are the usual side rails that stone balls cannot pass through. Stone Specific side rails are side rails that are more tilted and can be passed by stone balls, and of course, paper and wood balls.
|
||||
|
||||
In addition to the side rail type, side rail creation also requires the Length and capping attributes.
|
||||
|
||||
## Curve Rails
|
||||
|
||||
In the Add Rails menu, under the `Curve Rails` category, you can add curved rails.
|
||||
|
||||
### Arc Rail
|
||||
|
||||
The first thing you need to do with an arc rail is to specify the Angle and Radius, which indicates how much the rail will turn at what radius. Generally speaking, angles of 90, 180, and 270 degrees are more common, but any number of degrees can be specified. The radius is usually adjusted as needed. For the double rail arc, the radius is the distence between arc rail rotation center to the double rail section center. For the mono rail arc rail, is the distence between arc rail rotation center to the mono rail section center.
|
||||
|
||||
Steps of the arc rail, the number of steps indicates the number of segments of the arc rail, the larger the number, the smoother the arc rail looks, relatively, the vertices will be more, the storage space and rendering requirements are also higher, so you need to choose a reasonable value.
|
||||
|
||||
Arc rails also support double-rail mono-rail selection, you can create mono-rail arc rails and double-rail arc rails. The capping attribute is also supported.
|
||||
|
||||
### Spiral Rail
|
||||
|
||||
Spiral rail, or spiral double rail, is similar to arc rail in that you need to specify Radius, which is the radius of rotation, but not the angle, since it always rotates in 360 degrees.
|
||||
|
||||
Spiral rails have an Iterations property, which indicates how many times the rail will spiral up, and a Screw property, which indicates the distance between each iteration.
|
||||
|
||||
The spiral rail also needs to set the Steps property, which has the same meaning as the arc rail. However, it should be noted that the number of steps refers to the number of steps in each iteration, not the overall number of steps. Therefore, when adjusting the iteration attribute, you do not need to change the Steps attribute again.
|
||||
|
||||
Side Spiral Rail also has a capping property.
|
||||
|
||||
### Side Spiral Rail
|
||||
|
||||
Side Spiral Rail, similar to spiral rail, but the ball is rolled along the side, similar to side rail.
|
||||
|
||||
Side Spiral Rail does not have a Screw property, because Side Spiral Rail is designed so that adjacent spins share a common edge, so the screw is fixed.
|
||||
|
||||
The Radius, Iterations and Steps attributes in the Side Spiral Rail settings have the same meaning as the spiral rail. Side Spiral Rail also have a capping attribute.
|
32
docs/docs/en/report-bugs.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Report Issue
|
||||
|
||||
!!! info "Not latest version"
|
||||
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
|
||||
|
||||
## What Can Go Wrong
|
||||
|
||||
BBP is not perfect, and since BBP's Virtools file import/export module is written in C++, BBP is more prone to errors than other plugins, and the consequences of errors can be more serious (including but not limited to memory leaks, accidental deletion of user files, etc.).
|
||||
|
||||
In Blender, you will observe if the plugin execution goes wrong:
|
||||
|
||||
* The expected effect is not achieved
|
||||
* A large stack of output text pops up at the mouse that you can't read
|
||||
* After opening the console using `Window - Toggle System Console`, you can observe the Python exception output.
|
||||
|
||||
## What Part Went Wrong
|
||||
|
||||
For the BBP plugin, if you observe something like `BMap operation failed` in the Python exception output, or the `IronPad.log` file in the `<Plugin-Install-Location>/PyBMap` folder, it means that The BBP plugin's BMap section, written in C++, is in error, and **you need to immediately save your current Blender document and exit Blender**. Because the plugin is in an abnormal state at this point, you should not continue any operations.
|
||||
|
||||
If there is no such thing as the above, then this is just a normal Python code execution error and you don't need to worry too much about it, but the error is still fatal and it is recommended to exit Blender and report the error after doing all the necessary operations.
|
||||
|
||||
## Where to Report
|
||||
|
||||
If you have a GitHub account, you can create and report issues in [Issue page of BBP repository](https://github.com/yyc12345/BallanceBlenderHelper/issues).
|
||||
|
||||
If you can't do that and you have proper way to contact with plugin author, then reporting directly to the plugin author is fine.
|
||||
|
||||
## The Content of the Report
|
||||
|
||||
First of all you need to describe in detail how you raise this error and what are the results of this error. If you can upload the documentation that led to the error, please try to do so (if it's not convenient to post it publicly, you can send it to the author through a private way such as email).
|
||||
|
||||
You also need to provide the Python stack report output in the Blender console (use `Window - Toggle System Console` to open the console). If your error is an error in the BMap section, you also need to provide the `IronPad.log` and `IronPad.dmp` files in the `<Plugin-Install-Location>/PyBMap` folder to make it easier for developers to locate the error.
|
12
docs/docs/en/tech-infos.md
Normal file
@ -0,0 +1,12 @@
|
||||
# Technical Information
|
||||
|
||||
* BM File Specification: https://github.com/yyc12345/gist/blob/master/BMFileSpec/BMSpec_ZH.md
|
||||
* Mapping toolchain standards and format of files in the `meshes' folder: https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md
|
||||
* Format of the JSON file for BMERevenge: https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_v2.0_ZH.md
|
||||
|
||||
This plugin works with the `fake-bpy-module` module to implement type hinting to speed up development. Use the following command to install Blender's type hinting library.
|
||||
|
||||
* Blender 3.6: `pip install fake-bpy-module-latest==20230627`
|
||||
* Blender 4.2: `pip install fake-bpy-module-latest==20240716`
|
||||
|
||||
The main reason for doing this is that `fake-bpy-module` doesn't release an official package for the given Blender version, so I had to install it by choosing the daily build closest to the release time of the corresponding Blender version.
|
57
docs/docs/en/uv-mapping.md
Normal file
@ -0,0 +1,57 @@
|
||||
# UV Mapping
|
||||
|
||||
## Rail Mapping
|
||||
|
||||
The menu `Ballance - Rail UV` in the 3D view provides the ability to map rails.
|
||||
|
||||
To start mapping rails, you need to select the rails to be mapped (multiple selections are possible) and then click on this menu. Then you only need to select the material used for the rail. This will clear all materials for the selected object and assign all materials used on each face to the material you specify. Then you will set the UV of the selected object in the same way as the rails are post-processed in Ballance, so that the rails will have the same appearance as in the game by performing this operation.
|
||||
|
||||
!!! info "Rails UV doesn't really matter"
|
||||
In fact, all objects grouped in the `Phys_FloorRails` group undergo a post-processing when Ballance loads the level. What the post-processing method does is to reset the UVs of these objects according to a specific mapping function (`TT_ReflectionMapping` is called in Virtools). So the UVs of the rails don't really matter, because Ballance will set them uniformly for you when you enter the game, and even if you don't set any UV data to the rails, the rails will look correct when you enter the game.
|
||||
|
||||
Therefore, this feature is usually applied to set the UVs for objects that are not grouped in the `Phys_FloorRails` group, but you still want them to show the appearance of the rails material. Or for rendering Ballance map previews in Blender, etc.
|
||||
|
||||
## Mapping Along Edges
|
||||
|
||||
Mapping Along Edges is the most important feature of the BBP plugin, the most powerful and at the same time the most difficult to understand. It is widely used for setting UVs of custom structures or a line of floor, wood, etc. The menu `Ballance - Flatten UV` in the 3D view offers the Mapping Along Edges feature. It only works in **Edit Mode**, so you need to be in Edit Mode (and entering Face selection mode at the same time, since Mapping Along Edges operates on faces) to use it.
|
||||
|
||||
Mapping Along Edges is usually used in combination with [Select Face Loops](https://docs.blender.org/manual/en/4.2/modeling/meshes/selecting/loops.html), [Shortest Path](https://docs.blender.org/manual/en/4.2/modeling/meshes/selecting/linked.html#bpy-ops-mesh-shortest-path-select) and other functions. You need to first select a series of faces, for example a series of consecutive Ballance floor sides, and then click `Ballance - Flatten UV` to start mapping along edges.
|
||||
|
||||
The configuration screen for Mapping Along Edges is shown below, with Scale Size mode on the left and Ref. Point (refernece point) mode on the right, we'll explain the difference between these two modes later.
|
||||
|
||||

|
||||
|
||||
Mapping Along Edges, as the name implies, means UV mapping along a certain edge. The specific operation is to use linear algebra to transform the 3D coordinates of the vertices under a new coordinate system using a transition matrix. In this new coordinate system:
|
||||
|
||||
* The origin is the vertex with the index specified by the Reference Edge, as shown below. The figure below identifies the vertex index of the face in UV and 3D in yellow font, and the Reference Edge is 1 in the figure, then the vertex with the vertex index of 1 in the current face is used as the origin of the new coordinate system.
|
||||
* The Y-axis (i.e., the V-axis, XYZ corresponds to UVW, and will not be commented subsequently) is the line from the vertex specified by Reference Edge to the next vertex, i.e., the edge from vertex 1 to vertex 2, i.e., the edge with the serial number of 1, as shown in the following figure, which identifies the serial number of the edge of the face in purple font in the following figure in UV and 3D. Edge 1 is a vector that starts at vertex 1 and ends at vertex 2. It has a direction, is a vector, and needs to be normalized when used as a coordinate axis.
|
||||
* The Z-axis is obtained by cross-multiplying and normalizing the edge specified by Reference Edge (in this case, Edge 1) with its next neighboring edge (in this case, Edge 2). In the case of a three-point covariance where the cross-multiplication yields a zero vector, an attempt is made to use the normal data of the face instead.
|
||||
* The X-axis is obtained by cross-multiplying and normalizing the previously calculated Y-axis and Z-axis. The XYZ in the new coordinate system still needs to satisfy the requirements of the right-handed coordinate system.
|
||||
|
||||

|
||||
|
||||
After setting up the new coordinate system and building the transition matrix and transforming every vertex to the new coordinate system, we can discard the Z component and map XY to UV and mirror the vertices on the -U axis to the +U axis, which is why Flatten UV doesn't support concave polygons. The reason for mirroring is to prevent UV mapping errors, for example, in the above image, the upper edge stripe of the floor side mapping is located in the V-axis, if we flip the UV vertices in the above image along the V-axis (you can try it for better understanding), it will result in the final mapping display does not meet our expectation, i.e., the edges specified by Reference Edge do not show the floor side stripe.
|
||||
|
||||
!!! info "Reference Edge actually refers to"
|
||||
The Reference Edge actually refers to the index of the edge that needs to be mapped to the V-axis. Because this edge is directional, it also determines the origin in the new coordinate system.
|
||||
|
||||
In short words, Flatten UV allows the user to specify an edge and then flatten it along the V-axis of the UV.
|
||||
|
||||
After determining the coordinate system, we also need to know how far this UV mapping needs to be expanded, specifically what the scaling value of this UV mapping should be. For the V-axis direction, due to the Ballance mapping charcateristic (Ballance mapping always extend along the V-axis, and in Ballance, 5 in 3D is equivalent to 1 in UV), we can know the UV:3D relationship is 1:5. While for the U-axis direction, we can't determine it, that's the work of Scale Mode property, which lets the user decide the scaling of the U-axis direction.
|
||||
|
||||
The Scale Size mode allows the user to directly specify the scale value, for example, the default value of 5 means that 5 in the 3D world is equal to 1 in the UV world, while the Ref. Point mode allows the user to specify the U value of the UV of a reference point in the surface, and lets the plugin calculate the scaling on the U-axis by itself. For example, if we switch the Scale Size mode in the image above to Ref. Point mode and specify Reference Point as 2 and Reference Point UV as 1 we can also apply the same texture, let's explain the principle here. First of all, Reference Point specifies the reference point, this reference point is relative to the vertex specified by Reference Edge, that is, the reference system origin of the offset, so here, Reference Edge is 1, Reference Point is 2, then the actual reference point for the vertex 3. Reference Point UV specifies a U value of 1 in the UV value of this point, i.e., it can be seen as forcing vertex 3 to be placed at a U-axis of 1. BBP will calculate the corresponding U-axis scaling value based on this point and apply it to all vertices.
|
||||
|
||||
Scale Size mode is usually used for floor side mapping, since these faces have fixed U-axis scaling values. The Ref. Point mode is typically used for maps where the scaling value cannot be determined (usually due to model deformation that causes the scaling value to fluctuate around the standard range of scaling values), such as the upper surface of a sink floor generated from a curve with bevel shape, as shown in the following figure. We can be sure that the UV of the center of the sink floor must be 0.5, but it is not convenient to determine its scaling value because it is difficult to calculate it, so we just use the Ref. Point mode. The top half of the figure below shows the mapping result in the Ref. Point mode, and the bottom half is in the Scale Size mode. We can observe that in the Scale Size mode, the UV of the center of the sink floor is not exactly 0.5, which results in the center of the sink floor not being dark enough on the display. The Ref. Point mode, on the other hand, accurately sets the UV of the center of the sink floor to 0.5.
|
||||
|
||||

|
||||
|
||||
Finally, Flatten Mode specifies the unfolding mode, Raw means that the connection between faces is not considered at all, and each face is treated as an independent content, Floor means that the continuity of adjacent faces in the V-axis direction is taken into account, and adjacent faces will be made adjacent in the UVs as much as possible, which can avoid the problem of visual duplications caused by Raw unfolding in the case of too many subdivisions of the road surface. Floor is often used in floor mapping, so it's called Floor. Wood is similar to Floor, except that it not only considers continuity on the V-axis, but also on the U-axis, which is often used in concave and convex wood mapping, and is the reason of its name.
|
||||
|
||||
The figure below shows the distribution of UV mapping for three different unfolding modes. At the top is the Raw mode, where you can see that the Reference Edge of each face is expanded at the UV coordinates of the origin. In the middle is the Floor mode, where you can see that BBP unfolded a series of consecutive faces along the V-axis, instead of stacking them all at the origin as in the Raw mode. At the bottom is the Wood unfolding mode, which is unfolding a convex wood, and you can see that the continuity of the faces has been taken into account on both the V and U axes.
|
||||
|
||||

|
||||
|
||||
!!! info "Floor and Wood Flatten Mode failed"
|
||||
Floor and Wood mode have more limitations than Raw modes, they only support rectangle faces, and they require a lot of modeling operations, usually only the geometry generated by batch operations (e.g. subdividing, lofting, etc.) will be correctly recognized by Floor and Wood.
|
||||
|
||||
If Floor and Wood unfolding modes fail and the resulting unfolded maps are completely unacceptable, try modeling in a more prescriptive way or switch to manual mapping.
|
62
docs/docs/en/virtools-properties.md
Normal file
@ -0,0 +1,62 @@
|
||||
# Virtools Properties
|
||||
|
||||
!!! info "Not latest version"
|
||||
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
|
||||
|
||||
## Virtools Group
|
||||
|
||||
The BBP plugin adds a new property to every Blender object, called Virtools Group. has the same functionality as Group in Virtools. Select an object and the `Virtools Group` panel can be found in the `Object` properties panel.
|
||||
|
||||

|
||||
|
||||
In the `Virtools Group` panel, you can click Add to group objects. After clicking the Add button, you can select Predefined and then select one of all legal Ballance group names to add. Or select Custom and enter the group name you want to add. The selected Virtools group can also be deleted by clicking the Delete button. Finally, all group data for this object can be deleted at once by clicking the Trash icon button (which will let you confirm before deleting).
|
||||
|
||||
BBP also provides access to Virtools groups in Blender's other menus, see [Group Operation](./group-operations.md).
|
||||
|
||||
## Virtools Material
|
||||
|
||||
The plugin adds a new property to every Blender material, called Virtools Material, which bridges the gap between Virtools materials and Blender materials. Go to the `Material` properties panel and select a material to find the `Virtools Material` panel.
|
||||
|
||||

|
||||
|
||||
You can set material properties in the `Virtools Material` panel, just like in Virtools. All material parameters in the `Virtools Material` panel are maps of the material parameters in Virtools and will be accurately reflected in the final saved Virtools document.
|
||||
|
||||
The `Virtools Material` panel provides a preset function, which can be started by clicking the `Preset` button at the top. The preset function allows the user to use some preset material settings, such as the material data for the top surface of the road, the sides, etc., for ease of use. Note that using a preset does not affect the material's texture options, and you will still need to set the material's texture manually once the preset has been applied.
|
||||
|
||||
The `Virtools Material` panel also provides the ability to convert the material data in the `Virtools Material` panel to the Blender material for a visual effect in Blender. Click the `Apply` button at the top to perform this function. When you save a Virtools document in Blender, the material data in the Virtools document will be retrieved from the values specified in the `Virtools Material` panel and not from the Blender material. This means that a proper procedure for setting up a material is to edit the material parameters in the `Virtools Material` panel and then use the `Apply` button to convert them to the Blender material, instead of editing the Blender material directly.
|
||||
|
||||
The `Virtools Material` panel provides material repair functionality, which is inherited from the [Ballance Virtools Plugin](https://github.com/yyc12345/BallanceVirtoolsHelper). The Material repair button is located to the right of the `Preset` button and the `Apply` button, and is a button with a wrench icon. Clicking it requires reconfirmation to prevent misuse. The material repair function determines which type of material the current material is based on the filename of the texture file it references, and then modifies the other parameters to make it visually appealing based on our preset repair settings (taken from the game). This is usually used for fixing objects that look like they have the wrong material in the game, such as a black Stopper, etc.
|
||||
|
||||
!!! info "There is also a global material fix function"
|
||||
In the 3D view, the menu `Ballance - Fix Material` is similar to the material fix function, but it will fix all materials within the current document. Don't use this feature unless you are sure that all materials in the current document need to be fixed, as it may set some of the originally special, correct materials back to generic values that you don't want.
|
||||
|
||||
The Global Material Repair function also needs to be reconfirmed after clicking on it before it can be used, to prevent misuse.
|
||||
|
||||
The Texture property in the `Virtools Material` panel not only allows you to select a texture within a document by clicking on it, but also opens the Texture File Browser by clicking on the folder button on the right side, which allows you to select the texture you want directly from the file system (much faster than selecting it from the Shading tab). The file browser is navigated in Ballance's Texture directory by default, to make it easier to select Ballance materials.
|
||||
|
||||
## Virtools Texture
|
||||
|
||||
The BBP plugin adds a new property to all Blender textures (actually Images) called Virtools Texture, which creates a link between Virtools textures and Blender images.
|
||||
|
||||
Unlike Blender materials, due to Blender's implementation there is no separate properties panel for textures, so we can only access Virtools texture properties in an indirect way in the `Virtools Material` panel. First find the `Virtools Material` panel by referring to the instructions in the `Virtools Material` chapter, then select a texture or open a texture in the material slot in the `Virtools Material` panel, and you'll see that the Virtools texture attributes are displayed additionally underneath the texture attributes of the material, as shown in the highlighted portion of the image below.
|
||||
|
||||

|
||||
|
||||
Among them, Save Option indicates how the texture is stored in Virtools, and these are the common storage methods:
|
||||
|
||||
* External: The file only stores the name of the referenced file. All Ballance native textures should use this mode.
|
||||
* Raw Data: The texture is stored inside the file, the disadvantage is that it will lead to a large file. All non-native Ballance maps should use this mode.
|
||||
* Use Global: Use global settings. We do not recommend using this unless you are modifying an existing map. We recommend explicitly specifying how individual textures are stored right here, rather than using global values. The global setting is determined when exporting the Virtools document.
|
||||
|
||||
The Video Format indicates the rendering mode of the texture in Virtools, and these are the commonly used modes:
|
||||
|
||||
* 32 Bits ARGB8888: The storage mode for all kinds of textures with transparency, such as the column gradient part.
|
||||
* 16 Bits ARGB1555: The way to store all kinds of maps without transparency, such as floor.
|
||||
|
||||
## Virtools Mesh
|
||||
|
||||
The BBP plugin adds a new property to all Blender meshes called Virtools Mesh. go to the `Data` properties panel to find the `Virtools Mesh` panel.
|
||||
|
||||

|
||||
|
||||
The Virtools Mesh is currently only used as a compatibility feature. It has only one property, Lit Mode, that can be set. Most early maps had black floor issue because they didn't know how to set the material correctly, so the Lit Mode was often set to Prelit to get the floor to show up properly. This attribute exists for compatibility with this compromise and the user usually does not need to set this option.
|
4
docs/docs/en/zzq-features.md
Normal file
@ -0,0 +1,4 @@
|
||||
# ZZQ Features
|
||||
|
||||
!!! info "No Translation"
|
||||
This page has not been translated. Please see source page of this page.
|
BIN
docs/docs/imgs/ballance-properties.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
docs/docs/imgs/bme-adder-dialog.png
Normal file
After Width: | Height: | Size: 272 KiB |
BIN
docs/docs/imgs/bme-adder.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
docs/docs/imgs/component-adder.png
Normal file
After Width: | Height: | Size: 212 KiB |
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 66 KiB |
BIN
docs/docs/imgs/flatten-uv-flatten-mode.png
Normal file
After Width: | Height: | Size: 744 KiB |
BIN
docs/docs/imgs/flatten-uv-mechanism.png
Normal file
After Width: | Height: | Size: 573 KiB |
BIN
docs/docs/imgs/flatten-uv-scale-mode.png
Normal file
After Width: | Height: | Size: 436 KiB |
BIN
docs/docs/imgs/flatten-uv.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/docs/imgs/grouping.png
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
docs/docs/imgs/legacy-align.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
docs/docs/imgs/naming-convention.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
docs/docs/imgs/rail-adder.png
Normal file
After Width: | Height: | Size: 199 KiB |
BIN
docs/docs/imgs/virtools-group.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
docs/docs/imgs/virtools-light.png
Normal file
After Width: | Height: | Size: 49 KiB |