30 Commits
v4.1 ... master

Author SHA1 Message Date
14de9f3f1b i18n: update translation 2025-01-23 13:09:11 +08:00
dd64c0ce04 feat: finish work of release
- bump up version to 4.2
- update document about translation.
2025-01-21 10:10:36 +08:00
84f6baae6a i18n: basically finish translation 2025-01-20 22:38:29 +08:00
5afbf679ed i18n: update translations 2025-01-20 20:32:08 +08:00
89f5b5990d i18n: update translation 2025-01-20 18:21:14 +08:00
b441063061 i18n: first commit about i18n translation.
- add i18n translation files.
- add document about how to translate this plugin.
- fix some field descriptions in source code.
2025-01-20 16:08:46 +08:00
b96550ca5c feat: finish BME prototype improvement 2025-01-20 10:07:45 +08:00
cc74a2ca8b feat: add i18n for forgetten entries.
- add i18n for progress hints when importing or exporting virtools file.
- add i18n for naming convention report.
2025-01-19 10:52:20 +08:00
f9fe4af1fe feat: update BME structure 2025-01-19 10:29:58 +08:00
4a96906002 feat: update BME prototypes
- update BMe prototypes. add chris suggested component for future changes.
- add new 2 custom function in BME environment.
2025-01-18 19:09:32 +08:00
c448216496 doc: update document 2025-01-17 22:22:51 +08:00
f10c273067 fix: improve BME extractor
- improve BMe extractor with new added classes.
- I have written a half of BME validator, but I gave up now.
	* it takes too much time and I don't want to pay more on it. postpone it to next update.
	* I annotate all of BMe validator code for future implementation.
	* the improvement of BME json files (upgrade them to YAML format) also is postponed.
- change some interface in common.py. synchronize it to other modules using it.
2025-01-17 17:53:03 +08:00
04aa879c22 feat: finish i18n extract work
- masically finish u18n extract work (most fields are extracted)
2025-01-12 15:15:29 +08:00
4ffe29654b feat: add translation context for operators and their properties.
- add translation context for operator, menu, panel and etc. and their associated properties.
- improve some name and description but not finished.
- move reset BME material function inside BMEMaterialsHelper.
- rename variable of collection visitor in BME adder operator for clear meaning.
- replace some message box to report in ballance elements reset operator, BME materials reset operator and rail UV operator
2025-01-11 21:36:11 +08:00
1d7ac76d0e chore: update the generator of json
- add PO file extractor for BME prototypes.
- add validator for BME prototypes but not finished.
- update json builder script.
2025-01-09 21:41:52 +08:00
77315ffbea fix: fix previous commit remained issues.
- vt encodings in list showcase now works.
- modify some usage of message_box to Operator.report to make it more like blender suggested.
2025-01-08 20:20:10 +08:00
0862ecd269 feat: use list to display virtools encodings settings, instead of raw string.
- use list to show bmap encoding settings, instead of user input raw string. it will give a more obvisous interface.
	* add bmap encoding setting properties in ptrprop_resolver.
	* update ptrprop_resolver for more clear usage.
- following features has not been implemented yet.
	* default value for bmap encoding list.
	* the bridge function in ioport_shared module (filter empty item in result)
	* validate encoding list result when importing and exporting virtools file.
2025-01-08 13:21:35 +08:00
f4d3e48be2 refactor: add 2 classes to refactor old code which raise too much erros by linter.
- add TinyMutex to resolve the issue that we can not operate the virtools group infos of 2 individual objects.
	* use this new class for all class need resource mutex, such as ballance element, bme materials and etc.
- add CollectionVisitor to resolve that blender have bad document and type hint for runtime bpy.types.CollectionProperty.
	* now all visit to CollectionProperty are delegated by this class.
2025-01-06 15:12:14 +08:00
6ae8899912 feat: add new operator about convert curve to mesh with group infos.
- add a new operator which can converte selected object to mesh, and if object is curve and has bevel object, it will copy the virtools group info of bevel object at the same time.
- add a hint in virtools group panel to tell user that the virtools group of non-mesh object will not be saved.
2025-01-05 20:06:40 +08:00
76f1cdc3c7 fix: fix various rail creation issue.
- move rail creation function into an individual file, UTIL_rail_creator.py
- add flip options for screw rails. this allow user to create any types of screw rail they needed.
2025-01-04 20:13:20 +08:00
8105b110f2 feat: add default rail material when creating rail.
- add default rail material when creating rail.
	* add Rail material in bme material.
- remove negative screw radius feature when creating curve rail.
	* originally negative screw radius will cause normal flip issue when creating.
	* however, negative screw radius feature can no fulfill each shapes of screw style. it is more convenient using blender mirror feature after creating screw rail.
	* thus I remove this feature, not fix it (I can fix it).
2025-01-04 12:28:59 +08:00
acb87b3844 fix: fix step count error when exporting virtools file 2025-01-04 10:19:31 +08:00
94d5c934c6 fix: fix light direction issue when importing and exporting virtools file. 2025-01-03 23:42:44 +08:00
3372c7a4b7 feat: add light support in exporting virtools file.
Some checks failed
Publish docs via GitHub Pages / Deploy docs (push) Has been cancelled
- support light type when exporting virtools.
- both import and export virtools file now support light type. however there is a slight bug about light direction need to be resolved.
- fix bug that exporter throw exception if exported collection contain non-mesh object or exported object is not mesh object.
- fix the usage of BMMeshTrans according to upstream changes.
2025-01-03 17:36:44 +08:00
4181096a9e fix: redirect some reference of bpy.context to context passed by operator argument 2025-01-03 10:37:26 +08:00
89a5e6367b feat: improve name conflict strategy UI layout 2025-01-03 09:55:02 +08:00
cb893b770a feat: add light object support in importing virtools file.
- fix all `def poll(self)` to `def poll(cls)` to let it fit class method name convention.
- fix wrong progress counter when importing virtools file.
- add light support when importing virtools file.
- add corresponding conflict strategy and resolver for light.
2025-01-03 09:36:32 +08:00
2f08455518 feat: add virtools light features.
- add virtools light feature for blender light type and add all essential operators, functions and structs.
- remove PyBMap from repository. order builder fetch it on their own.
- update gitignore.
2024-12-31 14:40:41 +08:00
729e12ed7b feat: update virtools file importer and exporter
- use panel to organise property group in virtools file importer and exporter.
- move all ballance params and virtools params into ioport_shared module and enable different showcase according to the argument passed to show function presenting whether current window is importer or exporter.
- add multiple type ignore to ignore the error of bpy operator member field type hints.
2024-12-30 17:53:42 +08:00
fe47861bd0 fix: fix issue of BME module prototype 2024-11-02 17:44:06 +08:00
80 changed files with 9713 additions and 3432 deletions

3
.gitignore vendored
View File

@ -1,2 +1,3 @@
# disable distribution build folder
## ===== Personal =====
# Disable distribution build folder
redist/

19
bbp_ng/.gitignore vendored
View File

@ -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]

View File

@ -1,7 +1,7 @@
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.
#
@ -51,8 +55,9 @@ 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
@ -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,15 +137,17 @@ 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".
@ -149,7 +158,8 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
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)
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",
@ -157,7 +167,8 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
size = 3,
subtype = 'EULER',
step = 100, # We choosen 100, mean 1. Sync with property window.
default = (0.0, 0.0, 0.0)
default = (0.0, 0.0, 0.0),
translation_context = 'BBP_OT_add_bme_struct/property'
) # type: ignore
@classmethod
@ -182,20 +193,23 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
# 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
@ -225,50 +239,58 @@ 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:")
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:")
layout.label(text="Extra Transform", text_ctxt='BBP_OT_add_bme_struct/draw')
# translation
layout.label(text = '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 = '')
hbox_layout.prop(self, 'extra_translation', text='')
# rotation
layout.label(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 = '')
hbox_layout.prop(self, 'extra_rotation', text='')
@classmethod
def draw_blc_menu(cls, layout: bpy.types.UILayout):
@ -277,7 +299,8 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
cop = layout.operator(
cls.bl_idname,
text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident),
icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident)
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)

View File

@ -8,10 +8,11 @@ from . import PROP_ballance_element, PROP_virtools_group, PROP_ballance_map_info
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:
@ -23,10 +24,11 @@ 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:
@ -192,15 +194,17 @@ _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):
@ -220,7 +224,9 @@ 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
@ -240,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)
@ -254,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
@ -289,13 +297,15 @@ 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 = [
('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(
@ -307,6 +317,7 @@ class BBP_OT_add_nong_ventilator(bpy.types.Operator, ComponentSectorParam, Compo
('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):
@ -315,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)
@ -363,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",
@ -370,6 +383,7 @@ 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):
@ -407,6 +421,7 @@ class BBP_OT_add_swing_series(bpy.types.Operator, ComponentSectorParam, Componen
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",
@ -414,12 +429,14 @@ class BBP_OT_add_swing_series(bpy.types.Operator, ComponentSectorParam, Componen
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
default = True,
translation_context = 'BBP_OT_add_swing_series/property'
) # type: ignore
def draw(self, context):
@ -465,6 +482,7 @@ class BBP_OT_add_ventilator_series(bpy.types.Operator, ComponentSectorParam, Com
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",
@ -473,6 +491,7 @@ 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):
@ -514,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:
@ -534,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

View File

@ -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)
@ -46,7 +46,8 @@ class SharedExtraTransform():
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)
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",
@ -54,21 +55,22 @@ class SharedExtraTransform():
size = 3,
subtype = 'EULER',
step = 100, # We choosen 100, mean 1. Sync with property window.
default = (0.0, 0.0, 0.0)
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:")
layout.label(text="Extra Transform", text_ctxt='BBP/OP_ADDS_rail.SharedExtraTransform/draw')
# translation
layout.label(text = 'Translation')
layout.label(text='Translation', text_ctxt='BBP/OP_ADDS_rail.SharedExtraTransform/draw')
row = layout.row()
row.prop(self, 'extra_translation', text = '')
row.prop(self, 'extra_translation', text='')
# rotation
layout.label(text = 'Rotation')
layout.label(text='Rotation', text_ctxt='BBP/OP_ADDS_rail.SharedExtraTransform/draw')
row = layout.row()
row.prop(self, 'extra_rotation', text = '')
row.prop(self, 'extra_rotation', text='')
def general_get_extra_transform(self) -> mathutils.Matrix:
return mathutils.Matrix.LocRotScale(
@ -91,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:
@ -109,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)
@ -139,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:
@ -158,13 +165,37 @@ class SharedScrewRailInputProperty():
description = "The segment count per iteration. More segment, more smooth but lower performance.",
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:
@ -176,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
@ -185,10 +231,11 @@ 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
),
@ -205,27 +252,29 @@ 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(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,
@ -237,11 +286,10 @@ class BBP_OT_add_straight_rail(SharedExtraTransform, SharedRailSectionInputPrope
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)
@ -251,10 +299,11 @@ class BBP_OT_add_transition_rail(SharedExtraTransform, SharedRailCapInputPropert
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(),
@ -266,10 +315,9 @@ class BBP_OT_add_transition_rail(SharedExtraTransform, SharedRailCapInputPropert
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)
@ -279,6 +327,7 @@ class BBP_OT_add_side_rail(SharedExtraTransform, SharedRailCapInputProperty, Sha
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",
@ -288,11 +337,12 @@ class BBP_OT_add_side_rail(SharedExtraTransform, SharedRailCapInputProperty, Sha
('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(),
@ -305,11 +355,10 @@ class BBP_OT_add_side_rail(SharedExtraTransform, SharedRailCapInputProperty, Sha
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)
@ -319,6 +368,7 @@ class BBP_OT_add_arc_rail(SharedExtraTransform, SharedRailSectionInputProperty,
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",
@ -326,16 +376,18 @@ class BBP_OT_add_arc_rail(SharedExtraTransform, SharedRailSectionInputProperty,
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()
)
@ -343,12 +395,13 @@ class BBP_OT_add_arc_rail(SharedExtraTransform, SharedRailSectionInputProperty,
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)
@ -358,12 +411,15 @@ class BBP_OT_add_spiral_rail(SharedExtraTransform, SharedRailCapInputProperty, S
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(
@ -371,16 +427,18 @@ class BBP_OT_add_spiral_rail(SharedExtraTransform, SharedRailCapInputProperty, S
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()
)
@ -388,12 +446,13 @@ class BBP_OT_add_spiral_rail(SharedExtraTransform, SharedRailCapInputProperty, S
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)
@ -403,6 +462,7 @@ class BBP_OT_add_side_spiral_rail(SharedExtraTransform, SharedRailSectionInputPr
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",
@ -411,16 +471,18 @@ class BBP_OT_add_side_spiral_rail(SharedExtraTransform, SharedRailSectionInputPr
# 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()
)
@ -428,324 +490,18 @@ class BBP_OT_add_side_spiral_rail(SharedExtraTransform, SharedRailSectionInputPr
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], extra_transform: mathutils.Matrix) -> bpy.types.Object:
# create mesh first
bm: bmesh.types.BMesh = bmesh.new()
# call cret fct
fct_poly_cret(bm)
# finish up
mesh: bpy.types.Mesh = bpy.data.meshes.new('Rail')
bm.to_mesh(mesh)
bm.free()
# setup smooth for mesh
mesh.shade_smooth()
# create object and assoc with it
# create info first
rail_info: UTIL_naming_convension.BallanceObjectInfo = UTIL_naming_convension.BallanceObjectInfo.create_from_others(
UTIL_naming_convension.BallanceObjectType.RAIL
)
# then get object name
rail_name: str | None = UTIL_naming_convension.YYCToolchainConvention.set_to_name(rail_info, None)
if rail_name is None: raise UTIL_functions.BBPException('impossible null name')
# create object by name
obj: bpy.types.Object = bpy.data.objects.new(rail_name, mesh)
# assign virtools groups
UTIL_naming_convension.VirtoolsGroupConvention.set_to_object(obj, rail_info, None)
# move to cursor
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
# add extra transform
obj.matrix_world = obj.matrix_world @ extra_transform
# select created object
UTIL_functions.select_certain_objects((obj, ))
# return rail
return obj
def _create_rail_section(
bm: bmesh.types.BMesh,
is_monorail: bool, rail_radius: float, rail_span: float,
matrix: mathutils.Matrix = mathutils.Matrix.Identity(4)) -> None:
"""
Add a rail section.
If created is monorail, the original point locate at the center of section.
Otherwise, the original point locate at the center point of the line connecting between left rail section and right rail section.
The section will be placed in XZ panel.
If ordered is monorail, `rail_span` param will be ignored.
"""
if is_monorail:
# create monorail
bmesh.ops.create_circle(
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
None,
mathutils.Euler((math.radians(90), math.radians(22.5), 0), 'XYZ'),
None
)),
calc_uvs = False
)
else:
# create rail
# create left rail
bmesh.ops.create_circle(
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
mathutils.Vector((-rail_span / 2, 0, 0)),
mathutils.Euler((math.radians(90), 0, 0), 'XYZ'),
None
)),
calc_uvs = False
)
# create right rail
bmesh.ops.create_circle(
bm, cap_ends = False, cap_tris = False, segments = 8, radius = rail_radius,
matrix = typing.cast(mathutils.Matrix, matrix @ mathutils.Matrix.LocRotScale(
mathutils.Vector((rail_span / 2, 0, 0)),
mathutils.Euler((math.radians(90), 0, 0), 'XYZ'),
None
)),
calc_uvs = False
)
def _create_transition_section(
bm: bmesh.types.BMesh,
rail_radius: float, rail_span: float) -> None:
"""
Create the transition section between rail and monorail.
"""
# create rail section
_create_rail_section(bm, False, rail_radius, rail_span)
# create monorail
# calc sink first
monorail_sink: float
try:
monorail_sink = math.sqrt((rail_radius + 2) ** 2 - (rail_span / 2) ** 2) - 2 - rail_radius
except:
monorail_sink = -2 # if sqrt(minus number) happended, it mean no triangle relation. the depth should always be -2.
# create monorail with calculated sink
_create_rail_section(
bm, True, rail_radius, rail_span,
mathutils.Matrix.Translation((0, 0, monorail_sink))
)
def _create_straight_rail(
bm: bmesh.types.BMesh,
is_monorail: bool, rail_radius: float, rail_span: float,
rail_length: float, rail_angle: float,
rail_start_cap: bool, rail_end_cap: bool) -> None:
"""
Add a straight rail.
The original point is same as `_add_rail_section()`.
The start terminal of this straight will be placed in XZ panel.
The expand direction is +Y.
If ordered is monorail, `rail_span` param will be ignored.
The rail angle is in degree unit and indicate how any angle this rail should rotated by its axis.
It usually used to create side rail.
"""
# create section first
_create_rail_section(
bm, is_monorail, rail_radius, rail_span,
mathutils.Matrix.LocRotScale(
None,
mathutils.Euler((0, math.radians(rail_angle), 0), 'XYZ'),
None
)
)
# get start edges
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
# extrude and get end edges
end_edges: list[bmesh.types.BMEdge] = _bmesh_extrude(
bm, start_edges, mathutils.Vector((0, rail_length, 0))
)
# smooth geometry
_bmesh_smooth_all_edges(bm)
# cap start and end edges if needed
if rail_start_cap:
_bmesh_cap(bm, start_edges)
if rail_end_cap:
_bmesh_cap(bm, end_edges)
def _create_transition_rail(
bm: bmesh.types.BMesh,
rail_radius: float, rail_span: float,
rail_length: float,
rail_start_cap: bool, rail_end_cap: bool) -> None:
"""
Add a transition rail.
The original point is same as `_add_transition_section()`.
The start terminal of this straight will be placed in XZ panel.
The expand direction is +Y.
"""
# create section first
_create_transition_section(bm, rail_radius, rail_span)
# get start edges
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
# extrude and get end edges
end_edges: list[bmesh.types.BMEdge] = _bmesh_extrude(
bm, start_edges, mathutils.Vector((0, rail_length, 0))
)
# smooth geometry
_bmesh_smooth_all_edges(bm)
# cap start and end edges if needed
if rail_start_cap:
_bmesh_cap(bm, start_edges)
if rail_end_cap:
_bmesh_cap(bm, end_edges)
def _create_screw_rail(
bm: bmesh.types.BMesh,
is_monorail: bool, rail_radius: float, rail_span: float,
rail_start_cap: bool, rail_end_cap: bool,
rail_screw_angle: float, rail_screw_screw: float, rail_screw_iterations: int,
rail_screw_steps: int, rail_screw_radius: float) -> None:
"""
Add a screw rail.
The original point is same as `_add_rail_section()`.
The start terminal of this straight will be placed in XZ panel.
The expand direction is +Y.
If ordered is monorail, `rail_span` param will be ignored.
Angle is input as degree unit.
"""
# create section first
_create_rail_section(bm, is_monorail, rail_radius, rail_span)
start_edges: list[bmesh.types.BMEdge] = bm.edges[:]
end_edges: list[bmesh.types.BMEdge] = _bmesh_screw(
bm,
bm.verts[:], start_edges,
rail_screw_angle,
rail_screw_steps, rail_screw_iterations,
mathutils.Vector((rail_screw_radius, 0, 0)),
rail_screw_screw
)
# smooth geometry
_bmesh_smooth_all_edges(bm)
# cap start and end edges if needed
if rail_start_cap:
_bmesh_cap(bm, start_edges)
if rail_end_cap:
_bmesh_cap(bm, end_edges)
#endregion
def register() -> None:
bpy.utils.register_class(BBP_OT_add_rail_section)
bpy.utils.register_class(BBP_OT_add_transition_section)

View File

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

View File

@ -1,72 +1,53 @@
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, UTIL_icons_manager, UTIL_naming_convension
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_ballance_map_info
from . 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)
) # type: ignore
use_compress: bpy.props.BoolProperty(
name="Use Compress",
description = "Whether use ZLib to compress result when saving composition.",
default = True,
) # type: ignore
compress_level: bpy.props.IntProperty(
name = "Compress Level",
description = "The ZLib compress level used by Virtools Engine when saving composition.",
min = 1, max = 9,
default = 5,
) # type: ignore
successive_sector: bpy.props.BoolProperty(
name="Successive Sector",
description = "Whether order exporter to use document specified sector count to make sure sector is successive.",
default = True,
) # type: ignore
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,
self.successive_sector,
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
)
@ -75,32 +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')
# show sector info to notice user
layout.separator()
layout.label(text = 'Ballance Params')
box = layout.box()
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
box.prop(self, 'successive_sector')
box.label(text = f'Map Sectors: {map_info.mSectorCount}')
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]
@ -112,12 +73,15 @@ def _export_virtools(
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(
@ -127,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, successive_sector_, 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
@ -150,43 +118,67 @@ 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:
if obj3d not in obj3d_cret_set:
# add into set
obj3d_cret_set.add(obj3d)
# create virtools instance
vtobj3d: bmap.BM3dObject = writer.create_3dobject()
# add into result list
obj3d_crets.append((obj3d, vtobj3d))
# 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)
# create virtools instance
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 sector group first if user ordered
# This step is designed for ensure that the created sector group is successive.
@ -197,9 +189,7 @@ def _export_virtools_groups(
#
# So we create all needed sector group in here to make sure exported virtools file can be read by Ballancde correctly.
if successive_sector:
map_info: PROP_ballance_map_info.RawBallanceMapInfo
map_info = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
for i in range(map_info.mSectorCount):
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:
@ -227,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,
@ -236,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)
@ -282,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:
@ -374,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
@ -396,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
@ -461,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
@ -500,7 +539,8 @@ 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()

View File

@ -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:

View File

@ -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, UTIL_naming_convension
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_ballance_map_info
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,6 +355,62 @@ 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,
@ -352,7 +423,8 @@ def _import_virtools_groups(
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
@ -367,7 +439,7 @@ def _import_virtools_groups(
# 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
@ -387,7 +459,8 @@ def _import_virtools_groups(
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)
@ -398,6 +471,10 @@ def _import_virtools_groups(
gpoper.clear_groups()
gpoper.add_groups(mapv)
# step
progress.step()
# leave progress
progress.leave_substeps()

View File

@ -5,8 +5,9 @@ from . import PROP_virtools_material, PROP_preferences
class BBP_OT_fix_all_material(bpy.types.Operator):
"""Fix All Materials by Its Referred Ballance Texture Name."""
bl_idname = "bbp.fix_all_material"
bl_label = "Fix Material"
bl_label = "Fix All Materials"
bl_options = {'UNDO'}
bl_translation_context = 'BBP_OT_fix_all_material'
@classmethod
def poll(cls, context):
@ -29,7 +30,9 @@ class BBP_OT_fix_all_material(bpy.types.Operator):
counter_suc += 1
# report and return
self.report({'INFO'}, f'Fix {counter_suc}/{counter_all} materials.')
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:

View File

@ -43,24 +43,29 @@ 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,22 +112,25 @@ 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
@ -127,16 +138,18 @@ class BBP_OT_legacy_align(bpy.types.Operator):
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()
# INFO: YYC MARK:
# 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.
@ -145,8 +158,9 @@ class BBP_OT_legacy_align(bpy.types.Operator):
# 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,
@ -157,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)
@ -171,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
@ -182,8 +198,10 @@ 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

View File

@ -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,13 +82,17 @@ 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
)

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

View File

@ -32,12 +32,14 @@ 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
@ -50,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)
)
@ -57,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.
@ -86,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:
@ -94,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:
@ -121,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(cls, context):
return len(bpy.context.selected_objects) != 0
return len(context.selected_objects) != 0
def invoke(self, context, event):
wm = context.window_manager
@ -132,7 +136,7 @@ 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.")
@ -146,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(cls, context):
return len(bpy.context.selected_objects) != 0
return len(context.selected_objects) != 0
def invoke(self, context, event):
wm = context.window_manager
@ -157,7 +162,7 @@ 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.")
@ -171,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(cls, context):
return len(bpy.context.selected_objects) != 0
return len(context.selected_objects) != 0
def invoke(self, context, event):
wm = context.window_manager
@ -182,7 +188,7 @@ 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.")

View File

@ -78,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",
@ -85,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(
@ -94,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(
@ -103,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(
@ -114,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(
@ -122,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(
@ -131,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':
@ -162,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:

View File

@ -8,10 +8,11 @@ class BBP_OT_rail_uv(bpy.types.Operator):
bl_idname = "bbp.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,

View File

@ -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):

View File

@ -22,7 +22,8 @@ class BBP_PG_ballance_map_info(bpy.types.PropertyGroup):
default = 1,
max = 999, min = 1,
soft_max = 8, soft_min = 1,
step = 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:
@ -51,6 +52,7 @@ class BBP_PT_ballance_map_info(bpy.types.Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "scene"
bl_translation_context = 'BBP_PT_ballance_map_info'
@classmethod
def poll(cls, context):

View File

@ -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):

View File

@ -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:

View File

@ -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
def get_ptrprop_resolver() -> BBP_PG_ptrprop_resolver:
return bpy.context.scene.bbp_ptrprop_resolver
# 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
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')
#endregion
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_ptrprop_resolver(scene: bpy.types.Scene) -> BBP_PG_ptrprop_resolver:
return scene.bbp_ptrprop_resolver
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 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
)
# 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='')
@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 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() -> 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)

View File

@ -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", "",

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

View File

@ -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,7 +132,8 @@ 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(
@ -142,7 +143,8 @@ 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(
@ -152,7 +154,8 @@ 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(
@ -162,7 +165,8 @@ 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(
@ -170,13 +174,15 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
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(
@ -186,89 +192,103 @@ 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(
@ -276,21 +296,24 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
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
@ -877,13 +900,14 @@ 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'}
@ -892,6 +916,7 @@ class BBP_OT_fix_single_material(bpy.types.Operator):
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):
@ -905,13 +930,13 @@ class BBP_OT_fix_single_material(bpy.types.Operator):
def execute(self, context):
# get mtl and try to fix
mtl: bpy.types.Material = context.material
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 done.')
self.report({'INFO'}, 'Fix material successfully.')
else:
# otherwise report warning
self.report({'WARNING'}, 'This material is not suit for fixer.')
@ -923,6 +948,7 @@ class BBP_OT_preset_virtools_material(bpy.types.Operator):
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",
@ -943,7 +969,7 @@ 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
@ -956,7 +982,8 @@ class BBP_OT_direct_set_virtools_texture(bpy.types.Operator, UTIL_file_browser.I
"""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):
@ -977,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
@ -1009,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):
@ -1017,16 +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_fix_single_material.bl_idname, text = '', icon = "MODIFIER")
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')
@ -1034,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')
@ -1057,27 +1091,27 @@ 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() -> None:

View File

@ -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):

View File

@ -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

View File

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

View File

@ -1,789 +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_BMapLibPath: str = os.path.join(os.path.dirname(__file__), _g_BMapLibName)
_g_BMapModule: ctypes.CDLL | None = None
try:
_g_BMapModule = ctypes.cdll.LoadLibrary(_g_BMapLibPath)
except:
print(f'Fail to load native BMap dynamic library file "{_g_BMapLibPath}".')
_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
##### GENERATED FUNCTIONS BEGIN #####
## 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])
##### GENERATED FUNCTIONS END #####
#endregion

View File

@ -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

View File

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

View File

@ -104,7 +104,11 @@ 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

View File

@ -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:

View File

@ -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

View File

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

View File

@ -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,10 +67,7 @@ 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:
@ -171,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

View File

@ -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,6 +159,7 @@ 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(
@ -135,6 +167,7 @@ class ImportParams():
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(
@ -142,6 +175,15 @@ class ImportParams():
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(
@ -149,17 +191,29 @@ class ImportParams():
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, layout: bpy.types.UILayout) -> None:
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
# 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

View File

@ -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:
@ -280,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
@ -300,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
@ -320,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
@ -335,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,
@ -343,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
@ -359,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)
@ -367,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
@ -416,8 +444,9 @@ class VirtoolsGroupConvention():
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
@ -469,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
@ -512,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
@ -575,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
@ -620,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
View 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

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

View File

@ -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,13 +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.
# 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_PyBMapDefaultEncoding: str = 'cp1252;gb2312'
g_PyBMapDefaultEncodings: tuple[str, ...] = (
'cp1252',
'gbk'
)
#endregion

View File

@ -5,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
@ -16,42 +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
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')
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
@ -61,49 +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 =====
@ -112,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
@ -127,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')
@ -143,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
@ -156,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
@ -187,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),
@ -196,12 +234,15 @@ 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()
@ -223,6 +264,7 @@ def register() -> None:
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
@ -247,6 +289,7 @@ 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()
@ -268,12 +311,15 @@ def unregister() -> None:
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()

View File

@ -6,7 +6,7 @@ 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.1.0"
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>"
@ -78,4 +78,5 @@ paths_exclude_pattern = [
"/raw_jsons", # Raw JSONs.
"/raw_icons", # Raw Icons.
"/tools", # Assistant tools.
"/i18n", # GNU gettext Translation.
]

5269
bbp_ng/i18n/zh_HANS.po Normal file

File diff suppressed because it is too large Load Diff

View 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)"
}
]
},

View File

@ -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)"
}
]
},

View 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)"
}
]
}
]

View File

@ -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",

View File

@ -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)"
}
]
},

View File

@ -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",

View File

@ -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)"
}
]
},

View File

@ -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()"
}
]
},
{
"identifier": "floor_triangle_bottom",
"showcase": null,
"params": [
{
"field": "length",
"data": "5.0"
},
{
"field": "uv_width",
"data": "width / 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)"
},
{
"skip": "False",
"data": "(0, width, 0)"
},
{
"skip": "False",
"data": "(length, 0, 0)"
},
{
"skip": "False",
"data": "(length, width, 0)"
"params": {
"top_pos": "(0, 0)",
"bottom_pos": "(length, 0)",
"tip_pos": "(tip_offset, width)"
},
"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": []
]
}
]

View File

@ -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[2], face[3], face[5], False)",
"is_sink": "is_sink",
"is_ribbon": "False"
},
"transform": "move(0, 5, 0) @ scale(1, -1, 1)"
}
]
},
{
"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)"
}
]
}

View File

@ -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)"
}
]
},

View File

@ -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",

View File

@ -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
View 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
```

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

View File

@ -1,43 +1,55 @@
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:
# get folder path
root_folder: str = common.get_plugin_folder()
__mReporter: bme_utils.Reporter
# prepare handler
def folder_handler(src_folder: str, dst_folder: str) -> None:
# just create folder
print(f'Creating Folder: {src_folder} -> {dst_folder}')
os.makedirs(dst_folder, exist_ok = True)
def file_handler(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)
def __init__(self):
self.__mReporter = bme_utils.Reporter()
# call common processor
common.common_file_migrator(
os.path.join(root_folder, 'raw_icons'),
os.path.join(root_folder, 'icons'),
folder_handler,
file_handler
)
def run(self) -> None:
self.__create_thumbnails()
print('Done.')
def __create_thumbnails(self) -> None:
# get folder path
root_folder: str = common.get_plugin_folder()
# prepare handler
def folder_handler(rel_name: str, src_folder: str, dst_folder: str) -> None:
# just create folder
self.__mReporter.info(f'Creating Folder: {src_folder} -> {dst_folder}')
os.makedirs(dst_folder, exist_ok = True)
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
self.__mReporter.info(f'Processing Thumbnail: {src_file} -> {dst_file}')
self.__resize_image(src_file, dst_file)
# call common processor
common.common_file_migrator(
os.path.join(root_folder, 'raw_icons'),
os.path.join(root_folder, 'icons'),
folder_handler,
file_handler
)
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()

View File

@ -1,43 +1,84 @@
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:
class JsonCompressor():
__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 = common.get_plugin_folder()
# prepare handler
def folder_handler(rel_name: str, src_folder: str, dst_folder: str) -> None:
# just create folder
self.__mReporter.info(f'Creating Folder: {src_folder} -> {dst_folder}')
os.makedirs(dst_folder, exist_ok = True)
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
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(
os.path.join(root_folder, 'raw_jsons'),
os.path.join(root_folder, 'jsons'),
folder_handler,
file_handler
)
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(
json.load(fr), # load from src file
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
)
def create_compressed_jsons() -> None:
# get folder path
root_folder: str = common.get_plugin_folder()
# prepare handler
def folder_handler(src_folder: str, dst_folder: str) -> None:
# just create folder
print(f'Creating Folder: {src_folder} -> {dst_folder}')
os.makedirs(dst_folder, exist_ok = True)
def file_handler(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)
# call common processor
common.common_file_migrator(
os.path.join(root_folder, 'raw_jsons'),
os.path.join(root_folder, 'jsons'),
folder_handler,
file_handler
)
print('Done.')
if __name__ == '__main__':
create_compressed_jsons()
with JsonCompressor() as json_compressor:
json_compressor.run()

View File

@ -27,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.
@ -37,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.
@ -55,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
View 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
View File

@ -1,2 +1,2 @@
# mkdocs
# ===== MkDocs =====
site/

View File

@ -1,5 +1,8 @@
# 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.

View File

@ -1,5 +1,8 @@
# 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

View File

@ -1,5 +1,8 @@
# 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.
@ -21,7 +24,7 @@ The default values for the options in the Conflict Options section are the solut
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.
* gb2312: The default encoding for Chinese Windows system.
* 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.

View File

@ -1,5 +1,8 @@
# 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.
![](../imgs/rail-adder.png)

View File

@ -1,5 +1,8 @@
# 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.).

View File

@ -1,5 +1,8 @@
# 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.

View File

@ -0,0 +1,4 @@
# ZZQ Features
!!! info "No Translation"
This page has not been translated. Please see source page of this page.

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@ -23,6 +23,12 @@
最后它指示我们需要配置这个路面哪些面需要显示。需要注意的是Top和Bottom指的是沿高度方向Z轴的顶面和底面而FrontBackLeftRight则是以头顶朝向-X轴眼睛朝向-Z轴俯视状态下的前后左右。您可能注意到这6个面按钮中间有一个透视的六面体实际上这六个面的选项的位置与这个透视六面体的六个面的位置是一一对应的。
## 额外变换
在BME配置对话框的底部您总是可以找到一个被称为额外变换的区域。在这个区域中有额外移动和额外旋转两个选项可以配置。这些字段主要是为了可视化服务的。
在说明这些字段的实际作用前你需要知道BME创建的路面总是以当前3D游标的位置进行创建的即3D游标在哪里新创建的路面就在哪里。这主要是为了可视化来考虑到用户可以先将3D游标移动到想要添加路面的位置再添加对应的BME结构便可以在调整参数的时候即时预览到结果。有时候你的3D游标的位置不是那么的准确又或者最终生成的结构需要一定的旋转才是你期望的此时就可以利用额外变换字段为最终生成的结构增加相对于3D游标的额外的移动和旋转使之处于正确的位置这样就可以在调整参数的时候实时预览到结果了。
## 小贴士
每个路面类型,其配置的条目数是不同的,因此对于不同路面类型,需要根据配置的提示文本来了解对应配置具体是做什么的。一些路面类型所需要设置的条目可能很多,另一些则根本没有配置条目。

View File

@ -10,7 +10,7 @@ BBP的Virtools文件原生导入导出功能依赖BMap以及其Python绑定PyBMa
然后我们需要配置PyBMap。PyBMap是随LibCmo一起提供的。请按照PyBMap的手册将编译得到的二进制BMap库和PyBMap结合在一起。即完成PyBMap配置。
然后我们需要将配置好的PyBMap拷贝到本项目的`bbp_ng/PyBMap`下即可完成此步
然后我们需要将配置好的PyBMap拷贝到本项目的根目录下即可完成此步(即存在`bbp_ng/PyBMap`文件夹为配置好的PyBMap
## 生成缩略图和压缩JSON
@ -18,6 +18,70 @@ BBP内置了一系列自定义图标以及其组件BME需要的用于描述
转到`bbp_ng/tools`文件夹下,运行`python3 build_icons.py`将批量生成缩略图此功能需要PIL库请提前通过pip安装。其实际上是将`bbp_ng/raw_icons`目录下的原始图片生成对应的缩略图并存储于`bbp_ng/icons`文件夹下。运行`python3 build_jsons.py`将压缩JSON。其实际上是将`bbp_ng/raw_jsons`目录下的原始JSON文件读取压缩再写入到`bbp_ng/jsons`文件夹下。
## 翻译
BBP插件支持多语言功能因此在正式发布前我们需要先提取并更新要翻译的内容翻译完所有内容后再进行下一步操作。
Blender对于插件的多语言支持不尽如人意且BBP的设计比较特殊因此BBP采用了一套与Blender官方推荐的插件翻译管理方式不同的方式来管理翻译即使用PO文件来管理翻译而非官方推荐的Python脚本格式。
!!! info "不要提交Python格式的翻译"
如上文所述BBP使用PO文件来管理翻译而不是Blender官方建议的Python源码格式。但这并不能阻止Blender的多语言插件将Python源码格式的翻译写入到插件源码中。重复提交翻译不仅增加仓库体积也不利于管理因此BBP要求你在提交前需要删除Python格式的翻译。
具体的操作方法是在提交前,打开`bbp_ng/UTIL_translation.py`文件,将翻译元组变量`translations_tuple`的值改为空元组(即`translations_tuple = ()`)。
### 提取翻译模板
在翻译之前首先你需要意识到BBP需要翻译的文本由两部分组成一部分是BBP插件本身可以通过Blender自带的多语言插件来实现待翻译文本的提取。另一部分是BME组件中的用于描述结构的JSON文件其中各个展示用字段的名称需要进行翻译而这一部分Blender的多语言插件无能为力因为它是动态加载的。幸运的是我们已经写好了一个提取器可以提取BME的JSON文件中的相关待翻译文本当你在进行上一步压缩JSON的操作时实际上压缩器也一并运行了并提取了待翻译文本写入了`bbp_ng/i18n/bme.pot`文件中。那么接下来的任务就只剩下提取插件部分的翻译了。
首先你需要启用Blender内置的多语言插件Manage UI translations。为了启用它你可能还需要下载对应Blender版本的源代码和翻译仓库具体操作方法可参考[Blender的官方文档](https://developer.blender.org/docs/handbook/translating/translator_guide/)。在启用插件并在偏好设置中配置了合适的相关路径后你就可以在Render面板下找到I18n Update Translation面板接下来就可以提取翻译了。按照以下步骤提取翻译
1. 首先确保关闭了所有Blender进程否则插件会保持在加载状态对翻译元组变量的修改会不起作用。
1. 将插件中翻译元组变量`translations_tuple`的值改为空元组(参见前文有关提交的注意事项)。将翻译元组设置为空可以将插件的翻译状态归零,使得后文进行的文本提取操作不会受到已有翻译的干扰。
1. 打开Blender转到I18n Update Translation面板点击Deselect All取消所有语言的选中然后仅勾选下列语言右侧的框因为BBP目前仅支持有限的语言
* Simplified Chinese (简体中文)
1. 点击最下方一栏的Refresh I18n Data按钮然后在弹出的窗口中选择Ballance Blender Plugin等待一会后插件就会完成待翻译字符的提取。此时插件只是将他们按照Blender推荐的方式以Python源码的格式将翻译字段提取到了插件的源代码中。
1. 为了获得我们希望的可以用于编辑的POT文件还需要点击Export PO按钮在弹出的窗口中选择Ballance Blender Plugin保存的位置可以选择任意的文件夹例如桌面因为它会产生许多文件其中只有POT文件才是我们想要的取消勾选右侧的Update Existing选项并确保Export POT是勾选的最后进行保存。导出完成后可以在你选择的文件夹中找到一个名为`blender.pot`的翻译模板文件,以及众多以语言标识符为文件名的`.po`文件。
1. 你需要复制`blender.pot``bbp_ng/i18n`文件夹下,并将其重命名为`bbp_ng.pot`。至此我们提取了所有需要翻译的内容。
### 合并翻译模板
现在`bbp_ng/i18n`文件夹下有两个POT文件分别代表了两部分提取的待翻译文本我们需要把他们合并起来。在`bbp_ng/i18n`文件夹执行`xgettext -o blender.pot bbp_ng.pot bme.pot`来进行合并。合并完成后的`bbp_ng/i18n/blender.pot`将用作翻译模板。
### 创建新语言翻译
如果未来BBP需要支持更多语言你需要从POT文件为新语言创建其对应的PO翻译文件。你可以通过以下方式之一来创建它们。
1. 通过使用Poedit等软件打开POT文件选择创建新的翻译再进行保存来创建。
1. 通过诸如`msginit -i blender.pot -o zh_HANS.po -l zh_CN.utf8`的命令来创建新语言的PO翻译文件。
创建的方式多种多样唯一需要注意的是你需要按下表所示设定文件名文件名错误Blender会拒绝接受和区域名称使用`msginit`时会用到目的是确保是UTF8格式编码的
|语言|文件名|区域名称|
|:---|:---|:---|
|Simplified Chinese (简体中文)|`zh_HANS.po`|`zh_CN.utf8`|
### 更新语言翻译
创建新的语言翻译并不常见,更为常见的操作是根据翻译模板,对现有语言翻译文件进行更新。你可以通过以下方式之一来更新它们
1. 通过Poedit等软件打开PO文件再选择从POT文件更新。
1. 通过诸如`msgmerge -U zh-HANS.po blender.pot --backup=none`的命令来更新。
### 进行翻译
在更新完所有语言的PO翻译文件后你可以选择你喜欢的方式来进行翻译例如使用Poedit或直接进行编辑。
BBP要求使用KDE社区的翻译规范来规范插件的翻译。例如你可以在[KDE中国相关网页](https://kde-china.org/tutorial.html)找到KDE社区有关简体中文的翻译标准。
### 翻译回写
PO格式的翻译并不能被Blender识别因此在翻译完成后你还需要继续借助Blender的多语言插件将PO文件回写成Blender可识别的Python源码格式的翻译。由于Blender的多语言插件设计的问题我们不能直接使用Import PO功能将PO文件回写成Python源码格式。你需要按照下列步骤依次操作才可以将PO翻译导入插件中
1. 首先确保关闭了所有Blender进程否则插件会保持在加载状态对翻译元组变量的修改会不起作用。
1. 将插件中翻译元组变量`translations_tuple`的值改为空元组参见前文有关提交的注意事项。这一步操作的意图是让整个插件不存在翻译条目这样之后在使用Import PO功能的时候Blender的多语言插件就会认为PO文件中存储的所有字段都是要翻译的就不会出现只导入了一部分翻译的情况因为BME部分的翻译是后来合并入的
1. 打开Blender转到I18n Update Translation面板按照提取翻译模板时的操作方法在语言列表中仅选中需要翻译的语言。
1. 点击最下方一栏的Import PO按钮然后在弹出的窗口中选择Ballance Blender Plugin然后选择`bbp_ng/i18n`文件夹进行导入。这样我们就完成了将PO文件导入为Blender可识别的Python源码格式的操作。
## 打包
从Blender 4.2 LTS开始插件使用Blender自带的打包功能进行打包。
@ -26,6 +90,7 @@ BBP内置了一系列自定义图标以及其组件BME需要的用于描述
Blender会根据`blender_manifest.toml`的指示,在排除下列文件的情况下将插件打包:
* `bbp_ng/i18n`:翻译文件夹。
* `bbp_ng/raw_icons`:原始图片文件夹。
* `bbp_ng/raw_jsons`原始JSON文件夹。
* `bbp_ng/tools`:编译用工具。

View File

@ -9,7 +9,7 @@
### 冲突解决选项
Conflict Options冲突解决选项章节指示了当导入器遇到物体名称重复的情况时该如何处理。分为4个等级分别针对Object物体Mesh网格Material材质Texture贴图。处理方式则有2种Rename重命名和Use Current使用当前。选择重命名后当遇到重复名称时将会自动为其添加名称后缀使其名称唯一化。而选择使用当前则会忽略从文件中导入此项转而使用Blender文档中已经存在的同名的项目。
Conflict Options冲突解决选项章节指示了当导入器遇到物体名称重复的情况时该如何处理。分为4个等级分别针对Object物体Light灯光Mesh网格Material材质Texture贴图。处理方式则有2种Rename重命名和Use Current使用当前。选择重命名后当遇到重复名称时将会自动为其添加名称后缀使其名称唯一化。而选择使用当前则会忽略从文件中导入此项转而使用Blender文档中已经存在的同名的项目。
!!! info "与Virtools冲突解决的不同"
相比较于在Virtools的冲突解决对话框BBP插件提供的冲突解决选项不支持替换功能同时其粒度也不支持精细到单个实例只能针对一整个类型进行设定。因此你无法单独为每一个冲突的实例设置不同的冲突解决方案。但目前这种设置已经能满足绝大对数的使用场景了。
@ -18,10 +18,10 @@ Conflict Options冲突解决选项章节指示了当导入器遇到物体
### Virtools参数
众所周知Virtools使用基于系统的多字节字符编码来处理文档因而很容易出现所谓乱码问题。Blender本身不会出现乱码问题然而如果我们不能以正确的编码读取Virtools文档则当Virtools文档被导入Blender时其中存储的字符仍然可能会呈现乱码状态。Virtools ParamsVirtools参数章节的Encodings编码属性用于指定读取Virtools文档的编码。可以指定多个编码多个编码之间用`;`(分号)分隔。下面列出一些常用的编码:
众所周知Virtools使用基于系统的多字节字符编码来处理文档因而很容易出现所谓乱码问题。Blender本身不会出现乱码问题然而如果我们不能以正确的编码读取Virtools文档则当Virtools文档被导入Blender时其中存储的字符仍然可能会呈现乱码状态。Virtools ParamsVirtools参数章节的Encodings编码属性用于指定读取Virtools文档的编码。可以指定多个编码越靠上的编码越优先使用,当无法解码时才会使用下一级编码。下面列出一些常用的编码:
* cp1252Ballance所用的西欧编码
* gb2312中文Windows系统默认编码
* gbk中文Windows系统默认编码
编码属性非常重要如果设置了错误的编码导入Blender的各类物体的名称会出现不可认知的情况又或者会导致程序出错。

View File

@ -60,6 +60,8 @@
圆弧轨的Steps步数步数表示这个圆弧轨的分段数数字越大圆弧轨看起来越平滑相对的顶点也会更多对存储空间和渲染的要求也越高因此需要选择一个合理的数值。
圆弧轨的Flip翻转选项可以让你在指定的轴向上翻转也可以视为镜像生成的结构三个轴上的翻转选项可以满足所有对应手性结构的生成。
圆弧轨同样支持双轨单轨选择,可以创建单轨圆弧轨和双轨圆弧轨。也支持封盖属性。
### Spiral Rail
@ -70,7 +72,7 @@
螺旋轨也需要设置Steps步数属性含义与圆弧轨一致。但需要注意的是步数指的是每一个迭代内的步数个数并不是总体的步数。因此调整迭代属性的时候不需要再调整步数属性。
螺旋轨也有封盖属性。
螺旋轨也有封盖属性和翻转属性
### Side Spiral Rail
@ -78,4 +80,10 @@
侧边螺旋轨没有螺距属性,因为侧边螺旋轨在设计上,相邻的旋进是共用一条边的,因此螺距是固定的。
侧边螺旋轨设定中的Radius半径Iterations迭代和Steps步数属性含义均与螺旋轨一致。侧边螺旋轨也有封盖属性。
侧边螺旋轨设定中的Radius半径Iterations迭代和Steps步数属性含义均与螺旋轨一致。
侧边螺旋轨也有封盖属性和翻转属性。
## 额外变换
在添加直线钢轨和曲线钢轨的时候,你总可以设置一个被称为额外变换的属性。其效果与添加路面中的额外变换字段一致,都是为可视化服务的。

View File

@ -12,7 +12,7 @@ BBP不是完美的由于BBP的Virtools文件导入导出模块是由C++编写
## 哪部分出错了
对于BBP插件而言如果你在Python异常输出中观察到类似于`BMap operation failed`的字样,或者在`<插件安装位置>/PyBMap`文件夹下观察到了`IronPad.log`文件则说明BBP插件的由C++编写的BMap部分出错了**你需要立即保存你当前的Blender文档并退出Blender。** 因为此时插件已处于非正常状态,你不应继续任何操作。
对于BBP插件而言如果你在Python异常输出中观察到类似于`BMap operation failed`的字样,或者在`%LOCALAPPDATA%/CrashDumps`文件夹(`%LOCALAPPDATA%`是一个Windows环境变量如果你不清楚它是什么你可以简单地将该地址直接粘贴到Windows文件资源管理器中Windows文件资源管理器会自动为你导航到正确位置下观察到了`blender.exe.<xxx>.log`文件(其中`<xxx>`是一串数字)则说明BBP插件的由C++编写的BMap部分出错了**你需要立即保存你当前的Blender文档并退出Blender。** 因为此时插件已处于非正常状态,你不应继续任何操作。
如果并没有上述情况那么这就只是普通的Python代码执行错误不需要过度担心但错误仍然是致命的建议做完所有必要的操作后退出Blender并报告错误。
@ -26,4 +26,4 @@ BBP不是完美的由于BBP的Virtools文件导入导出模块是由C++编写
首先你需要详细描述你是如何引发这个错误的,这个错误有什么结果。如果可以上传导致错误的文档,请尽量上传(如果不方便公开发布,可以通过邮件等私有渠道发送给作者)。
你还需要提供Blender控制台中输出的Python堆栈报告使用`Window - Toggle System Console`打开控制台。如果你的错误是BMap部分的错误你还需要提供`<插件安装位置>/PyBMap`文件夹下的`IronPad.log``IronPad.dmp`文件以方便开发者定位错误
你还需要提供Blender控制台中输出的Python堆栈报告使用`Window - Toggle System Console`打开控制台。如果你的错误是BMap部分的错误你还需要提供BMap模块输出的错误日志文件`blender.exe.<xxx>.log`和内存转储文件`blender.exe.<xxx>.dmp``<xxx>`同理为一串数字),这些文件同样可在`%LOCALAPPDATA%/CrashDumps`文件夹中找到

View File

@ -10,6 +10,8 @@ BBP插件为每一个Blender物体添加了新的属性被称为Virtools Grou
BBP还在Blender的其它菜单提供了对Virtools组的访问具体内容请参阅[按组操作](./group-operations.md)。
需要注意的是Virtools组仅对网格物体生效当你在其它物体上打开Virtools组面板时你会在面板中看到一条警告消息提示你Virtools组在该物体上无效。在非网格物体上设置的Virtools组数据尽管会被Blender承认和存储但不会在导出时保存到Virtools文件中。
## Virtools材质
插件为每一个Blender材质添加了新的属性被称为Virtools Material。它在Virtools材质与Blender材质之间架起沟通的桥梁。转到`Material`属性面板,选择一个材质,即可以找到`Virtools Material`面板。
@ -57,3 +59,11 @@ BBP插件为所有Blender网格添加了新的属性称为Virtools Mesh。转
![](../imgs/virtools-mesh.png)
Virtools网格目前只是作为兼容来使用的。其只有Lit Mode一个属性可以设置。多数早期地图由于不知道如何正确设置材质导致路面发黑所以经常将Lit Mode设置为Prelit以让路面正常显示。此属性是为了兼容这种设计而存在的用户通常无需设置此选项。
## Virtools灯光
BBP插件为所有Blender灯光添加了新的属性称为Virtools Light。转到`Data`属性面板,即可以找到`Virtools Light`面板。
![](../imgs/virtools-light.png)
与Virtools材质类似Virtools的灯光系统与Blender的灯光系统差距较大Virtools灯光相当于一个桥梁它可以准确地反映Virtools的设置使得其可以完美地存储于Blender文件中并在导入导出时提供必要的数据。同时该面板并提供一个应用按钮用以将Virtools灯光设置应用到Blender灯光中。

View File

@ -0,0 +1,9 @@
# ZZQ功能
本插件在制作过程中得到了来自ZZQ的许多建议这些建议所产生的功能比较杂乱因此在一个单独的页面中一起描述。
## 窥视归组并转换为网格
窥视归组并转换为网格其全称为窥视并复制曲线倒角物体的Virtools归组信息后再转换为网格。你可以选中一些物体后右键在物体上下文菜单中找到这一功能。
该功能正如其名,其将选中的物体转换为网格,如果选中的物体是曲线,且设置了倒角物体,则将倒角物体的归组信息赋予当前曲线(覆盖曲线当前归组设置)。如果选中的物体不是曲线,或者是曲线但没有倒角物体,那么该功能与执行转换为网格无异。该功能在放样建模时极为有用,因为只需要为截面物体进行正确的归组,然后再使用此功能将曲线转换为网格,就可以确保放样后的物体归组正确。

View File

@ -20,6 +20,7 @@ nav:
- 'Add Floor': 'en/bme-adder.md'
- 'Add Rail': 'en/rail-adder.md'
- 'Add Component': 'en/component-adder.md'
- 'ZZQ Features': 'en/zzq-features.md'
- 'Compile and Distribute Plugin': 'en/compile-distribute-plugin.md'
- 'Report Issue': 'en/report-bugs.md'
- 'Technical Information': 'en/tech-infos.md'
@ -37,6 +38,7 @@ nav:
- '添加路面': 'zh-cn/bme-adder.md'
- '添加钢轨': 'zh-cn/rail-adder.md'
- '添加机关': 'zh-cn/component-adder.md'
- 'ZZQ功能': 'zh-cn/zzq-features.md'
- '编译与分发插件': 'zh-cn/compile-distribute-plugin.md'
- '报告问题': 'zh-cn/report-bugs.md'
- '技术信息': 'zh-cn/tech-infos.md'