36 Commits

Author SHA1 Message Date
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
c894d88c54 doc: modify document to mark changes
- add universal encoding infos in documentation.
- bump up version to 4.1 for release.
2024-09-21 23:30:56 +08:00
c8d59ef5f4 feat: update PyBMap dependency.
- update PyBMap dependency.
- use BMap 0.2 instead of old one.
- change default encoding name string according to the change of LibCmo changes.
2024-09-21 22:59:14 +08:00
f5c50ae079 fix: fix legacy align issue.
- fix issus that legacy align can not align object with non-1 scale factors.
- refactor some functions in legacy align to have better looking.
- fix issus that legacy fail to align objects since second executing by forcely updating view layer.
2024-09-07 20:34:22 +08:00
b1199f6a21 doc: translate documentation 2024-07-27 11:44:35 +08:00
47a8d81ecd doc: translate documentation 2024-07-26 09:46:16 +08:00
24ac07a3b3 doc: translate documentation 2024-07-25 10:37:16 +08:00
b647d7256f doc: translate documentation 2024-07-24 13:49:13 +08:00
421f01b3db doc: translate documentation 2024-07-23 11:01:41 +08:00
f215d3487f doc: translate documentation 2024-07-22 16:40:52 +08:00
1661f82a07 doc: update english documentation 2024-07-21 14:42:02 +08:00
512e7e868b doc: add english documentation.
- add english documentation for install-plugin and tech-info chapters.
- modify gitignore because we do not need redist folder any more.
2024-07-20 10:26:50 +08:00
427bad4f6b fix: fix various trivial issues
- update blender_manifest.toml to the latest version and fix the issues raised by blender when packaging.
- use blender do packaging work. remove redist.py because blender_manifest.toml has gitignore like filter feature when packaging.
- update document about installing, configurating, building plugin for blender 4.2.
- update a document image for blender 4.2.
2024-07-19 15:25:38 +08:00
209d212287 fix: fix blender mesh writer issue.
- fix blender mesh writer with the reference of FBX Importer.
- ballance map importer now may works as expected (still need more review).
2024-07-18 20:23:21 +08:00
2271b0a621 fix: remove bl_info.
- remove bl_info in __init__.py because blender_manifest.toml has replace it.
2024-07-17 21:20:40 +08:00
6940428b88 fix: fix blender 4.2 mesh issue.
- fix issue that blender 4.2 lost use_auto_smooth and calc_split_normals. (it almost works but not perfect. still need more debugging)
- remove depracted material blend_mode. use modern one instead. (the modern setting make me consufes but it works)
2024-07-17 19:45:35 +08:00
aa602a7bb8 fix: update to blender 4.2 LTS
- update to Blender 4.2 LTS
- fix issue that can not find specular property in priniciple BSDF when applying virtools material.
- update blender manifest toml to let plugin works.
- there are various mesh works need to be fixed in later commits. this commit can not works normally on blender 4.2
2024-07-17 09:23:16 +08:00
8588f097a2 fix: change default screw steps to 28.
- change default screw steps to 28 (7 steps for 90 degree)
- update blender mesh annotation.
2024-05-25 20:32:30 +08:00
5d8ffb7e48 feat: add redist script
- add redist script for the convenience of building plugin package, especially for Blender 4.2
2024-05-21 16:19:13 +08:00
270fddff52 feat: do some trivial adaptions for blender 4.2 LTS
- add blender_manifest.toml
- add a removal note for bl_info in __init__.py.
- it doesn't mean that this plugin now can be used in blender 4.2. I just add some trivial changes which do not break current compatibility to reduce migration work in future.
2024-05-17 09:15:12 +08:00
084e7fbe61 feat: add extra transform for rail creation
- add extra transform for rail creation.
- remove scale from extra transform because it is rarely used. because Ballance can not handle the physicalization of scaled object.
2024-05-13 13:39:42 +08:00
190be6ec61 fix: restore extra transform to identy when entering bme operator. 2024-05-10 15:33:28 +08:00
36e925101e feat: extra transform properties for bme creation.
- add extra transform properties (translation, rotation and scale) for bme creation operator. this is good for "what you seen is what you gotten" and can increase user experience when add bme protorypes. please note these extra transform is applied to object after moving to cursor, not the mesh self.
- fix a weired bug that bme creation execute() may get outdated configurations when switch bme type in pop up window. the solution is simply adding an extra outdated flag checker at the head of execute(). but i have no spare time to research whether this patch is correct for this bug.
2024-05-10 14:44:37 +08:00
84e7e8380f feat: hide some operators in edit mode and add some notify for grouping oper.
- now grouping, ungrouping clear groups operators will show a report at the bottom of blender to indicate their work have done.
- disable fix material, 3ds max align, select by virtools group operators in edit mode to prevent any possible undefined behavior.
2024-04-22 21:06:41 +08:00
c58af8ce48 fix: fix face generation error of BME streets prototype. 2024-04-22 21:06:35 +08:00
07298fd21c feat: add blc params in exporter. fix bme.
- add ballance params section and successive switch in virtools file exporter to allow user manually cancel sector group ensurance which give user a way to export component, not the whole level.
- add mirror matrix detection in bme creation. a mirror matrix will reverse triangle indice order. we need reverse it again when facing mirror matrix. this resolves the issue when applying term 3.
- use mirror, not rotation to create straight floor in bme which give a better looks in game.
2024-04-07 10:35:06 +08:00
3a1a0fb0f6 doc: update document
- add section about ballance related props
2024-04-06 15:43:42 +08:00
3396947115 feat: add ballance map sector field in scene
- add ballance map sector info in scene to indicate the maximum sector count of this map.
- this adding will prevent the bug that the exported ballance map do not have successive sector groups. because original implement will not create sector group if no component in corresponding sector and previous remedy still have bug. and if this happended, ballance will show spaceship in wrong sector. this adding is the final solution of this bug.
- exlarge ballance map sector info when user adding component. the enlarged value will be calculated by user input sector.
- auto enlarge ballance map sector info when importing. this will give user a fluent experience when modifying existing map.
- exporting map will also use ballance map sector info to pre-create successive sector group as term 2 stated.
- move sector name extractor from virtools file exporting module to naming convention module.
2024-04-01 14:39:11 +08:00
6cf2ab895d fix: fix unregister error in virtools group
- fix del attribute error when unregister virtools group.
2024-03-30 16:12:34 +08:00
b039dd8b43 fix: fix grouping and naming error
- fix wood and rail soundeffect grouping error.
- fix checkpoint and resetpoint naming error again (forget to change PC/PR).
2024-02-20 21:52:50 +08:00
78 changed files with 2049 additions and 2437 deletions

3
.gitignore vendored
View File

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

12
bbp_ng/.gitignore vendored
View File

@ -1,15 +1,15 @@
# 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
## ===== Python =====
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]

View File

@ -1,4 +1,4 @@
import bpy
import bpy, mathutils
import typing
from . import PROP_preferences
from . import UTIL_functions, UTIL_bme
@ -43,7 +43,7 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
# So these is the solution about generating cache list according to the change of bme struct type.
# First, update function will only set a "outdated" flag for operator which is a pre-registered Blender property.
# The "outdated" flags is not showen and not saved.
# Then call a internal cache list update function at the begin of `invoke` and `draw`.
# Then call a internal cache list update function at the begin of `invoke`, `execute` and `draw`.
# In this internal cache list updator, check "outdated" flag first, if cache is outdated, update and reset flag.
# Otherwise do nothing.
#
@ -141,11 +141,34 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
type = BBP_PG_bme_adder_cfgs,
) # type: ignore
## Extra transform for good "what you see is what you gotten".
# Extra transform will be added after moving this object to cursor.
extra_translation: bpy.props.FloatVectorProperty(
name = "Extra Translation",
description = "The extra translation applied to object after moving to cursor.",
size = 3,
subtype = 'TRANSLATION',
step = 50, # same step as the float entry of BBP_PG_bme_adder_cfgs
default = (0.0, 0.0, 0.0)
) # type: ignore
extra_rotation: bpy.props.FloatVectorProperty(
name = "Extra Rotation",
description = "The extra rotation applied to object after moving to cursor.",
size = 3,
subtype = 'EULER',
step = 100, # We choosen 100, mean 1. Sync with property window.
default = (0.0, 0.0, 0.0)
) # type: ignore
@classmethod
def poll(self, context):
def poll(cls, context):
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
def invoke(self, context, event):
# reset extra transform to identy
self.extra_translation = (0.0, 0.0, 0.0)
self.extra_rotation = (0.0, 0.0, 0.0)
self.extra_scale = (1.0, 1.0, 1.0)
# create internal list
self.bme_struct_cfg_index_cache = []
# trigger default bme struct type updator
@ -156,6 +179,9 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
return self.execute(context)
def execute(self, context):
# call internal updator
self.__internal_update_bme_struct_type()
# collect cfgs data
cfgs: dict[str, typing.Any] = {}
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
@ -178,8 +204,14 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
cfgs
)
# move to cursor
# add into scene and move to cursor
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
# add extra transform
obj.matrix_world = obj.matrix_world @ mathutils.Matrix.LocRotScale(
mathutils.Vector(self.extra_translation),
mathutils.Euler(self.extra_rotation, 'XYZ'),
mathutils.Vector((1.0, 1.0, 1.0)) # no scale
)
# select created object
UTIL_functions.select_certain_objects((obj, ))
return {'FINISHED'}
@ -194,6 +226,7 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
layout.prop(self, 'bme_struct_type')
# visit cfgs cache list to show cfg
layout.label(text = "Prototype Configurations:")
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
# create box for cfgs
box_layout: bpy.types.UILayout = layout.box()
@ -225,12 +258,24 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
grids.prop(self.bme_struct_cfgs[cfg_index + 1], 'prop_bool', text = 'Bottom') # bottom
grids.separator()
# show extra transform props
# forcely order that each one are placed horizontally
layout.label(text = "Extra Transform:")
# translation
layout.label(text = 'Translation')
hbox_layout: bpy.types.UILayout = layout.row()
hbox_layout.prop(self, 'extra_translation', text = '')
# rotation
layout.label(text = 'Rotation')
hbox_layout = layout.row()
hbox_layout.prop(self, 'extra_rotation', text = '')
@classmethod
def draw_blc_menu(self, layout: bpy.types.UILayout):
def draw_blc_menu(cls, layout: bpy.types.UILayout):
for ident in _g_EnumHelper_BmeStructType.get_bme_identifiers():
# draw operator
cop = layout.operator(
self.bl_idname,
cls.bl_idname,
text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident),
icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident)
)

View File

@ -1,7 +1,7 @@
import bpy, mathutils
import math, typing
from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convension
from . import PROP_ballance_element, PROP_virtools_group
from . import PROP_ballance_element, PROP_virtools_group, PROP_ballance_map_info
#region Param Help Classes
@ -128,6 +128,7 @@ class _GeneralComponentCreator():
"""
# get element info first
ele_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector)
# create blc element context
with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator:
# object creation counter
@ -143,6 +144,27 @@ class _GeneralComponentCreator():
# put into created object list
self.__mObjList.append(obj)
# enlarge scene sector field for non-PS (start point) PE (end point) component
# read from scene and create var for enlarged sector count
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
enlarged_sector: int
# check component type to get enlarged value
match(ele_info.mBasicType):
case UTIL_naming_convension.BallanceObjectType.COMPONENT:
enlarged_sector = comp_sector
case UTIL_naming_convension.BallanceObjectType.CHECKPOINT:
# checkpoint 1 means that there is sector 2, so we plus 1 for it.
enlarged_sector = comp_sector + 1
case UTIL_naming_convension.BallanceObjectType.RESETPOINT:
enlarged_sector = comp_sector
case _:
# this component is not a sector based component
# so we do not change it (use original value)
enlarged_sector = map_info.mSectorCount
# enlarge it
map_info.mSectorCount = max(map_info.mSectorCount, enlarged_sector)
PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info)
def finish_component(self) -> None:
"""
Finish up component creation.

View File

@ -33,6 +33,50 @@ c_SideSpiralRailScrew: float = 3.6
#region Operator Helpers
class SharedExtraTransform():
"""
This class is served for all rail creation which allow user
provide extra transform after moving created rail to cursor.
For "what you look is what you gotten" experience, this extra transform is essential.
"""
extra_translation: bpy.props.FloatVectorProperty(
name = "Extra Translation",
description = "The extra translation applied to object after moving to cursor.",
size = 3,
subtype = 'TRANSLATION',
step = 50, # same step as the float entry of BBP_PG_bme_adder_cfgs
default = (0.0, 0.0, 0.0)
) # type: ignore
extra_rotation: bpy.props.FloatVectorProperty(
name = "Extra Rotation",
description = "The extra rotation applied to object after moving to cursor.",
size = 3,
subtype = 'EULER',
step = 100, # We choosen 100, mean 1. Sync with property window.
default = (0.0, 0.0, 0.0)
) # 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:")
# translation
layout.label(text = 'Translation')
row = layout.row()
row.prop(self, 'extra_translation', text = '')
# rotation
layout.label(text = 'Rotation')
row = layout.row()
row.prop(self, 'extra_rotation', text = '')
def general_get_extra_transform(self) -> mathutils.Matrix:
return mathutils.Matrix.LocRotScale(
mathutils.Vector(self.extra_translation),
mathutils.Euler(self.extra_rotation, 'XYZ'),
mathutils.Vector((1.0, 1.0, 1.0)) # no scale
)
class SharedRailSectionInputProperty():
"""
This class is served for user to pick the transition type of rail.
@ -112,7 +156,7 @@ class SharedScrewRailInputProperty():
rail_screw_steps: bpy.props.IntProperty(
name = "Steps",
description = "The segment count per iteration. More segment, more smooth but lower performance.",
default = 16,
default = 28,
min = 1,
) # type: ignore
@ -147,7 +191,8 @@ class BBP_OT_add_rail_section(SharedRailSectionInputProperty, bpy.types.Operator
lambda bm: _create_rail_section(
bm, self.general_get_is_monorail(),
c_DefaultRailRadius, c_DefaultRailSpan
)
),
mathutils.Matrix.Identity(4)
)
return {'FINISHED'}
@ -163,7 +208,8 @@ class BBP_OT_add_transition_section(bpy.types.Operator):
def execute(self, context):
_rail_creator_wrapper(
lambda bm: _create_transition_section(bm, c_DefaultRailRadius, c_DefaultRailSpan)
lambda bm: _create_transition_section(bm, c_DefaultRailRadius, c_DefaultRailSpan),
mathutils.Matrix.Identity(4)
)
return {'FINISHED'}
@ -171,7 +217,7 @@ class BBP_OT_add_transition_section(bpy.types.Operator):
layout = self.layout
layout.label(text = 'No Options Available')
class BBP_OT_add_straight_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
class BBP_OT_add_straight_rail(SharedExtraTransform, SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
"""Add Straight Rail"""
bl_idname = "bbp.add_straight_rail"
bl_label = "Straight Rail"
@ -184,7 +230,8 @@ class BBP_OT_add_straight_rail(SharedRailSectionInputProperty, SharedRailCapInpu
self.general_get_is_monorail(), c_DefaultRailRadius, c_DefaultRailSpan,
self.general_get_rail_length(), 0,
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
)
),
self.general_get_extra_transform()
)
return {'FINISHED'}
@ -196,8 +243,10 @@ class BBP_OT_add_straight_rail(SharedRailSectionInputProperty, SharedRailCapInpu
layout.separator()
layout.label(text = 'Rail Cap')
self.draw_rail_cap_input(layout)
layout.separator()
self.draw_extra_transform_input(layout)
class BBP_OT_add_transition_rail(SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
class BBP_OT_add_transition_rail(SharedExtraTransform, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
"""Add Transition Rail"""
bl_idname = "bbp.add_transition_rail"
bl_label = "Transition Rail"
@ -210,7 +259,8 @@ class BBP_OT_add_transition_rail(SharedRailCapInputProperty, SharedStraightRailI
c_DefaultRailRadius, c_DefaultRailSpan,
self.general_get_rail_length(),
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
)
),
self.general_get_extra_transform()
)
return {'FINISHED'}
@ -221,8 +271,10 @@ class BBP_OT_add_transition_rail(SharedRailCapInputProperty, SharedStraightRailI
layout.separator()
layout.label(text = 'Rail Cap')
self.draw_rail_cap_input(layout)
layout.separator()
self.draw_extra_transform_input(layout)
class BBP_OT_add_side_rail(SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
class BBP_OT_add_side_rail(SharedExtraTransform, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
"""Add Side Rail"""
bl_idname = "bbp.add_side_rail"
bl_label = "Side Rail"
@ -246,7 +298,8 @@ class BBP_OT_add_side_rail(SharedRailCapInputProperty, SharedStraightRailInputPr
self.general_get_rail_length(),
c_NormalSideRailAngle if self.side_rail_type == 'NORMAL' else c_StoneSideRailAngle,
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
)
),
self.general_get_extra_transform()
)
return {'FINISHED'}
@ -258,8 +311,10 @@ class BBP_OT_add_side_rail(SharedRailCapInputProperty, SharedStraightRailInputPr
layout.separator()
layout.label(text = 'Rail Cap')
self.draw_rail_cap_input(layout)
layout.separator()
self.draw_extra_transform_input(layout)
class BBP_OT_add_arc_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
class BBP_OT_add_arc_rail(SharedExtraTransform, SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
"""Add Arc Rail"""
bl_idname = "bbp.add_arc_rail"
bl_label = "Arc Rail"
@ -281,7 +336,8 @@ class BBP_OT_add_arc_rail(SharedRailSectionInputProperty, SharedRailCapInputProp
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_extra_transform()
)
return {'FINISHED'}
@ -294,8 +350,10 @@ class BBP_OT_add_arc_rail(SharedRailSectionInputProperty, SharedRailCapInputProp
layout.separator()
layout.label(text = 'Rail Cap')
self.draw_rail_cap_input(layout)
layout.separator()
self.draw_extra_transform_input(layout)
class BBP_OT_add_spiral_rail(SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
class BBP_OT_add_spiral_rail(SharedExtraTransform, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
"""Add Spiral Rail"""
bl_idname = "bbp.add_spiral_rail"
bl_label = "Spiral Rail"
@ -323,7 +381,8 @@ class BBP_OT_add_spiral_rail(SharedRailCapInputProperty, SharedScrewRailInputPro
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_extra_transform()
)
return {'FINISHED'}
@ -336,8 +395,10 @@ class BBP_OT_add_spiral_rail(SharedRailCapInputProperty, SharedScrewRailInputPro
layout.separator()
layout.label(text = 'Rail Cap')
self.draw_rail_cap_input(layout)
layout.separator()
self.draw_extra_transform_input(layout)
class BBP_OT_add_side_spiral_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
class BBP_OT_add_side_spiral_rail(SharedExtraTransform, SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
"""Add Side Spiral Rail"""
bl_idname = "bbp.add_side_spiral_rail"
bl_label = "Side Spiral Rail"
@ -360,7 +421,8 @@ class BBP_OT_add_side_spiral_rail(SharedRailSectionInputProperty, SharedRailCapI
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_extra_transform()
)
return {'FINISHED'}
@ -372,6 +434,8 @@ class BBP_OT_add_side_spiral_rail(SharedRailSectionInputProperty, SharedRailCapI
layout.separator()
layout.label(text = 'Rail Cap')
self.draw_rail_cap_input(layout)
layout.separator()
self.draw_extra_transform_input(layout)
#endregion
@ -460,7 +524,7 @@ def _bmesh_cap(bm: bmesh.types.BMesh, edges: list[bmesh.types.BMEdge]) -> None:
#region Real Rail Creators
def _rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], None]) -> bpy.types.Object:
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()
@ -473,8 +537,6 @@ def _rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], No
bm.free()
# setup smooth for mesh
mesh.use_auto_smooth = True
mesh.auto_smooth_angle = math.radians(50)
mesh.shade_smooth()
# create object and assoc with it
@ -492,6 +554,8 @@ def _rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], No
# 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, ))

View File

@ -8,7 +8,7 @@ class BBP_OT_export_bmfile(bpy.types.Operator, UTIL_file_browser.ExportBmxFile,
bl_options = {'PRESET'}
@classmethod
def poll(self, context):
def poll(cls, context):
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
def execute(self, context):

View File

@ -1,41 +1,19 @@
import bpy
from bpy_extras.wm_utils.progress_report import ProgressReport
import tempfile, os, typing, re
import tempfile, os, typing
from . import PROP_preferences, UTIL_ioport_shared
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_icons_manager
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_icons_manager, UTIL_naming_convension
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_virtools_light
from .PyBMap import bmap_wrapper as bmap
# define global tex save opt blender enum prop helper
_g_EnumHelper_CK_TEXTURE_SAVEOPTIONS: UTIL_virtools_types.EnumPropHelper = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS)
class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtoolsFile, UTIL_ioport_shared.ExportParams, UTIL_ioport_shared.VirtoolsParams):
class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtoolsFile, UTIL_ioport_shared.ExportParams, UTIL_ioport_shared.VirtoolsParams, UTIL_ioport_shared.BallanceParams):
"""Export Virtools File"""
bl_idname = "bbp.export_virtools"
bl_label = "Export Virtools File"
bl_options = {'PRESET'}
texture_save_opt: bpy.props.EnumProperty(
name = "Global Texture Save Options",
description = "Decide how texture saved if texture is specified as Use Global as its Save Options.",
items = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.generate_items(),
default = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.to_selection(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL)
)
use_compress: bpy.props.BoolProperty(
name="Use Compress",
default = True,
)
compress_level: bpy.props.IntProperty(
name = "Compress Level",
description = "The ZLib compress level used by Virtools Engine when saving composition.",
min = 1, max = 9,
default = 5,
)
@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())
@ -51,14 +29,26 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
)
return {'CANCELLED'}
# check texture save option to prevent real stupid user.
texture_save_opt = self.general_get_texture_save_opt()
if texture_save_opt == UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_USEGLOBAL:
UTIL_functions.message_box(
('You can not specify "Use Global" as global texture save option!', ),
'Wrong Parameters',
UTIL_icons_manager.BlenderPresetIcons.Error.value
)
return {'CANCELLED'}
# start exporting
with UTIL_ioport_shared.ExportEditModeBackup() as editmode_guard:
_export_virtools(
self.general_get_filename(),
self.general_get_vt_encodings(),
_g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.get_selection(self.texture_save_opt),
self.use_compress,
self.compress_level,
texture_save_opt,
self.general_get_use_compress(),
self.general_get_compress_level(),
self.general_get_successive_sector(),
self.general_get_successive_sector_count(),
objls
)
@ -67,24 +57,12 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
def draw(self, context):
layout = self.layout
layout.label(text = 'Export Target')
self.draw_export_params(layout.box())
layout.separator()
layout.label(text = 'Virtools Params')
box = layout.box()
self.draw_virtools_params(box)
box.separator()
box.label(text = 'Global Texture Save Option')
box.prop(self, 'texture_save_opt', text = '')
box.separator()
box.prop(self, 'use_compress')
if self.use_compress:
box.prop(self, 'compress_level')
self.draw_export_params(layout)
self.draw_virtools_params(layout, False)
self.draw_ballance_params(layout, False)
_TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject]
_TLightPair = tuple[bpy.types.Object, bpy.types.Light, bmap.BMTargetLight]
_TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh]
_TMaterialPair = tuple[bpy.types.Material, bmap.BMMaterial]
_TTexturePair = tuple[bpy.types.Image, bmap.BMTexture]
@ -95,6 +73,8 @@ def _export_virtools(
texture_save_opt_: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS,
use_compress_: bool,
compress_level_: int,
successive_sector_: bool,
successive_sector_count_: int,
export_objects: tuple[bpy.types.Object, ...]
) -> None:
@ -110,11 +90,15 @@ def _export_virtools(
# prepare progress reporter
with ProgressReport(wm = bpy.context.window_manager) as progress:
# prepare 3dobject
obj3d_crets: tuple[_TObj3dPair, ...] = _prepare_virtools_3dobjects(
writer, progress, export_objects)
# export group and 3dobject by prepared 3dobject
_export_virtools_groups(writer, progress, obj3d_crets)
# prepare 3dobject and light
obj3d_crets: tuple[_TObj3dPair, ...]
light_crets: tuple[_TLightPair, ...]
(obj3d_crets, light_crets) = _prepare_virtools_3dobjects(writer, progress, export_objects)
# export group according to prepared 3dobject
_export_virtools_groups(writer, progress, successive_sector_, successive_sector_count_, obj3d_crets)
# export prepared light
_export_virtools_light(writer, progress, light_crets)
# export prepared 3dobject
mesh_crets: tuple[_TMeshPair, ...] = _export_virtools_3dobjects(
writer, progress, obj3d_crets)
# export mesh
@ -133,36 +117,59 @@ 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")
progress.enter_substeps(len(export_objects), "Creating 3dObjects and Lights")
# 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
@ -170,8 +177,22 @@ def _export_virtools_groups(
# start saving
progress.enter_substeps(len(obj3d_crets), "Saving Groups")
# create group exporting helper
group_cret_guard: VirtoolsGroupCreationGuard = VirtoolsGroupCreationGuard(writer)
# create sector group first if user ordered
# This step is designed for ensure that the created sector group is successive.
#
# Due to the design of Ballance, Ballance rely on checking the existance of Sector_XX to get how many sectors this map have.
# Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash
# or be ended at a accident sector.
#
# So we create all needed sector group in here to make sure exported virtools file can be read by Ballancde correctly.
if successive_sector:
for i in range(successive_sector_count):
gp_name: str = UTIL_naming_convension.build_name_from_sector_index(i + 1)
vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None)
if vtgroup is None:
vtgroup = writer.create_group()
vtgroup.set_name(gp_name)
group_cret_map[gp_name] = vtgroup
for obj3d, vtobj3d in obj3d_crets:
# open group visitor
@ -180,8 +201,8 @@ def _export_virtools_groups(
# get group or create new group from guard
vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None)
if vtgroup is None:
# note: no need to set name, guard has set it.
vtgroup = group_cret_guard.create_group(gp_name)
vtgroup = writer.create_group()
vtgroup.set_name(gp_name)
group_cret_map[gp_name] = vtgroup
# group this object
@ -193,6 +214,50 @@ 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
progress.enter_substeps(0, "Saving Lights")
for obj3d, light, vtlight in light_crets:
# set name
vtlight.set_name(obj3d.name)
# setup 3d entity parts
# set world matrix
# TODO: fix light direction matrix issue.
vtmat: UTIL_virtools_types.VxMatrix = UTIL_virtools_types.VxMatrix()
UTIL_virtools_types.vxmatrix_from_blender(vtmat, obj3d.matrix_world)
UTIL_virtools_types.vxmatrix_conv_co(vtmat)
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,
@ -209,7 +274,7 @@ def _export_virtools_3dobjects(
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)
@ -340,7 +405,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
@ -471,73 +536,6 @@ def _save_virtools_document(
progress.step()
progress.leave_substeps()
class VirtoolsGroupCreationGuard():
"""
This class is designed for ensure that the created sector group is successive.
Due to the design of Ballance, Ballance rely on checking the existance of Sector_XX to get how many sectors this map have.
Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash
or be ended at a accident sector.
This class hook the operation of Virtools group creation and check all Sector group creation.
Create essential group to make Sector_XX group successive.
Thus all group creation in this module should be passed by this class.
"""
cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
@staticmethod
def __get_group_index(group_name: str) -> int | None:
"""
Return the sector index of group name if it is. Otherwise None.
"""
regex_result = VirtoolsGroupCreationGuard.cRegexGroupSector.match(group_name)
if regex_result is not None:
return int(regex_result.group(1))
else:
return None
@staticmethod
def __get_group_name(group_index: int) -> str:
"""
Output Sector group name by given sector index
"""
if group_index == 9:
return 'Sector_9'
else:
return f'Sector_{group_index:0>2d}'
__mWriter: bmap.BMFileWriter
__mSectors: list[bmap.BMGroup]
def __init__(self, assoc_writer: bmap.BMFileWriter):
self.__mWriter = assoc_writer
self.__mSectors = []
def create_group(self, group_name: str) -> bmap.BMGroup:
"""
The hooked group creation function.
Please note the passed group name argument is just for name checking.
This function will set group name for you, not like BMFileWriter.create_group() operated.
"""
# check whether it is sector group
# note: the return sector index is 1 based, not 0
sector_idx: int | None = VirtoolsGroupCreationGuard.__get_group_index(group_name)
# if it is regular group, return normal creation
if sector_idx is None:
gp: bmap.BMGroup = self.__mWriter.create_group()
gp.set_name(group_name)
return gp
# get from sector cahce list
# enlarge sector cache list if it is not fulfilled given sector index
while sector_idx > len(self.__mSectors):
gp: bmap.BMGroup = self.__mWriter.create_group()
self.__mSectors.append(gp)
gp.set_name(self.__get_group_name(len(self.__mSectors)))
# return ordered sector from sector caches
return self.__mSectors[sector_idx - 1]
def register() -> None:
bpy.utils.register_class(BBP_OT_export_virtools)

View File

@ -8,7 +8,7 @@ class BBP_OT_import_bmfile(bpy.types.Operator, UTIL_file_browser.ImportBmxFile,
bl_options = {'PRESET', 'UNDO'}
@classmethod
def poll(self, context):
def poll(cls, context):
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
def execute(self, context):

View File

@ -2,18 +2,18 @@ import bpy
from bpy_extras.wm_utils.progress_report import ProgressReport
import tempfile, os, typing
from . import PROP_preferences, UTIL_ioport_shared
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_naming_convension
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_virtools_light, PROP_ballance_map_info
from .PyBMap import bmap_wrapper as bmap
class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams):
class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams, UTIL_ioport_shared.BallanceParams):
"""Import Virtools File"""
bl_idname = "bbp.import_virtools"
bl_label = "Import Virtools File"
bl_options = {'PRESET', 'UNDO'}
@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())
@ -29,11 +29,9 @@ 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(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
@ -61,6 +59,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)
@ -202,7 +202,7 @@ 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")
progress.enter_substeps(reader.get_mesh_count(), "Loading Meshes")
for vtmesh in reader.get_meshs():
# create mesh
@ -298,11 +298,7 @@ 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
progress.enter_substeps(reader.get_3dobject_count(), "Loading 3dObjects")
for vt3dobj in reader.get_3dobjects():
# get virtools binding mesh data first
@ -316,8 +312,8 @@ def _import_virtools_3dobjects(
# setup if necessary
if init_obj3d:
# link to collection
blender_collection.objects.link(obj3d)
# add into scene
UTIL_functions.add_into_scene(obj3d)
# set world matrix
vtmat: UTIL_virtools_types.VxMatrix = vt3dobj.get_world_matrix()
@ -340,26 +336,89 @@ 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
progress.enter_substeps(reader.get_target_light_count(), "Loading Lights")
# 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
# TODO: fix light direction
vtmat: UTIL_virtools_types.VxMatrix = vtlight.get_world_matrix()
UTIL_virtools_types.vxmatrix_conv_co(vtmat)
light_3dobj.matrix_world = UTIL_virtools_types.vxmatrix_to_blender(vtmat)
# set visibility
light_3dobj.hide_set(not vtlight.get_visibility())
# leave progress
progress.leave_substeps()
def _import_virtools_groups(
reader: bmap.BMFileReader,
progress: ProgressReport,
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object]
) -> dict[bmap.BM3dObject, bpy.types.Object]:
) -> None:
# we need iterate all groups to construct a reversed map
# to indicate which groups should this 3dobject be grouped into.
reverse_map: dict[bmap.BM3dObject, set[str]] = {}
# sector counter to record the maximum sector we have processed.
sector_count: int = 1
# prepare progress
progress.enter_substeps(reader.get_material_count(), "Loading Groups")
progress.enter_substeps(reader.get_group_count(), "Loading Groups")
for vtgroup in reader.get_groups():
# if this group do not have name, skip it
group_name: str | None = vtgroup.get_name()
if group_name is None: continue
# try extracting sector info
potential_sector_count: int | None = UTIL_naming_convension.extract_sector_from_name(group_name)
if potential_sector_count is not None:
sector_count = max(sector_count, potential_sector_count)
# creating map
for item in vtgroup.get_objects():
# get or create set
objgroups: set[str] = reverse_map.get(item, None)
objgroups: set[str] | None = reverse_map.get(item, None)
if objgroups is None:
objgroups = set()
reverse_map[item] = objgroups
@ -370,11 +429,16 @@ def _import_virtools_groups(
# step
progress.step()
# assign to ballance map info according to gotten sector count
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
map_info.mSectorCount = max(map_info.mSectorCount, sector_count)
PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info)
# leave progress
progress.leave_substeps()
# now we can assign 3dobject group data by reverse map
progress.enter_substeps(reader.get_material_count(), "Applying Groups")
progress.enter_substeps(len(reverse_map), "Applying Groups")
for mapk, mapv in reverse_map.items():
# check object
assoc_obj = obj3d_cret_map.get(mapk, None)
@ -385,6 +449,10 @@ def _import_virtools_groups(
gpoper.clear_groups()
gpoper.add_groups(mapv)
# step
progress.step()
# leave progress
progress.leave_substeps()

View File

@ -1,4 +1,5 @@
import bpy
from . import UTIL_functions
from . import PROP_virtools_material, PROP_preferences
class BBP_OT_fix_all_material(bpy.types.Operator):
@ -9,7 +10,9 @@ class BBP_OT_fix_all_material(bpy.types.Operator):
@classmethod
def poll(cls, context):
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
# only enable this when plugin have a valid ballance texture folder
# and we are in object mode
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder() and UTIL_functions.is_in_object_mode()
def invoke(self, context, event):
wm = context.window_manager

View File

@ -43,25 +43,25 @@ class BBP_PG_legacy_align_history(bpy.types.PropertyGroup):
align_x: bpy.props.BoolProperty(
name = "X Position",
default = False,
)
) # type: ignore
align_y: bpy.props.BoolProperty(
name = "Y Position",
default = False,
)
) # type: ignore
align_z: bpy.props.BoolProperty(
name = "Z Position",
default = False,
)
) # 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),
)
) # type: ignore
target_align_mode: bpy.props.EnumProperty(
name = "Target Objects (Other Objects)",
items = _g_EnumHelper_AlignMode.generate_items(),
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
)
) # type: ignore
#endregion
@ -109,21 +109,21 @@ class BBP_OT_legacy_align(bpy.types.Operator):
options = {'HIDDEN', 'SKIP_SAVE'},
default = True, # default True value to make it as a "light" button, not a grey one.
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.",
options = {'HIDDEN', 'SKIP_SAVE'},
default = False,
)
) # type: ignore
align_history : bpy.props.CollectionProperty(
name = "Historys",
description = "Align history.",
type = BBP_PG_legacy_align_history,
)
) # type: ignore
@classmethod
def poll(self, context):
def poll(cls, context):
return _check_align_requirement()
def invoke(self, context, event):
@ -136,6 +136,14 @@ class BBP_OT_legacy_align(bpy.types.Operator):
def execute(self, context):
# get processed objects
(current_obj, target_objs) = _prepare_objects()
# INFO: YYC MARK:
# This statement is VERY IMPORTANT.
# If this statement is not presented, Blender will return identity matrix
# when getting world matrix from Object since the second execution of this function.
# It seems that Blender fail to read restored value from a new execution.
# Additionally, this statement only can be placed in there.
# If you place it at the end of this function, it doesn't work.
context.view_layer.update()
# iterate history to align objects
entry: BBP_PG_legacy_align_history
for entry in self.align_history:
@ -180,10 +188,13 @@ class BBP_OT_legacy_align(bpy.types.Operator):
#region Core Functions
def _check_align_requirement() -> bool:
# if we are not in object mode, do not do legacy align
if not UTIL_functions.is_in_object_mode():
return False
# check current obj
if bpy.context.active_object is None:
return False
# check target obj with filter of current obj
length = len(bpy.context.selected_objects)
if bpy.context.active_object in bpy.context.selected_objects:
@ -209,56 +220,52 @@ def _align_objects(
# if no align, skip
if not (align_x or align_y or align_z):
return
# calc current object data
current_obj_bbox: tuple[mathutils.Vector] = tuple(current_obj.matrix_world @ mathutils.Vector(corner) for corner in current_obj.bound_box)
current_obj_ref: mathutils.Vector = _get_object_ref_point(current_obj, current_obj_bbox, current_mode)
current_obj_ref: mathutils.Vector = _get_object_ref_point(current_obj, current_mode)
# process each target obj
for target_obj in target_objs:
# calc target object data
target_obj_bbox: tuple[mathutils.Vector] = tuple(target_obj.matrix_world @ mathutils.Vector(corner) for corner in target_obj.bound_box)
target_obj_ref: mathutils.Vector = _get_object_ref_point(target_obj, target_obj_bbox, target_mode)
# do align
if align_x:
target_obj.location.x += current_obj_ref.x - target_obj_ref.x
if align_y:
target_obj.location.y += current_obj_ref.y - target_obj_ref.y
if align_z:
target_obj.location.z += current_obj_ref.z - target_obj_ref.z
target_obj_ref: mathutils.Vector = _get_object_ref_point(target_obj, target_mode)
# build translation transform
target_obj_translation: mathutils.Vector = current_obj_ref - target_obj_ref
if not align_x: target_obj_translation.x = 0
if not align_y: target_obj_translation.y = 0
if not align_z: target_obj_translation.z = 0
# target_obj.location += target_obj_translation
target_obj_translation_matrix: mathutils.Matrix = mathutils.Matrix.Translation(target_obj_translation)
# apply translation transform to left side (add into original matrix)
target_obj.matrix_world = target_obj_translation_matrix @ target_obj.matrix_world
def _get_object_ref_point(obj: bpy.types.Object, corners: tuple[mathutils.Vector], mode: AlignMode) -> mathutils.Vector:
bpy.context.scene.update_tag
def _get_object_ref_point(obj: bpy.types.Object, mode: AlignMode) -> mathutils.Vector:
ref_pos: mathutils.Vector = mathutils.Vector((0, 0, 0))
# calc bounding box data
corners: tuple[mathutils.Vector] = tuple(obj.matrix_world @ mathutils.Vector(corner) for corner in obj.bound_box)
bbox_min_corner: mathutils.Vector = mathutils.Vector((0, 0, 0))
bbox_min_corner.x = min((vec.x for vec in corners))
bbox_min_corner.y = min((vec.y for vec in corners))
bbox_min_corner.z = min((vec.z for vec in corners))
bbox_max_corner: mathutils.Vector = mathutils.Vector((0, 0, 0))
bbox_max_corner.x = max((vec.x for vec in corners))
bbox_max_corner.y = max((vec.y for vec in corners))
bbox_max_corner.z = max((vec.z for vec in corners))
# return value by given align mode
match(mode):
case AlignMode.Min:
ref_pos.x = min((vec.x for vec in corners))
ref_pos.y = min((vec.y for vec in corners))
ref_pos.z = min((vec.z for vec in corners))
ref_pos = bbox_min_corner
case AlignMode.Max:
ref_pos.x = max((vec.x for vec in corners))
ref_pos.y = max((vec.y for vec in corners))
ref_pos.z = max((vec.z for vec in corners))
ref_pos = bbox_max_corner
case AlignMode.BBoxCenter:
max_vec_cache: mathutils.Vector = mathutils.Vector((0, 0, 0))
min_vec_cache: mathutils.Vector = mathutils.Vector((0, 0, 0))
min_vec_cache.x = min((vec.x for vec in corners))
min_vec_cache.y = min((vec.y for vec in corners))
min_vec_cache.z = min((vec.z for vec in corners))
max_vec_cache.x = max((vec.x for vec in corners))
max_vec_cache.y = max((vec.y for vec in corners))
max_vec_cache.z = max((vec.z for vec in corners))
ref_pos.x = (max_vec_cache.x + min_vec_cache.x) / 2
ref_pos.y = (max_vec_cache.y + min_vec_cache.y) / 2
ref_pos.z = (max_vec_cache.z + min_vec_cache.z) / 2
ref_pos = (bbox_max_corner + bbox_min_corner) / 2
case AlignMode.AxisCenter:
ref_pos.x = obj.location.x
ref_pos.y = obj.location.y
ref_pos.z = obj.location.z
ref_pos = obj.matrix_world.translation
case _:
raise UTIL_functions.BBPException('inpossible align mode.')
raise UTIL_functions.BBPException('impossible align mode.')
return ref_pos

View File

@ -38,7 +38,11 @@ class BBP_OT_select_object_by_virtools_group(bpy.types.Operator, PROP_virtools_g
description = "Selection mode",
items = _g_EnumHelper_SelectMode.generate_items(),
default = _g_EnumHelper_SelectMode.to_selection(SelectMode.Intersect)
)
) # type: ignore
@classmethod
def poll(cls, context):
return UTIL_functions.is_in_object_mode()
def invoke(self, context, event):
wm = context.window_manager
@ -46,6 +50,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)
)
@ -61,17 +66,17 @@ class BBP_OT_select_object_by_virtools_group(bpy.types.Operator, PROP_virtools_g
layout.label(text='Group Parameters')
self.draw_group_name_input(layout)
def _select_object_by_virtools_group(group_name: str, mode: SelectMode) -> None:
def _select_object_by_virtools_group(context: bpy.types.Context, group_name: str, mode: SelectMode) -> None:
match(mode):
case SelectMode.Set:
# iterate all objects and directly set
for obj in bpy.context.scene.objects:
for obj in context.scene.objects:
# check group and decide whether select this obj
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
obj.select_set(gp.contain_group(group_name))
case SelectMode.Extend:
# also iterate all objects
for obj in bpy.context.scene.objects:
for obj in context.scene.objects:
# but only increase selection, for selected object, skip check
if obj.select_get(): continue
# if not selected, check whether add it.
@ -82,7 +87,7 @@ def _select_object_by_virtools_group(group_name: str, mode: SelectMode) -> None:
# subtract only involving selected item. so we get selected objest first
# and copy it (because we need modify it)
# and iterate it to reduce useless operations
selected = bpy.context.selected_objects[:]
selected = context.selected_objects[:]
for obj in selected:
# remove matched only
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
@ -90,16 +95,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:
@ -119,8 +124,8 @@ class BBP_OT_add_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.
bl_options = {'UNDO'}
@classmethod
def poll(self, context):
return len(bpy.context.selected_objects) != 0
def poll(cls, context):
return len(context.selected_objects) != 0
def invoke(self, context, event):
wm = context.window_manager
@ -128,9 +133,10 @@ class BBP_OT_add_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.
def execute(self, context):
group_name: str = self.general_get_group_name()
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
gp.add_group(group_name)
self.report({'INFO'}, "Grouping objects successfully.")
return {'FINISHED'}
def draw(self, context):
@ -143,8 +149,8 @@ class BBP_OT_rm_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.S
bl_options = {'UNDO'}
@classmethod
def poll(self, context):
return len(bpy.context.selected_objects) != 0
def poll(cls, context):
return len(context.selected_objects) != 0
def invoke(self, context, event):
wm = context.window_manager
@ -152,9 +158,10 @@ class BBP_OT_rm_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.S
def execute(self, context):
group_name: str = self.general_get_group_name()
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
gp.remove_group(group_name)
self.report({'INFO'}, "Ungrouping objects successfully.")
return {'FINISHED'}
def draw(self, context):
@ -167,8 +174,8 @@ class BBP_OT_clear_objects_virtools_group(bpy.types.Operator):
bl_options = {'UNDO'}
@classmethod
def poll(self, context):
return len(bpy.context.selected_objects) != 0
def poll(cls, context):
return len(context.selected_objects) != 0
def invoke(self, context, event):
wm = context.window_manager
@ -176,9 +183,10 @@ class BBP_OT_clear_objects_virtools_group(bpy.types.Operator):
def execute(self, context):
# iterate object
for obj in bpy.context.selected_objects:
for obj in context.selected_objects:
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
gp.clear_groups()
self.report({'INFO'}, "Clear objects groups successfully.")
return {'FINISHED'}
#endregion

View File

@ -135,7 +135,7 @@ class BBP_OT_flatten_uv(bpy.types.Operator):
@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,7 +162,7 @@ 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.')
return {'FINISHED'}

View File

@ -10,7 +10,7 @@ class BBP_OT_rail_uv(bpy.types.Operator):
bl_options = {'UNDO'}
@classmethod
def poll(self, context):
def poll(cls, context):
return _check_rail_target()
def invoke(self, context, event):

View File

@ -97,12 +97,12 @@ class BBP_PG_ballance_element(bpy.types.PropertyGroup):
element_id: bpy.props.IntProperty(
name = "Element Id",
default = 0
)
) # type: ignore
mesh_ptr: bpy.props.PointerProperty(
name = "Mesh",
type = bpy.types.Mesh
)
) # type: ignore
def get_ballance_elements(scene: bpy.types.Scene) -> bpy.types.CollectionProperty:
return scene.ballance_elements

View File

@ -0,0 +1,81 @@
import bpy
import typing
from . import UTIL_functions
class RawBallanceMapInfo():
cSectorCount: typing.ClassVar[int] = 1
mSectorCount: int
def __init__(self, **kwargs):
self.mSectorCount = kwargs.get("mSectorCount", RawBallanceMapInfo.cSectorCount)
def regulate(self):
self.mSectorCount = UTIL_functions.clamp_int(self.mSectorCount, 1, 999)
#region Prop Decl & Getter Setter
class BBP_PG_ballance_map_info(bpy.types.PropertyGroup):
sector_count: bpy.props.IntProperty(
name = "Sector",
description = "The sector count of this Ballance map which is used in exporting map and may be changed when importing map.",
default = 1,
max = 999, min = 1,
soft_max = 8, soft_min = 1,
step = 1
) # type: ignore
def get_ballance_map_info(scene: bpy.types.Scene) -> BBP_PG_ballance_map_info:
return scene.ballance_map_info
def get_raw_ballance_map_info(scene: bpy.types.Scene) -> RawBallanceMapInfo:
props: BBP_PG_ballance_map_info = get_ballance_map_info(scene)
rawdata: RawBallanceMapInfo = RawBallanceMapInfo()
rawdata.mSectorCount = props.sector_count
rawdata.regulate()
return rawdata
def set_raw_ballance_map_info(scene: bpy.types.Scene, rawdata: RawBallanceMapInfo) -> None:
props: BBP_PG_ballance_map_info = get_ballance_map_info(scene)
props.sector_count = rawdata.mSectorCount
#endregion
class BBP_PT_ballance_map_info(bpy.types.Panel):
"""Show Ballance Map Infos."""
bl_label = "Ballance Map"
bl_idname = "BBP_PT_ballance_map_info"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "scene"
@classmethod
def poll(cls, context):
return context.scene is not None
def draw(self, context):
layout: bpy.types.UILayout = self.layout
target: bpy.types.Scene = context.scene
props: BBP_PG_ballance_map_info = get_ballance_map_info(target)
# show map sector count numberbox
layout.prop(props, 'sector_count')
def register() -> None:
# register
bpy.utils.register_class(BBP_PG_ballance_map_info)
bpy.utils.register_class(BBP_PT_ballance_map_info)
# add into scene metadata
bpy.types.Scene.ballance_map_info = bpy.props.PointerProperty(type = BBP_PG_ballance_map_info)
def unregister() -> None:
# del from scene metadata
del bpy.types.Scene.ballance_map_info
# unregister
bpy.utils.unregister_class(BBP_PG_ballance_map_info)
bpy.utils.unregister_class(BBP_PT_ballance_map_info)

View File

@ -73,12 +73,12 @@ class BBP_PG_bme_material(bpy.types.PropertyGroup):
bme_material_name: bpy.props.StringProperty(
name = "Name",
default = ""
)
) # type: ignore
material_ptr: bpy.props.PointerProperty(
name = "Material",
type = bpy.types.Material
)
) # type: ignore
def get_bme_materials(scene: bpy.types.Scene) -> bpy.types.CollectionProperty:
return scene.bme_materials

View File

@ -24,13 +24,13 @@ class BBPPreferences(bpy.types.AddonPreferences):
description = "The path to folder which will be used by this plugin to get external Ballance texture.",
subtype='DIR_PATH',
default = RawPreferences.cBallanceTextureFolder,
)
) # 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",
default = RawPreferences.cNoComponentCollection,
)
) # type: ignore
def draw(self, context):
layout = self.layout

View File

@ -10,19 +10,19 @@ class BBP_PG_ptrprop_resolver(bpy.types.PropertyGroup):
name = "Material",
description = "The material used for rail",
type = bpy.types.Material,
)
) # type: ignore
export_collection: bpy.props.PointerProperty(
type = bpy.types.Collection,
name = "Collection",
description = "The collection exported. Nested collections allowed."
)
) # type: ignore
export_object: bpy.props.PointerProperty(
type = bpy.types.Object,
name = "Object",
description = "The object exported"
)
) # type: ignore
def get_ptrprop_resolver() -> BBP_PG_ptrprop_resolver:
return bpy.context.scene.bbp_ptrprop_resolver

View File

@ -8,7 +8,7 @@ class BBP_PG_virtools_group(bpy.types.PropertyGroup):
group_name: bpy.props.StringProperty(
name = "Group Name",
default = ""
)
) # type: ignore
def get_virtools_groups(obj: bpy.types.Object) -> bpy.types.CollectionProperty:
return obj.virtools_groups
@ -255,19 +255,19 @@ class SharedGroupNameInputProperties():
('DEFINED', "Predefined", "Pre-defined group name."),
('CUSTOM', "Custom", "User specified group name."),
),
)
) # type: ignore
preset_group_name: bpy.props.EnumProperty(
name = "Group Name",
description = "Pick vanilla Ballance group name.",
items = _g_EnumHelper_Group.generate_items(),
)
) # type: ignore
custom_group_name: bpy.props.StringProperty(
name = "Custom Group Name",
description = "Input your custom group name.",
default = "",
)
) # type: ignore
def draw_group_name_input(self, layout: bpy.types.UILayout) -> None:
layout.prop(self, 'group_name_source', expand = True)
@ -297,7 +297,7 @@ class BBP_OT_add_virtools_group(bpy.types.Operator, SharedGroupNameInputProperti
bl_options = {'UNDO'}
@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):
@ -324,7 +324,7 @@ class BBP_OT_rm_virtools_group(bpy.types.Operator):
# 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
@ -351,7 +351,7 @@ class BBP_OT_clear_virtools_groups(bpy.types.Operator):
bl_options = {'UNDO'}
@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):
@ -405,14 +405,14 @@ def register() -> None:
bpy.utils.register_class(BBP_OT_clear_virtools_groups)
bpy.utils.register_class(BBP_PT_virtools_groups)
# add into scene metadata
# add into object metadata
bpy.types.Object.virtools_groups = bpy.props.CollectionProperty(type = BBP_PG_virtools_group)
bpy.types.Object.active_virtools_groups = bpy.props.IntProperty()
def unregister() -> None:
# del from scene metadata
del bpy.types.Scene.active_virtools_groups
del bpy.types.Scene.virtools_groups
# del from object metadata
del bpy.types.Object.active_virtools_groups
del bpy.types.Object.virtools_groups
bpy.utils.unregister_class(BBP_PT_virtools_groups)
bpy.utils.unregister_class(BBP_OT_clear_virtools_groups)

View File

@ -0,0 +1,321 @@
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)
) # 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()
) # 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
) # 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
) # 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
) # 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
) # 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
) # 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
) # 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
) # 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'}
@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.
@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')
# draw data
layout.separator()
layout.label(text = 'Basics')
# 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')
layout.prop(props, 'constant_attenuation', text = 'Constant')
layout.prop(props, 'linear_attenuation', text = 'Linear')
layout.prop(props, 'quadratic_attenuation', text = 'Quadratic')
# only spot light has spot cone properties.
if rawdata.mType == UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTSPOT:
layout.separator()
layout.label(text = 'Spot Cone')
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()
@ -397,13 +397,20 @@ def apply_to_blender_material(mtl: bpy.types.Material):
# bnode.inputs["Emission"].default_value = rawdata.mEmissive.to_const_rgba()
mtl.specular_intensity = rawdata.mSpecularPower
bnode.inputs["Specular"].default_value = UTIL_functions.clamp_float(
bnode.inputs["Specular IOR Level"].default_value = UTIL_functions.clamp_float(
rawdata.mSpecularPower, 0.0, 1.0
)
# set some alpha data
mtl.use_backface_culling = not rawdata.mEnableTwoSided
mtl.blend_method = 'BLEND' if rawdata.mEnableAlphaBlend else 'OPAQUE'
if rawdata.mEnableAlphaBlend:
# In old format: mtl.blend_method = 'BLEND'
mtl.surface_render_method = 'BLENDED'
mtl.use_raytrace_refraction = True
else:
# In old format: mtl.blend_method = 'OPAQUE'
mtl.surface_render_method = 'DITHERED'
mtl.use_raytrace_refraction = False
# set texture
if rawdata.mTexture is not None:
@ -1010,7 +1017,9 @@ 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: 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()
@ -1039,7 +1048,7 @@ class BBP_PT_virtools_material(bpy.types.Panel):
sublay.prop(props, 'texture', emboss = True)
sublay.operator(BBP_OT_direct_set_virtools_texture.bl_idname, text = '', icon = 'FILEBROWSER')
# texture detail
if props.texture is not None:
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")
@ -1050,27 +1059,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.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.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.prop(props, 'enable_z_write')
if props.enable_z_write:
if rawdata.mEnableZWrite:
layout.prop(props, 'z_func')
def register() -> None:

View File

@ -25,7 +25,7 @@ class BBP_PG_virtools_mesh(bpy.types.PropertyGroup):
description = "Lighting mode of the mesh.",
items = _g_Helper_VXMESH_LITMODE.generate_items(),
default = _g_Helper_VXMESH_LITMODE.to_selection(RawVirtoolsMesh.cDefaultLitMode)
)
) # type: ignore
# Getter Setter

View File

@ -30,14 +30,14 @@ class BBP_PG_virtools_texture(bpy.types.PropertyGroup):
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)
)
) # 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)
)
) # 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,785 +0,0 @@
import ctypes, os, sys, typing
#region Type Defines
class BMapException(Exception):
"""
The exception thrown by BMap bindings.
"""
pass
bm_CKSTRING = ctypes.c_char_p
bm_CKSTRING_p = ctypes.POINTER(bm_CKSTRING)
bm_CKDWORD = ctypes.c_uint32
bm_CKDWORD_p = ctypes.POINTER(bm_CKDWORD)
bm_CKDWORD_pp = ctypes.POINTER(bm_CKDWORD_p)
bm_CKWORD = ctypes.c_uint16
bm_CKWORD_p = ctypes.POINTER(bm_CKWORD)
bm_CKWORD_pp = ctypes.POINTER(bm_CKWORD_p)
bm_CKID = ctypes.c_uint32
bm_CKID_p = ctypes.POINTER(bm_CKID)
bm_CKID_pp = ctypes.POINTER(bm_CKID_p)
bm_CKFLOAT = ctypes.c_float
bm_CKFLOAT_p = ctypes.POINTER(bm_CKFLOAT)
bm_CKINT = ctypes.c_int32
bm_CKBYTE = ctypes.c_uint8
bm_CKBYTE_p = ctypes.POINTER(bm_CKBYTE)
bm_enum = bm_CKDWORD
bm_enum_p = ctypes.POINTER(bm_enum)
bm_bool = ctypes.c_bool
bm_bool_p = ctypes.POINTER(bm_bool)
bm_void_p = ctypes.c_void_p
bm_void_pp = ctypes.POINTER(ctypes.c_void_p)
bm_callback = ctypes.CFUNCTYPE(None, bm_CKSTRING)
class bm_VxVector2(ctypes.Structure):
_fields_ = [
('x', bm_CKFLOAT),
('y', bm_CKFLOAT),
]
bm_VxVector2_p = ctypes.POINTER(bm_VxVector2)
bm_VxVector2_pp = ctypes.POINTER(bm_VxVector2_p)
class bm_VxVector3(ctypes.Structure):
_fields_ = [
('x', bm_CKFLOAT),
('y', bm_CKFLOAT),
('z', bm_CKFLOAT),
]
bm_VxVector3_p = ctypes.POINTER(bm_VxVector3)
bm_VxVector3_pp = ctypes.POINTER(bm_VxVector3_p)
class bm_VxColor(ctypes.Structure):
_fields_ = [
('r', bm_CKFLOAT),
('g', bm_CKFLOAT),
('b', bm_CKFLOAT),
('a', bm_CKFLOAT),
]
bm_VxColor_p = ctypes.POINTER(bm_VxColor)
class bm_VxMatrix(ctypes.Structure):
_fields_ = list(
(f'i{idx}', bm_CKFLOAT) for idx in range(16)
)
bm_VxMatrix_p = ctypes.POINTER(bm_VxMatrix)
#endregion
#region BMap Loader
_g_BMapLibName: str
if sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
_g_BMapLibName = "BMap.dll"
elif sys.platform.startswith('linux') or sys.platform.startswith('freebsd'):
_g_BMapLibName = "BMap.so"
elif sys.platform.startswith('darwin'):
_g_BMapLibName = "BMap.dylib"
else:
_g_BMapLibName = "BMap.bin"
_g_BMapModule: ctypes.CDLL | None = None
try:
_g_BMapModule = ctypes.cdll.LoadLibrary(
os.path.join(os.path.dirname(__file__), _g_BMapLibName)
)
except:
_g_BMapModule = None
def is_bmap_available() -> bool:
return _g_BMapModule is not None
def _bmap_error_check(result: bool, func, args):
if not result:
raise BMapException("BMap operation failed.")
return result
def _create_bmap_func(fct_name: str, fct_params: list[typing.Any]) -> typing.Callable[..., bm_bool]:
if _g_BMapModule is None: return None
cache: typing.Callable[..., bm_bool] = getattr(_g_BMapModule, fct_name)
cache.argtypes = fct_params
cache.restype = bm_bool
cache.errcheck = _bmap_error_check
return cache
#endregion
#region Function Defines
## BMInit
# @return True if no error, otherwise False.
BMInit = _create_bmap_func('BMInit', [])
## BMDispose
# @return True if no error, otherwise False.
BMDispose = _create_bmap_func('BMDispose', [])
## BMFile_Load
# @param file_name[in] Type: LibCmo::CKSTRING.
# @param temp_folder[in] Type: LibCmo::CKSTRING.
# @param texture_folder[in] Type: LibCmo::CKSTRING.
# @param raw_callback[in] Type: BMap::NakedOutputCallback.
# @param encoding_count[in] Type: LibCmo::CKDWORD.
# @param encodings[in] Type: LibCmo::CKSTRING*.
# @param out_file[out] Type: BMap::BMFile*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_Load = _create_bmap_func('BMFile_Load', [bm_CKSTRING, bm_CKSTRING, bm_CKSTRING, bm_callback, bm_CKDWORD, bm_CKSTRING_p, bm_void_pp])
## BMFile_Create
# @param temp_folder[in] Type: LibCmo::CKSTRING.
# @param texture_folder[in] Type: LibCmo::CKSTRING.
# @param raw_callback[in] Type: BMap::NakedOutputCallback.
# @param encoding_count[in] Type: LibCmo::CKDWORD.
# @param encodings[in] Type: LibCmo::CKSTRING*.
# @param out_file[out] Type: BMap::BMFile*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_Create = _create_bmap_func('BMFile_Create', [bm_CKSTRING, bm_CKSTRING, bm_callback, bm_CKDWORD, bm_CKSTRING_p, bm_void_pp])
## BMFile_Save
# @param map_file[in] Type: BMap::BMFile*.
# @param file_name[in] Type: LibCmo::CKSTRING.
# @param texture_save_opt[in] Type: LibCmo::CK2::CK_TEXTURE_SAVEOPTIONS.
# @param use_compress[in] Type: bool.
# @param compreess_level[in] Type: LibCmo::CKINT.
# @return True if no error, otherwise False.
BMFile_Save = _create_bmap_func('BMFile_Save', [bm_void_p, bm_CKSTRING, bm_enum, bm_bool, bm_CKINT])
## BMFile_Free
# @param map_file[in] Type: BMap::BMFile*.
# @return True if no error, otherwise False.
BMFile_Free = _create_bmap_func('BMFile_Free', [bm_void_p])
## BMFile_GetGroupCount
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_GetGroupCount = _create_bmap_func('BMFile_GetGroupCount', [bm_void_p, bm_CKDWORD_p])
## BMFile_GetGroup
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param idx[in] Type: LibCmo::CKDWORD.
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_GetGroup = _create_bmap_func('BMFile_GetGroup', [bm_void_p, bm_CKDWORD, bm_CKID_p])
## BMFile_CreateGroup
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_CreateGroup = _create_bmap_func('BMFile_CreateGroup', [bm_void_p, bm_CKID_p])
## BMFile_Get3dObjectCount
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_Get3dObjectCount = _create_bmap_func('BMFile_Get3dObjectCount', [bm_void_p, bm_CKDWORD_p])
## BMFile_Get3dObject
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param idx[in] Type: LibCmo::CKDWORD.
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_Get3dObject = _create_bmap_func('BMFile_Get3dObject', [bm_void_p, bm_CKDWORD, bm_CKID_p])
## BMFile_Create3dObject
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_Create3dObject = _create_bmap_func('BMFile_Create3dObject', [bm_void_p, bm_CKID_p])
## BMFile_GetMeshCount
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_GetMeshCount = _create_bmap_func('BMFile_GetMeshCount', [bm_void_p, bm_CKDWORD_p])
## BMFile_GetMesh
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param idx[in] Type: LibCmo::CKDWORD.
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_GetMesh = _create_bmap_func('BMFile_GetMesh', [bm_void_p, bm_CKDWORD, bm_CKID_p])
## BMFile_CreateMesh
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_CreateMesh = _create_bmap_func('BMFile_CreateMesh', [bm_void_p, bm_CKID_p])
## BMFile_GetMaterialCount
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_GetMaterialCount = _create_bmap_func('BMFile_GetMaterialCount', [bm_void_p, bm_CKDWORD_p])
## BMFile_GetMaterial
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param idx[in] Type: LibCmo::CKDWORD.
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_GetMaterial = _create_bmap_func('BMFile_GetMaterial', [bm_void_p, bm_CKDWORD, bm_CKID_p])
## BMFile_CreateMaterial
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_CreateMaterial = _create_bmap_func('BMFile_CreateMaterial', [bm_void_p, bm_CKID_p])
## BMFile_GetTextureCount
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_GetTextureCount = _create_bmap_func('BMFile_GetTextureCount', [bm_void_p, bm_CKDWORD_p])
## BMFile_GetTexture
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param idx[in] Type: LibCmo::CKDWORD.
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_GetTexture = _create_bmap_func('BMFile_GetTexture', [bm_void_p, bm_CKDWORD, bm_CKID_p])
## BMFile_CreateTexture
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param out_id[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMFile_CreateTexture = _create_bmap_func('BMFile_CreateTexture', [bm_void_p, bm_CKID_p])
## BMMeshTrans_New
# @param out_trans[out] Type: BMap::BMMeshTransition*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMeshTrans_New = _create_bmap_func('BMMeshTrans_New', [bm_void_pp])
## BMMeshTrans_Delete
# @param trans[in] Type: BMap::BMMeshTransition*.
# @return True if no error, otherwise False.
BMMeshTrans_Delete = _create_bmap_func('BMMeshTrans_Delete', [bm_void_p])
## BMMeshTrans_PrepareVertexCount
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param count[in] Type: LibCmo::CKDWORD.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareVertexCount = _create_bmap_func('BMMeshTrans_PrepareVertexCount', [bm_void_p, bm_CKDWORD])
## BMMeshTrans_PrepareVertex
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param out_mem[out] Type: LibCmo::VxMath::VxVector3*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareVertex = _create_bmap_func('BMMeshTrans_PrepareVertex', [bm_void_p, bm_VxVector3_pp])
## BMMeshTrans_PrepareNormalCount
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param count[in] Type: LibCmo::CKDWORD.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareNormalCount = _create_bmap_func('BMMeshTrans_PrepareNormalCount', [bm_void_p, bm_CKDWORD])
## BMMeshTrans_PrepareNormal
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param out_mem[out] Type: LibCmo::VxMath::VxVector3*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareNormal = _create_bmap_func('BMMeshTrans_PrepareNormal', [bm_void_p, bm_VxVector3_pp])
## BMMeshTrans_PrepareUVCount
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param count[in] Type: LibCmo::CKDWORD.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareUVCount = _create_bmap_func('BMMeshTrans_PrepareUVCount', [bm_void_p, bm_CKDWORD])
## BMMeshTrans_PrepareUV
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param out_mem[out] Type: LibCmo::VxMath::VxVector2*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareUV = _create_bmap_func('BMMeshTrans_PrepareUV', [bm_void_p, bm_VxVector2_pp])
## BMMeshTrans_PrepareMtlSlotCount
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param count[in] Type: LibCmo::CKDWORD.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareMtlSlotCount = _create_bmap_func('BMMeshTrans_PrepareMtlSlotCount', [bm_void_p, bm_CKDWORD])
## BMMeshTrans_PrepareMtlSlot
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param out_mem[out] Type: LibCmo::CK2::CK_ID*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareMtlSlot = _create_bmap_func('BMMeshTrans_PrepareMtlSlot', [bm_void_p, bm_CKID_pp])
## BMMeshTrans_PrepareFaceCount
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param count[in] Type: LibCmo::CKDWORD.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareFaceCount = _create_bmap_func('BMMeshTrans_PrepareFaceCount', [bm_void_p, bm_CKDWORD])
## BMMeshTrans_PrepareFaceVertexIndices
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param out_mem[out] Type: LibCmo::CKDWORD*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareFaceVertexIndices = _create_bmap_func('BMMeshTrans_PrepareFaceVertexIndices', [bm_void_p, bm_CKDWORD_pp])
## BMMeshTrans_PrepareFaceNormalIndices
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param out_mem[out] Type: LibCmo::CKDWORD*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareFaceNormalIndices = _create_bmap_func('BMMeshTrans_PrepareFaceNormalIndices', [bm_void_p, bm_CKDWORD_pp])
## BMMeshTrans_PrepareFaceUVIndices
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param out_mem[out] Type: LibCmo::CKDWORD*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareFaceUVIndices = _create_bmap_func('BMMeshTrans_PrepareFaceUVIndices', [bm_void_p, bm_CKDWORD_pp])
## BMMeshTrans_PrepareFaceMtlSlot
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param out_mem[out] Type: LibCmo::CKDWORD*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMeshTrans_PrepareFaceMtlSlot = _create_bmap_func('BMMeshTrans_PrepareFaceMtlSlot', [bm_void_p, bm_CKDWORD_pp])
## BMMeshTrans_Parse
# @param trans[in] Type: BMap::BMMeshTransition*. The pointer to corresponding BMMeshTransition.
# @param bmfile[in] Type: BMap::BMFile*.
# @param objid[in] Type: LibCmo::CK2::CK_ID.
# @return True if no error, otherwise False.
BMMeshTrans_Parse = _create_bmap_func('BMMeshTrans_Parse', [bm_void_p, bm_void_p, bm_CKID])
## BMObject_GetName
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_name[out] Type: LibCmo::CKSTRING. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMObject_GetName = _create_bmap_func('BMObject_GetName', [bm_void_p, bm_CKID, bm_CKSTRING_p])
## BMObject_SetName
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param name[in] Type: LibCmo::CKSTRING.
# @return True if no error, otherwise False.
BMObject_SetName = _create_bmap_func('BMObject_SetName', [bm_void_p, bm_CKID, bm_CKSTRING])
## BMGroup_AddObject
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param memberid[in] Type: LibCmo::CK2::CK_ID.
# @return True if no error, otherwise False.
BMGroup_AddObject = _create_bmap_func('BMGroup_AddObject', [bm_void_p, bm_CKID, bm_CKID])
## BMGroup_GetObjectCount
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMGroup_GetObjectCount = _create_bmap_func('BMGroup_GetObjectCount', [bm_void_p, bm_CKID, bm_CKDWORD_p])
## BMGroup_GetObject
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param pos[in] Type: LibCmo::CKDWORD.
# @param out_objid[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMGroup_GetObject = _create_bmap_func('BMGroup_GetObject', [bm_void_p, bm_CKID, bm_CKDWORD, bm_CKID_p])
## BMTexture_GetFileName
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_filename[out] Type: LibCmo::CKSTRING. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMTexture_GetFileName = _create_bmap_func('BMTexture_GetFileName', [bm_void_p, bm_CKID, bm_CKSTRING_p])
## BMTexture_LoadImage
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param filename[in] Type: LibCmo::CKSTRING.
# @return True if no error, otherwise False.
BMTexture_LoadImage = _create_bmap_func('BMTexture_LoadImage', [bm_void_p, bm_CKID, bm_CKSTRING])
## BMTexture_SaveImage
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param filename[in] Type: LibCmo::CKSTRING.
# @return True if no error, otherwise False.
BMTexture_SaveImage = _create_bmap_func('BMTexture_SaveImage', [bm_void_p, bm_CKID, bm_CKSTRING])
## BMTexture_GetSaveOptions
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_saveopt[out] Type: LibCmo::CK2::CK_TEXTURE_SAVEOPTIONS. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMTexture_GetSaveOptions = _create_bmap_func('BMTexture_GetSaveOptions', [bm_void_p, bm_CKID, bm_enum_p])
## BMTexture_SetSaveOptions
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param saveopt[in] Type: LibCmo::CK2::CK_TEXTURE_SAVEOPTIONS.
# @return True if no error, otherwise False.
BMTexture_SetSaveOptions = _create_bmap_func('BMTexture_SetSaveOptions', [bm_void_p, bm_CKID, bm_enum])
## BMTexture_GetVideoFormat
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_vfmt[out] Type: LibCmo::VxMath::VX_PIXELFORMAT. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMTexture_GetVideoFormat = _create_bmap_func('BMTexture_GetVideoFormat', [bm_void_p, bm_CKID, bm_enum_p])
## BMTexture_SetVideoFormat
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param vfmt[in] Type: LibCmo::VxMath::VX_PIXELFORMAT.
# @return True if no error, otherwise False.
BMTexture_SetVideoFormat = _create_bmap_func('BMTexture_SetVideoFormat', [bm_void_p, bm_CKID, bm_enum])
## BMMaterial_GetDiffuse
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VxColor. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetDiffuse = _create_bmap_func('BMMaterial_GetDiffuse', [bm_void_p, bm_CKID, bm_VxColor_p])
## BMMaterial_SetDiffuse
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param col[in] Type: LibCmo::VxMath::VxColor.
# @return True if no error, otherwise False.
BMMaterial_SetDiffuse = _create_bmap_func('BMMaterial_SetDiffuse', [bm_void_p, bm_CKID, bm_VxColor])
## BMMaterial_GetAmbient
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VxColor. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetAmbient = _create_bmap_func('BMMaterial_GetAmbient', [bm_void_p, bm_CKID, bm_VxColor_p])
## BMMaterial_SetAmbient
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param col[in] Type: LibCmo::VxMath::VxColor.
# @return True if no error, otherwise False.
BMMaterial_SetAmbient = _create_bmap_func('BMMaterial_SetAmbient', [bm_void_p, bm_CKID, bm_VxColor])
## BMMaterial_GetSpecular
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VxColor. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetSpecular = _create_bmap_func('BMMaterial_GetSpecular', [bm_void_p, bm_CKID, bm_VxColor_p])
## BMMaterial_SetSpecular
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param col[in] Type: LibCmo::VxMath::VxColor.
# @return True if no error, otherwise False.
BMMaterial_SetSpecular = _create_bmap_func('BMMaterial_SetSpecular', [bm_void_p, bm_CKID, bm_VxColor])
## BMMaterial_GetEmissive
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VxColor. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetEmissive = _create_bmap_func('BMMaterial_GetEmissive', [bm_void_p, bm_CKID, bm_VxColor_p])
## BMMaterial_SetEmissive
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param col[in] Type: LibCmo::VxMath::VxColor.
# @return True if no error, otherwise False.
BMMaterial_SetEmissive = _create_bmap_func('BMMaterial_SetEmissive', [bm_void_p, bm_CKID, bm_VxColor])
## BMMaterial_GetSpecularPower
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::CKFLOAT. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetSpecularPower = _create_bmap_func('BMMaterial_GetSpecularPower', [bm_void_p, bm_CKID, bm_CKFLOAT_p])
## BMMaterial_SetSpecularPower
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::CKFLOAT.
# @return True if no error, otherwise False.
BMMaterial_SetSpecularPower = _create_bmap_func('BMMaterial_SetSpecularPower', [bm_void_p, bm_CKID, bm_CKFLOAT])
## BMMaterial_GetTexture
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_texid[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetTexture = _create_bmap_func('BMMaterial_GetTexture', [bm_void_p, bm_CKID, bm_CKID_p])
## BMMaterial_SetTexture
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param texid[in] Type: LibCmo::CK2::CK_ID.
# @return True if no error, otherwise False.
BMMaterial_SetTexture = _create_bmap_func('BMMaterial_SetTexture', [bm_void_p, bm_CKID, bm_CKID])
## BMMaterial_GetTextureBorderColor
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetTextureBorderColor = _create_bmap_func('BMMaterial_GetTextureBorderColor', [bm_void_p, bm_CKID, bm_CKDWORD_p])
## BMMaterial_SetTextureBorderColor
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::CKDWORD.
# @return True if no error, otherwise False.
BMMaterial_SetTextureBorderColor = _create_bmap_func('BMMaterial_SetTextureBorderColor', [bm_void_p, bm_CKID, bm_CKDWORD])
## BMMaterial_GetTextureBlendMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VXTEXTURE_BLENDMODE. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetTextureBlendMode = _create_bmap_func('BMMaterial_GetTextureBlendMode', [bm_void_p, bm_CKID, bm_enum_p])
## BMMaterial_SetTextureBlendMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::VxMath::VXTEXTURE_BLENDMODE.
# @return True if no error, otherwise False.
BMMaterial_SetTextureBlendMode = _create_bmap_func('BMMaterial_SetTextureBlendMode', [bm_void_p, bm_CKID, bm_enum])
## BMMaterial_GetTextureMinMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VXTEXTURE_FILTERMODE. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetTextureMinMode = _create_bmap_func('BMMaterial_GetTextureMinMode', [bm_void_p, bm_CKID, bm_enum_p])
## BMMaterial_SetTextureMinMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::VxMath::VXTEXTURE_FILTERMODE.
# @return True if no error, otherwise False.
BMMaterial_SetTextureMinMode = _create_bmap_func('BMMaterial_SetTextureMinMode', [bm_void_p, bm_CKID, bm_enum])
## BMMaterial_GetTextureMagMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VXTEXTURE_FILTERMODE. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetTextureMagMode = _create_bmap_func('BMMaterial_GetTextureMagMode', [bm_void_p, bm_CKID, bm_enum_p])
## BMMaterial_SetTextureMagMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::VxMath::VXTEXTURE_FILTERMODE.
# @return True if no error, otherwise False.
BMMaterial_SetTextureMagMode = _create_bmap_func('BMMaterial_SetTextureMagMode', [bm_void_p, bm_CKID, bm_enum])
## BMMaterial_GetTextureAddressMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VXTEXTURE_ADDRESSMODE. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetTextureAddressMode = _create_bmap_func('BMMaterial_GetTextureAddressMode', [bm_void_p, bm_CKID, bm_enum_p])
## BMMaterial_SetTextureAddressMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::VxMath::VXTEXTURE_ADDRESSMODE.
# @return True if no error, otherwise False.
BMMaterial_SetTextureAddressMode = _create_bmap_func('BMMaterial_SetTextureAddressMode', [bm_void_p, bm_CKID, bm_enum])
## BMMaterial_GetSourceBlend
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VXBLEND_MODE. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetSourceBlend = _create_bmap_func('BMMaterial_GetSourceBlend', [bm_void_p, bm_CKID, bm_enum_p])
## BMMaterial_SetSourceBlend
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::VxMath::VXBLEND_MODE.
# @return True if no error, otherwise False.
BMMaterial_SetSourceBlend = _create_bmap_func('BMMaterial_SetSourceBlend', [bm_void_p, bm_CKID, bm_enum])
## BMMaterial_GetDestBlend
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VXBLEND_MODE. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetDestBlend = _create_bmap_func('BMMaterial_GetDestBlend', [bm_void_p, bm_CKID, bm_enum_p])
## BMMaterial_SetDestBlend
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::VxMath::VXBLEND_MODE.
# @return True if no error, otherwise False.
BMMaterial_SetDestBlend = _create_bmap_func('BMMaterial_SetDestBlend', [bm_void_p, bm_CKID, bm_enum])
## BMMaterial_GetFillMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VXFILL_MODE. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetFillMode = _create_bmap_func('BMMaterial_GetFillMode', [bm_void_p, bm_CKID, bm_enum_p])
## BMMaterial_SetFillMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::VxMath::VXFILL_MODE.
# @return True if no error, otherwise False.
BMMaterial_SetFillMode = _create_bmap_func('BMMaterial_SetFillMode', [bm_void_p, bm_CKID, bm_enum])
## BMMaterial_GetShadeMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VXSHADE_MODE. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetShadeMode = _create_bmap_func('BMMaterial_GetShadeMode', [bm_void_p, bm_CKID, bm_enum_p])
## BMMaterial_SetShadeMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::VxMath::VXSHADE_MODE.
# @return True if no error, otherwise False.
BMMaterial_SetShadeMode = _create_bmap_func('BMMaterial_SetShadeMode', [bm_void_p, bm_CKID, bm_enum])
## BMMaterial_GetAlphaTestEnabled
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetAlphaTestEnabled = _create_bmap_func('BMMaterial_GetAlphaTestEnabled', [bm_void_p, bm_CKID, bm_bool_p])
## BMMaterial_SetAlphaTestEnabled
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param enabled[in] Type: bool.
# @return True if no error, otherwise False.
BMMaterial_SetAlphaTestEnabled = _create_bmap_func('BMMaterial_SetAlphaTestEnabled', [bm_void_p, bm_CKID, bm_bool])
## BMMaterial_GetAlphaBlendEnabled
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetAlphaBlendEnabled = _create_bmap_func('BMMaterial_GetAlphaBlendEnabled', [bm_void_p, bm_CKID, bm_bool_p])
## BMMaterial_SetAlphaBlendEnabled
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param enabled[in] Type: bool.
# @return True if no error, otherwise False.
BMMaterial_SetAlphaBlendEnabled = _create_bmap_func('BMMaterial_SetAlphaBlendEnabled', [bm_void_p, bm_CKID, bm_bool])
## BMMaterial_GetPerspectiveCorrectionEnabled
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetPerspectiveCorrectionEnabled = _create_bmap_func('BMMaterial_GetPerspectiveCorrectionEnabled', [bm_void_p, bm_CKID, bm_bool_p])
## BMMaterial_SetPerspectiveCorrectionEnabled
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param enabled[in] Type: bool.
# @return True if no error, otherwise False.
BMMaterial_SetPerspectiveCorrectionEnabled = _create_bmap_func('BMMaterial_SetPerspectiveCorrectionEnabled', [bm_void_p, bm_CKID, bm_bool])
## BMMaterial_GetZWriteEnabled
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetZWriteEnabled = _create_bmap_func('BMMaterial_GetZWriteEnabled', [bm_void_p, bm_CKID, bm_bool_p])
## BMMaterial_SetZWriteEnabled
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param enabled[in] Type: bool.
# @return True if no error, otherwise False.
BMMaterial_SetZWriteEnabled = _create_bmap_func('BMMaterial_SetZWriteEnabled', [bm_void_p, bm_CKID, bm_bool])
## BMMaterial_GetTwoSidedEnabled
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: bool. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetTwoSidedEnabled = _create_bmap_func('BMMaterial_GetTwoSidedEnabled', [bm_void_p, bm_CKID, bm_bool_p])
## BMMaterial_SetTwoSidedEnabled
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param enabled[in] Type: bool.
# @return True if no error, otherwise False.
BMMaterial_SetTwoSidedEnabled = _create_bmap_func('BMMaterial_SetTwoSidedEnabled', [bm_void_p, bm_CKID, bm_bool])
## BMMaterial_GetAlphaRef
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::CKBYTE. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetAlphaRef = _create_bmap_func('BMMaterial_GetAlphaRef', [bm_void_p, bm_CKID, bm_CKBYTE_p])
## BMMaterial_SetAlphaRef
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::CKBYTE.
# @return True if no error, otherwise False.
BMMaterial_SetAlphaRef = _create_bmap_func('BMMaterial_SetAlphaRef', [bm_void_p, bm_CKID, bm_CKBYTE])
## BMMaterial_GetAlphaFunc
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VXCMPFUNC. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetAlphaFunc = _create_bmap_func('BMMaterial_GetAlphaFunc', [bm_void_p, bm_CKID, bm_enum_p])
## BMMaterial_SetAlphaFunc
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::VxMath::VXCMPFUNC.
# @return True if no error, otherwise False.
BMMaterial_SetAlphaFunc = _create_bmap_func('BMMaterial_SetAlphaFunc', [bm_void_p, bm_CKID, bm_enum])
## BMMaterial_GetZFunc
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_val[out] Type: LibCmo::VxMath::VXCMPFUNC. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMaterial_GetZFunc = _create_bmap_func('BMMaterial_GetZFunc', [bm_void_p, bm_CKID, bm_enum_p])
## BMMaterial_SetZFunc
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param val[in] Type: LibCmo::VxMath::VXCMPFUNC.
# @return True if no error, otherwise False.
BMMaterial_SetZFunc = _create_bmap_func('BMMaterial_SetZFunc', [bm_void_p, bm_CKID, bm_enum])
## BMMesh_GetLitMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_mode[out] Type: LibCmo::VxMath::VXMESH_LITMODE. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMesh_GetLitMode = _create_bmap_func('BMMesh_GetLitMode', [bm_void_p, bm_CKID, bm_enum_p])
## BMMesh_SetLitMode
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param mode[in] Type: LibCmo::VxMath::VXMESH_LITMODE.
# @return True if no error, otherwise False.
BMMesh_SetLitMode = _create_bmap_func('BMMesh_SetLitMode', [bm_void_p, bm_CKID, bm_enum])
## BMMesh_GetVertexCount
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMesh_GetVertexCount = _create_bmap_func('BMMesh_GetVertexCount', [bm_void_p, bm_CKID, bm_CKDWORD_p])
## BMMesh_SetVertexCount
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param count[in] Type: LibCmo::CKDWORD.
# @return True if no error, otherwise False.
BMMesh_SetVertexCount = _create_bmap_func('BMMesh_SetVertexCount', [bm_void_p, bm_CKID, bm_CKDWORD])
## BMMesh_GetVertexPositions
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_mem[out] Type: LibCmo::VxMath::VxVector3*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMesh_GetVertexPositions = _create_bmap_func('BMMesh_GetVertexPositions', [bm_void_p, bm_CKID, bm_VxVector3_pp])
## BMMesh_GetVertexNormals
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_mem[out] Type: LibCmo::VxMath::VxVector3*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMesh_GetVertexNormals = _create_bmap_func('BMMesh_GetVertexNormals', [bm_void_p, bm_CKID, bm_VxVector3_pp])
## BMMesh_GetVertexUVs
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_mem[out] Type: LibCmo::VxMath::VxVector2*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMesh_GetVertexUVs = _create_bmap_func('BMMesh_GetVertexUVs', [bm_void_p, bm_CKID, bm_VxVector2_pp])
## BMMesh_GetFaceCount
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMesh_GetFaceCount = _create_bmap_func('BMMesh_GetFaceCount', [bm_void_p, bm_CKID, bm_CKDWORD_p])
## BMMesh_SetFaceCount
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param count[in] Type: LibCmo::CKDWORD.
# @return True if no error, otherwise False.
BMMesh_SetFaceCount = _create_bmap_func('BMMesh_SetFaceCount', [bm_void_p, bm_CKID, bm_CKDWORD])
## BMMesh_GetFaceIndices
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_mem[out] Type: LibCmo::CKWORD*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMesh_GetFaceIndices = _create_bmap_func('BMMesh_GetFaceIndices', [bm_void_p, bm_CKID, bm_CKWORD_pp])
## BMMesh_GetFaceMaterialSlotIndexs
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_mem[out] Type: LibCmo::CKWORD*. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMesh_GetFaceMaterialSlotIndexs = _create_bmap_func('BMMesh_GetFaceMaterialSlotIndexs', [bm_void_p, bm_CKID, bm_CKWORD_pp])
## BMMesh_GetMaterialSlotCount
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_count[out] Type: LibCmo::CKDWORD. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMesh_GetMaterialSlotCount = _create_bmap_func('BMMesh_GetMaterialSlotCount', [bm_void_p, bm_CKID, bm_CKDWORD_p])
## BMMesh_SetMaterialSlotCount
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param count[in] Type: LibCmo::CKDWORD.
# @return True if no error, otherwise False.
BMMesh_SetMaterialSlotCount = _create_bmap_func('BMMesh_SetMaterialSlotCount', [bm_void_p, bm_CKID, bm_CKDWORD])
## BMMesh_GetMaterialSlot
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param index[in] Type: LibCmo::CKDWORD.
# @param out_mtlid[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BMMesh_GetMaterialSlot = _create_bmap_func('BMMesh_GetMaterialSlot', [bm_void_p, bm_CKID, bm_CKDWORD, bm_CKID_p])
## BMMesh_SetMaterialSlot
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param index[in] Type: LibCmo::CKDWORD.
# @param mtlid[in] Type: LibCmo::CK2::CK_ID.
# @return True if no error, otherwise False.
BMMesh_SetMaterialSlot = _create_bmap_func('BMMesh_SetMaterialSlot', [bm_void_p, bm_CKID, bm_CKDWORD, bm_CKID])
## BM3dObject_GetWorldMatrix
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_mat[out] Type: LibCmo::VxMath::VxMatrix. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BM3dObject_GetWorldMatrix = _create_bmap_func('BM3dObject_GetWorldMatrix', [bm_void_p, bm_CKID, bm_VxMatrix_p])
## BM3dObject_SetWorldMatrix
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param mat[in] Type: LibCmo::VxMath::VxMatrix.
# @return True if no error, otherwise False.
BM3dObject_SetWorldMatrix = _create_bmap_func('BM3dObject_SetWorldMatrix', [bm_void_p, bm_CKID, bm_VxMatrix])
## BM3dObject_GetCurrentMesh
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_meshid[out] Type: LibCmo::CK2::CK_ID. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BM3dObject_GetCurrentMesh = _create_bmap_func('BM3dObject_GetCurrentMesh', [bm_void_p, bm_CKID, bm_CKID_p])
## BM3dObject_SetCurrentMesh
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param meshid[in] Type: LibCmo::CK2::CK_ID.
# @return True if no error, otherwise False.
BM3dObject_SetCurrentMesh = _create_bmap_func('BM3dObject_SetCurrentMesh', [bm_void_p, bm_CKID, bm_CKID])
## BM3dObject_GetVisibility
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param out_isVisible[out] Type: bool. Use ctypes.byref(data) pass it.
# @return True if no error, otherwise False.
BM3dObject_GetVisibility = _create_bmap_func('BM3dObject_GetVisibility', [bm_void_p, bm_CKID, bm_bool_p])
## BM3dObject_SetVisibility
# @param bmfile[in] Type: BMap::BMFile*. The pointer to corresponding BMFile.
# @param objid[in] Type: LibCmo::CK2::CK_ID. The CKID of object you accessing.
# @param is_visible[in] Type: bool.
# @return True if no error, otherwise False.
BM3dObject_SetVisibility = _create_bmap_func('BM3dObject_SetVisibility', [bm_void_p, bm_CKID, bm_bool])
#endregion

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,11 +104,15 @@ def _nest_custom_split_normal(nml_array: array.array) -> typing.Iterator[UTIL_vi
class TemporaryMesh():
"""
Create a temporary mesh for convenient exporting.
When exporting mesh, we need triangulate it first.
We create a temporary mesh to hold the triangulated mesh result.
So that original object will not be affected and keep its original geometry.
Please note passed bpy.types.Object must be Mesh Object.
"""
__mBindingObject: bpy.types.Object | None
__mTempMesh: bpy.types.Mesh | None
__mBindingObject: bpy.types.Object
__mTempMesh: bpy.types.Mesh
def __init__(self, binding_obj: bpy.types.Object):
self.__mBindingObject = binding_obj
@ -149,7 +153,7 @@ class MeshReader():
A helper class TemporaryMesh can help you do this.
"""
__mAssocMesh: bpy.types.Mesh | None ##< The binding mesh for this reader. None if this reader is invalid.
__mAssocMesh: bpy.types.Mesh ##< The binding mesh for this reader. None if this reader is invalid.
def __init__(self, assoc_mesh: bpy.types.Mesh):
self.__mAssocMesh = assoc_mesh
@ -157,7 +161,6 @@ class MeshReader():
# triangulate temp mesh
if self.is_valid():
self.__triangulate_mesh()
self.__mAssocMesh.calc_normals_split()
def is_valid(self) -> bool:
return self.__mAssocMesh is not None
@ -171,7 +174,6 @@ class MeshReader():
def dispose(self) -> None:
if self.is_valid():
# reset mesh
self.__mAssocMesh.free_normals_split()
self.__mAssocMesh = None
def get_vertex_position_count(self) -> int:
@ -203,10 +205,10 @@ class MeshReader():
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
cache: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
for nml in self.__mAssocMesh.loops:
cache.x = nml.normal.x
cache.y = nml.normal.y
cache.z = nml.normal.z
for nml in self.__mAssocMesh.corner_normals:
cache.x = nml.vector.x
cache.y = nml.vector.y
cache.z = nml.vector.z
yield cache
def get_vertex_uv_count(self) -> int:
@ -318,7 +320,7 @@ class MeshWriter():
then refer it to all face uv.
"""
__mAssocMesh: bpy.types.Mesh | None ##< The binding mesh for this writer. None if this writer is invalid.
__mAssocMesh: bpy.types.Mesh ##< The binding mesh for this writer. None if this writer is invalid.
__mVertexPos: array.array ##< Array item is float(f). Length must be an integer multiple of 3.
__mVertexNormal: array.array ##< Array item is float(f). Length must be an integer multiple of 3.
@ -348,6 +350,9 @@ class MeshWriter():
# Value is key's index in __mMtlSlot.
__mMtlSlotMap: dict[bpy.types.Material | None, int]
## The attribute name storing temporary normals data inside mesh.
__cTempNormalAttrName: typing.ClassVar[str] = 'temp_custom_normals'
def __init__(self, assoc_mesh: bpy.types.Mesh):
self.__mAssocMesh = assoc_mesh
@ -446,15 +451,19 @@ class MeshWriter():
self.__mAssocMesh.polygons.add(len(self.__mFaceVertexCount))
# create uv layer
self.__mAssocMesh.uv_layers.new(do_init = False)
# split normals, it is IMPORTANT
self.__mAssocMesh.create_normals_split()
# add vertex position data
self.__mAssocMesh.vertices.foreach_set('co', self.__mVertexPos)
# add face vertex pos index data
self.__mAssocMesh.loops.foreach_set('vertex_index', self.__mFacePosIndices)
# add face vertex nml by function
self.__mAssocMesh.loops.foreach_set('normal',
# add face vertex nml by function via mesh custom attribute
# NOTE: Blender 4.0 / 4.1 changed. I copy these code from FBX Importer.
temp_normal_attribute: bpy.types.FloatVectorAttribute
temp_normal_attribute = typing.cast(
bpy.types.FloatVectorAttribute,
self.__mAssocMesh.attributes.new(MeshWriter.__cTempNormalAttrName, 'FLOAT_VECTOR', 'CORNER')
)
temp_normal_attribute.data.foreach_set('vector',
tuple(_flat_face_nml_index(self.__mFaceNmlIndices, self.__mVertexNormal))
)
# add face vertex uv by function
@ -494,14 +503,22 @@ class MeshWriter():
# this should not happend in normal case, for testing, please load "Level_1.NMO" (Ballance Level 1).
# copy data from loops preserved in validate().
# NOTE: Blender 4.0 / 4.1 changed. I copy these code from FBX Importer.
loops_normals = array.array('f', [0.0] * (len(self.__mAssocMesh.loops) * 3))
self.__mAssocMesh.loops.foreach_get('normal', loops_normals)
temp_normal_attribute = typing.cast(
bpy.types.FloatVectorAttribute,
self.__mAssocMesh.attributes[MeshWriter.__cTempNormalAttrName]
)
temp_normal_attribute.data.foreach_get("vector", loops_normals)
# apply data
self.__mAssocMesh.normals_split_custom_set(
tuple(_nest_custom_split_normal(loops_normals))
)
# enable auto smooth. it is IMPORTANT
self.__mAssocMesh.use_auto_smooth = True
self.__mAssocMesh.attributes.remove(
# MARK: idk why I need fucking get this attribute again.
# But if I were not, this function must raise bullshit exception!
self.__mAssocMesh.attributes[MeshWriter.__cTempNormalAttrName]
)
def __clear_mesh(self):
if not self.is_valid():

View File

@ -344,6 +344,12 @@ def create_bme_struct(
cache_bv = typing.cast(mathutils.Vector, transform @ cache_bv)
# get result
prebuild_vec_data.append((cache_bv.x, cache_bv.y, cache_bv.z))
# Check whether given transform is mirror matrix
# because mirror matrix will reverse triangle indice order.
# If matrix is mirror matrix, we need reverse it again in following procession,
# including getting uv, calculating normal and providing face data.
mirror_matrix: bool = _is_mirror_matrix(transform)
# prepare mesh part data
mesh_part: UTIL_blender_mesh.MeshWriterIngredient = UTIL_blender_mesh.MeshWriterIngredient()
@ -370,7 +376,12 @@ def create_bme_struct(
if face_nml_data is None:
# nml is null, we need compute by ourselves
# get first 3 entries in indices list as the compution ref
face_indices_data: list[int] = face_data[TOKEN_FACES_INDICES]
# please note that we may need reverse it
face_indices_data: list[int]
if mirror_matrix:
face_indices_data = face_data[TOKEN_FACES_INDICES][::-1]
else:
face_indices_data = face_data[TOKEN_FACES_INDICES][:]
# compute it by getting vertices info from prebuild vertices data
# because the normals is computed from transformed vertices
# so no need to correct its by normal transform.
@ -399,7 +410,9 @@ def create_bme_struct(
v: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2()
for face_idx in valid_face_idx:
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx]
for i in range(len(face_data[TOKEN_FACES_INDICES])):
# iterate uv list considering mirror matrix
indices_count: int = len(face_data[TOKEN_FACES_INDICES])
for i in (range(indices_count)[::-1] if mirror_matrix else range(indices_count)):
# BME uv do not need any extra process
v.x, v.y = _eval_others(face_data[TOKEN_FACES_UVS][i], params)
yield v
@ -424,8 +437,13 @@ def create_bme_struct(
# get face data
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx]
# get face indices considering the mirror matrix
face_indices: list[int]
if mirror_matrix:
face_indices = face_data[TOKEN_FACES_INDICES][::-1]
else:
face_indices = face_data[TOKEN_FACES_INDICES][:]
# calc indices count
face_indices: list[int] = face_data[TOKEN_FACES_INDICES]
indices_count: int = len(face_indices)
# resize face data to fulfill req
while len(f.mIndices) > indices_count:
@ -499,5 +517,18 @@ def _compute_normals(
corss_mul.normalize()
return (corss_mul.x, corss_mul.y, corss_mul.z)
def _is_mirror_matrix(mat: mathutils.Matrix) -> bool:
"""
Reflection matrix (aka. mirror matrix) is a special scaling matrix.
In this matrix, 1 or 3 scaling factor is minus number.
Mirror matrix will cause the inverse of triangle indice order.
So we need detect it and re-reverse when creating bm struct.
This function can detect whether given matrix is mirror matrix.
Reference: https://zhuanlan.zhihu.com/p/96717729
"""
return mat.is_negative
#return mat.to_3x3().determinant() < 0
#endregion

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
@ -94,7 +94,7 @@ class ImportDirectory(bpy_extras.io_utils.ImportHelper):
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:
@ -80,6 +82,16 @@ def select_certain_objects(objs: tuple[bpy.types.Object, ...]) -> None:
# select first object as active object
bpy.context.view_layer.objects.active = objs[0]
def is_in_object_mode() -> bool:
# get active object from context
obj = bpy.context.active_object
# if there is no active object, we think it is in object mode
if obj is None: return True
# simply check active object mode
return obj.mode == 'OBJECT'
class EnumPropHelper():
"""
These class contain all functions related to EnumProperty, including generating `items`,

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,38 +159,55 @@ class ImportParams():
items = _g_EnumHelper_ConflictStrategy.generate_items(),
description = "Define how to process texture name conflict",
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Current),
)
) # type: ignore
material_conflict_strategy: bpy.props.EnumProperty(
name = "Material Name Conflict",
items = _g_EnumHelper_ConflictStrategy.generate_items(),
description = "Define how to process material name conflict",
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
)
) # type: ignore
mesh_conflict_strategy: bpy.props.EnumProperty(
name = "Mesh Name Conflict",
items = _g_EnumHelper_ConflictStrategy.generate_items(),
description = "Define how to process mesh name conflict",
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
)
) # 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),
) # type: ignore
object_conflict_strategy: bpy.props.EnumProperty(
name = "Object Name Conflict",
items = _g_EnumHelper_ConflictStrategy.generate_items(),
description = "Define how to process object name conflict",
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
)
) # 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')
if body is None: return
body.label(text = 'Name Conflict Strategy')
grid = body.grid_flow(row_major = False, columns = 2)
grid.label(text = 'Object', icon = 'CUBE')
grid.label(text = 'Light', icon = 'LIGHT')
grid.label(text = 'Mesh', icon = 'MESH_DATA')
grid.label(text = 'Material', icon = 'MATERIAL')
grid.label(text = 'Texture', icon = 'TEXTURE')
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 +218,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,19 +240,25 @@ class ExportParams():
('COLLECTION', "Collection", "Export a collection", 'OUTLINER_COLLECTION', 0),
('OBJECT', "Object", "Export an object", 'OBJECT_DATA', 1),
),
)
) # type: ignore
def draw_export_params(self, 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')
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
if self.export_mode == 'COLLECTION':
PROP_ptrprop_resolver.draw_export_collection(layout)
PROP_ptrprop_resolver.draw_export_collection(body)
elif self.export_mode == 'OBJECT':
PROP_ptrprop_resolver.draw_export_object(layout)
PROP_ptrprop_resolver.draw_export_object(body)
def general_get_export_objects(self) -> tuple[bpy.types.Object] | None:
"""
@ -216,18 +274,106 @@ class ExportParams():
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
)
) # type: ignore
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
def draw_virtools_params(self, 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')
if body is None: return
# draw encodings
body.label(text = 'Encodings')
body.prop(self, 'vt_encodings', text = '')
# following field are only valid in exporter
if not is_importer:
body.separator()
body.label(text = 'Global Texture Save Options')
body.prop(self, 'texture_save_opt', text = '')
body.separator()
body.label(text = 'Compression')
body.prop(self, 'use_compress')
if self.use_compress:
body.prop(self, 'compress_level')
def draw_virtools_params(self, layout: bpy.types.UILayout) -> None:
layout.label(text = 'Encodings')
layout.prop(self, 'vt_encodings', text = '')
def general_get_vt_encodings(self) -> tuple[str]:
# get encoding, split it by `;` and strip blank chars.
encodings: str = self.vt_encodings
return tuple(map(lambda x: x.strip(), encodings.split(';')))
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,
) # 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')
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')
body.label(text = f'Map Sectors: {map_info.mSectorCount}')
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

@ -184,6 +184,42 @@ class BallanceObjectInfo():
#endregion
#region Sector Extractor
_g_RegexBlcSectorGroup: re.Pattern = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
def extract_sector_from_name(group_name: str) -> int | None:
"""
A convenient function to extract sector index from given group name.
This function also supports 999 sector plugin.
Not only in this module, but also in outside modules, this function is vary used to extract sector index info.
Function return the index extracted, or None if given group name is not a valid sector group.
The valid sector index is range from 1 to 999 (inclusive)
"""
regex_result = _g_RegexBlcSectorGroup.match(group_name)
if regex_result is not None:
return int(regex_result.group(1))
else:
return None
def build_name_from_sector_index(sector_index: int) -> str:
"""
A convenient function to build Ballance recognizable sector group name.
This function also supports 999 sector plugin.
This function also is used in this module or other modules outside.
Function return a sector name string. It basically the reverse operation of `extract_sector_from_name`.
"""
if sector_index == 9:
return 'Sector_9'
else:
return f'Sector_{sector_index:0>2d}'
#endregion
#region Naming Convention Declaration
_g_BlcNormalComponents: set[str] = set((
@ -227,7 +263,6 @@ _g_BlcWood: set[str] = set((
))
class VirtoolsGroupConvention():
cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + ')_(0[1-9]|[1-9][0-9])_.*$')
cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_TwoFlames_(0[1-7])$')
cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_Resetpoint_(0[1-8])$')
@ -255,9 +290,9 @@ class VirtoolsGroupConvention():
counter: int = 0
last_matched_sector: int = 0
for i in gps:
regex_result = VirtoolsGroupConvention.cRegexGroupSector.match(i)
regex_result: int | None = extract_sector_from_name(i)
if regex_result is not None:
last_matched_sector = int(regex_result.group(1))
last_matched_sector = regex_result
counter += 1
if counter != 1: return None
@ -364,12 +399,12 @@ class VirtoolsGroupConvention():
gp.add_group('Shadow')
case BallanceObjectType.RAIL:
gp.add_group('Phys_FloorRails')
gp.add_group('Sound_HitID_02')
gp.add_group('Sound_RollID_02')
case BallanceObjectType.WOOD:
gp.add_group('Phys_Floors')
gp.add_group('Sound_HitID_03')
gp.add_group('Sound_RollID_03')
case BallanceObjectType.WOOD:
gp.add_group('Phys_Floors')
gp.add_group('Sound_HitID_02')
gp.add_group('Sound_RollID_02')
case BallanceObjectType.STOPPER:
gp.add_group('Phys_FloorStopper')
@ -377,12 +412,8 @@ class VirtoolsGroupConvention():
# group into component type
# use typing.cast() to force linter accept it because None is impossible
gp.add_group(typing.cast(str, info.mComponentType))
# group to sector
if info.mSector == 9:
gp.add_group('Sector_9')
else:
gp.add_group(f'Sector_{info.mSector:0>2d}')
gp.add_group(build_name_from_sector_index(typing.cast(int, info.mSector)))
case _:
if reporter is not None:
@ -460,9 +491,9 @@ class YYCToolchainConvention():
case BallanceObjectType.LEVEL_END:
return 'PE_Balloon_01'
case BallanceObjectType.CHECKPOINT:
return f'PR_TwoFlames_{info.mSector:0>2d}'
return f'PC_TwoFlames_{info.mSector:0>2d}'
case BallanceObjectType.RESETPOINT:
return f'PC_Resetpoint_{info.mSector:0>2d}'
return f'PR_Resetpoint_{info.mSector:0>2d}'
case BallanceObjectType.DEPTH_CUBE:
return 'DepthCubes_'

View File

@ -189,6 +189,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. "),
@ -227,12 +233,9 @@ def virtools_name_regulator(name: str | None) -> str:
## Default Encoding for PyBMap
# Use semicolon split each encodings. Support Western European and Simplified Chinese in default.
g_PyBMapDefaultEncoding: str
if sys.platform.startswith('win32') or sys.platform.startswith('cygwin'):
# See: https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers
g_PyBMapDefaultEncoding = "1252;936"
else:
# See: https://www.gnu.org/software/libiconv/
g_PyBMapDefaultEncoding = "CP1252;CP936"
# Since LibCmo 0.2, the encoding name of LibCmo become universal encoding which is platfoorm independent.
# So no need set it according to different platform.
# Use universal encoding name (like Python).
g_PyBMapDefaultEncoding: str = 'cp1252;gb2312'
#endregion

View File

@ -1,16 +1,3 @@
bl_info = {
"name": "Ballance Blender Plugin",
"description": "Ballance mapping tools for Blender",
"author": "yyc12345",
"version": (4, 0),
"blender": (3, 6, 0),
"category": "Object",
"support": "COMMUNITY",
"warning": "Please read document before using this plugin.",
"doc_url": "https://github.com/yyc12345/BallanceBlenderHelper",
"tracker_url": "https://github.com/yyc12345/BallanceBlenderHelper/issues"
}
#region Reload and Import
# import core lib
@ -29,7 +16,8 @@ from . import UTIL_icons_manager
UTIL_icons_manager.register()
# then load other modules
from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_group, PROP_ballance_element, PROP_bme_material
from . import 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
@ -214,9 +202,11 @@ def register() -> None:
PROP_virtools_material.register()
PROP_virtools_texture.register()
PROP_virtools_mesh.register()
PROP_virtools_light.register()
PROP_virtools_group.register()
PROP_ballance_element.register()
PROP_bme_material.register()
PROP_ballance_map_info.register()
OP_IMPORT_bmfile.register()
OP_EXPORT_bmfile.register()
@ -275,9 +265,11 @@ def unregister() -> None:
OP_EXPORT_bmfile.unregister()
OP_IMPORT_bmfile.unregister()
PROP_ballance_map_info.unregister()
PROP_bme_material.unregister()
PROP_ballance_element.unregister()
PROP_virtools_group.unregister()
PROP_virtools_light.unregister()
PROP_virtools_mesh.unregister()
PROP_virtools_texture.unregister()
PROP_virtools_material.unregister()

View File

@ -0,0 +1,81 @@
# Full context are copied from https://docs.blender.org/manual/en/dev/extensions/getting_started.html
# Please note any update of this manifest
schema_version = "1.0.0"
# Example of manifest file for a Blender extension
# Change the values according to your extension
id = "bbp_ng"
version = "4.1.0"
name = "Ballance Blender Plugin"
tagline = "The specialized add-on served for creating game map of Ballance"
maintainer = "yyc12345 <yyc12321@outlook.com>"
# Supported types: "add-on", "theme"
type = "add-on"
# Optional link to documentation, support, source files, etc
website = "https://github.com/yyc12345/BallanceBlenderHelper"
# Optional list defined by Blender and server, see:
# https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html
tags = ["Object", "Mesh", "UV", "Import-Export"]
blender_version_min = "4.2.0"
# # Optional: Blender version that the extension does not support, earlier versions are supported.
# # This can be omitted and defined later on the extensions platform if an issue is found.
# blender_version_max = "5.1.0"
# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix)
# https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html
license = [
"SPDX:GPL-3.0-or-later",
]
# Optional: required by some licenses.
# copyright = [
# "2002-2024 Developer Name",
# "1998 Company Name",
# ]
# Optional list of supported platforms. If omitted, the extension will be available in all operating systems.
platforms = ["windows-x64", "linux-x64"]
# Supported platforms: "windows-x64", "macos-arm64", "linux-x64", "windows-arm64", "macos-x64"
# Optional: bundle 3rd party Python modules.
# https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html
# wheels = [
# "./wheels/hexdump-3.3-py3-none-any.whl",
# "./wheels/jsmin-3.0.1-py3-none-any.whl",
# ]
# Optional: add-ons can list which resources they will require:
# * files (for access of any filesystem operations)
# * network (for internet access)
# * clipboard (to read and/or write the system clipboard)
# * camera (to capture photos and videos)
# * microphone (to capture audio)
#
# If using network, remember to also check `bpy.app.online_access`
# https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access
#
# For each permission it is important to also specify the reason why it is required.
# Keep this a single short sentence without a period (.) at the end.
# For longer explanations use the documentation or detail page.
[permissions]
# network = "Need to sync motion-capture data to server"
files = "Import/export Virtools file from/to disk"
# clipboard = "Copy and paste bone transforms"
# Optional: build settings.
# https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build
[build]
paths_exclude_pattern = [
"__pycache__/", # Python runtime cache
".style.yapf", # Python code style
"*.gitkeep", # Git directory keeper
".gitignore", # Git Ignore File
"*.md", # Useless document.
"/raw_jsons", # Raw JSONs.
"/raw_icons", # Raw Icons.
"/tools", # Assistant tools.
]

View File

@ -43,11 +43,11 @@
"params": {
"length": "length",
"height": "height",
"face": "(face[0], face[1], face[3], face[2], face[5], False)",
"face": "(face[0], face[1], face[2], face[3], face[5], False)",
"is_sink": "is_sink",
"is_ribbon": "False"
},
"transform": "move(length, 5, 0) @ rot(0, 0, 180)"
"transform": "move(0, 5, 0) @ scale(1, -1, 1)"
}
]
},

View File

@ -147,7 +147,7 @@
"skip": "False",
"params": {
"height": "height",
"face": "(face[0], face[1], face[5], False, face[2], face[5])"
"face": "(face[0], face[1], face[5], False, face[2], face[3])"
},
"transform": "move(0, 5 + width, 0) @ rot(0, 0, -90)"
},
@ -156,7 +156,7 @@
"skip": "False",
"params": {
"height": "height",
"face": "(face[0], face[1], face[2], face[5], face[4], False)"
"face": "(face[0], face[1], face[2], face[3], face[4], False)"
},
"transform": "ident()"
},
@ -166,7 +166,7 @@
"params": {
"height": "height",
"length": "width",
"face": "(face[0], face[1], False, False, face[2], face[5])"
"face": "(face[0], face[1], False, False, face[2], face[3])"
},
"transform": "move(0, 2.5 + width, 0) @ rot(0, 0, -90)"
}

View File

@ -15,7 +15,7 @@ def resize_image(src_file: str, dst_file: str) -> None:
def create_thumbnails() -> None:
# get folder path
root_folder: str = os.path.dirname(os.path.dirname(__file__))
root_folder: str = common.get_plugin_folder()
# prepare handler
def folder_handler(src_folder: str, dst_folder: str) -> None:

View File

@ -14,7 +14,7 @@ def compress_json(src_file: str, dst_file: str) -> None:
def create_compressed_jsons() -> None:
# get folder path
root_folder: str = os.path.dirname(os.path.dirname(__file__))
root_folder: str = common.get_plugin_folder()
# prepare handler
def folder_handler(src_folder: str, dst_folder: str) -> None:

View File

@ -1,4 +1,12 @@
import os, typing
import os, typing, fnmatch, shutil
def get_plugin_folder() -> str:
"""
Get the absolute path to plugin root folder.
@return The absolute path to plugin root folder.
"""
return os.path.dirname(os.path.dirname(__file__))
def relative_to_folder(abs_path: str, src_parent: str, dst_parent: str) -> str:
"""

2
docs/.gitignore vendored
View File

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

View File

@ -0,0 +1,29 @@
# Ballance Properties
The Ballance attributes are distinct from the Virtools attributes, which are a set of attributes dedicated to Ballance mapping. These properties are hosted in the scene and do not change within the same scene (mapping does not involve scene switching in Blender). The panels related to the Ballance property can be found in the `Scene` properties panel, as shown below, and they are:
* `Ballance Elements` panel (red arrows), corresponding to Ballance elements
* `BME Materials` panel (green arrow), corresponds to BME materials.
* `Ballance Map` panel (blue arrow), which corresponds to the Ballance map information.
Among these, only the Ballance Map information is the one you need to focus on, the other attributes are not normally of interest, except in the case of errors in some of the materials or meshes in the map.
![](../imgs/ballance-properties.png)
## Ballance Map Information
Ballance Map infomation currently has only one option, Sector (map sector count). This attribute indicates the final desired number of sectors for the current map. This attribute is mainly used to work around the bug of exporting map, see the [Import and Export Virtools Document](./import-export-virtools.md) section for details on the bug.
The only thing you need to do is to check if this field is the number of sectors you expect before exporting the final map. Note that although you can set this field at the beginning of creating your map, there are other features in BBP that may modify this field, such as adding elements, importing Virtools documents, etc. For example, if you have a map with sector 3 specified and you are adding an element that belongs to sector 4, this value will automatically increase to 4. Similarly, when importing a Ballance map with a total of 4 sectors, this value will also increase to 4 (if the previous value was less than 4). The main reason for doing this is so that users can use this plugin without perceiving this value, especially when making slight modifications to some existing maps. Doing so, however, may not meet the user's needs in some cases, so it is still recommended that you check this field before exporting.
## Ballance Elements
Ballance Elements keep a record of the meshes of all the elements you have added using the BBP Add Elements feature. Meshes generally take up the largest amount of data in a 3D file, so reducing the number of meshes, i.e. by sharing meshes between objects of the same shape, can drastically reduce the size of the map file. When you use BBP to add an element-related function, it will first try to get the element's mesh from here, and if there is no corresponding mesh, it will be loaded and recorded here.
This panel is for viewing only, not editing. If you have accidentally modified the meshes of a BBP-added element (which should not have been modified in the first place) and want to restore its original shape, just click `Reset Ballance Elements` to reset all the element meshes in the list to their correct state.
## BME Materials
Similar to the Ballance Elements, records the materials used when adding floor using BME. This is also designed so that materials can be reused, so that you don't need to create a set of materials associated with each object you create, greatly reducing the number of duplicate materials.
This panel is for viewing only, not editing. When you accidentally modify BME related materials (which should not have been modified) and want to restore them, just click `Reset BME Materials` to reset all materials in the list to their correct state.

View File

@ -1,4 +1,33 @@
# Add Floor
!!! info "Work in Progress"
This part of manual still work in progress.
## Start Generating
In the 3D view, click `Add - Floors` to expand the Add Floors menu. The menu is shown below.
![](../imgs/bme-adder.png)
Click on the menu to see all supported floor types in the submenu that pops up. Their names and icons hint at the style and shape of the floor it is intended to create.
!!! info "BME is extensible"
BME's floor adder is extensible, each item in the menu is actually described by a set of JSON data. You can read the [Technical Information](./tech-infos.md) section to learn how we write this JSON, and you can even expand the types of floors that BME can create to suit your needs.
## Configure Floor
Clicking on one of the floor types will open the floor creation dialog, here we are showing a Normal Platform as shown below. In the dialog, we can configure various properties of this floor type, such as the length, width, height, distance, and whether the surface is displayed or not, to customize the geometry it generates so that it meets our requirements.
![](../imgs/bme-adder-dialog.png)
In the Normal Platform dialog, we can first see that it asks us to provide the length and width of the floor, which determines the size of our platform, and there is a text description to help you understand what this property controls.
Then it also asks us to provide the height of the platform, which defaults to 5, which is the default height of the floor in Ballance. Anything less than 5 creates a thin floor similar to the one in the "The Devil Dragon" map, and anything greater than 5 creates a very high floor wall similar to the one in the "Exaggeratedly Dense Space Station" map.
Finally, it tells us which sides of the floor we need to configure to display. Note that Top and Bottom are the top and bottom surfaces along the height direction (Z axis), while Front, Back, Left, and Right are the front, back, left, and right surfaces when looking down with your head on the -X axis and your eyes on the -Z axis. You may notice that there is a perspective cube in the center of these six face buttons, and in fact the positions of these six face options correspond to the positions of the six faces of this perspective cube.
## Tips
Each floor type has a different number of configuration entries, so for different floor types, you will need to follow the configuration hint text to understand what the corresponding configuration does. Some floor types may have a large number of configuration entries, while others may have no configuration entries at all.
The default values for the floor type configuration are set to the values that were most commonly used when the floor was created. The values are reset to the defaults each time the floor type is switched or recreated.

View File

@ -1,4 +1,41 @@
# Compile and Distribute Plugin
!!! info "Work in Progress"
This part of manual still work in progress.
This page will guide you in compiling the plugin as well as distributing it.
## Compiling LibCmo with BMap
BBP's Virtools file native import/export functionality relies on BMap and its Python binding PyBMap. In order to distribute the plugin, we need to first compile BMap and its predecessor LibCmo, and before doing so, you need to check the version of BMap you need. Because BBP doesn't always use the latest version of BMap, e.g. if you're compiling an older version of BBP, it's obviously not possible to rely on the latest version of BMap. BMap is constantly being upgraded, and the functionality it provides is constantly changing, and different versions of BMap are incompatible. BBP usually states the version of BMap it uses at the time of release, but if BBP doesn't point it out, you may need to look for the most recent version of BMap that compiles with the version of BBP at the time of its release.
After specifying the version, you need to visit [LibCmo GitHub repository](https://github.com/yyc12345/libcmo21). Then clone the project and use the Git command to go to the corresponding version (or just download the source code of the corresponding version). Then follow LibCmo's compilation manual to compile to get BMap. on Windows, you'll usually get the files `BMap.dll` and `BMap.pdb`. On Linux, it will be `BMap.so`.
Then we need to configure PyBMap, which comes with LibCmo. Please follow the manual of PyBMap to combine the compiled binary BMap library with PyBMap. That is to complete the PyBMap configuration.
Then we need to copy the configured PyBMap to our project under `bbp_ng/PyBMap` to complete this step.
## Generate Thumbnails and Compress JSON
BBP comes with a built-in set of custom icons, as well as the JSON files needed by its component BME to describe the structure. By batch generating thumbnails and compressing JSON operations, the size of these parts can be reduced, making them suitable for loading in Blender and easier to distribute.
Go to the `bbp_ng/tools` folder and run `python3 build_icons.py` which will batch generate thumbnails (this requires the PIL library, please install it via pip in advance). It actually generates thumbnails from the original images in the `bbp_ng/raw_icons` directory and stores them in the `bbp_ng/icons` folder. Running `python3 build_jsons.py` will compress the JSON, which actually reads, compresses, and writes the raw JSON files from the `bbp_ng/raw_jsons` directory into the `bbp_ng/jsons` folder.
## Packaging
Starting from Blender 4.2 LTS, plugins are packaged using Blender's own packaging feature.
Assuming that the final output file is `redist/bbp_ng.zip`. If you are in the root directory of the project, execute the `blender --command extension build --source-dir bbp_ng --output-filepath redist/bbp_ ng.zip` command in a command line window to finish packaging. Please note `blender` is the executable Blender program.
Blender will package the plugin according to the instructions in `blender_manifest.toml` with the following files excluded:
* `bbp_ng/raw_icons`: raw thumbnail folder.
* `bbp_ng/raw_jsons`: raw JSON folder.
* `bbp_ng/tools`: tools for compiling.
* `bbp_ng/.style.yapf`: code style description file.
* `bbp_ng/.gitignore`: gitignore
* `bbp_ng/icons/.gitkeep`: folder placeholder
* `bbp_ng/jsons/.gitkeep`: folder placeholder
## Generating Help Documentation
Although this project will utilize the GitHub Page feature to provide help documentation, sometimes you may need to provide an offline version of the help documentation, this section will explain how to generate an offline version of the help documentation.
First you need to install `mkdocs` and `pymdown-extensions` via pip. Then go to the `docs` folder and run `mkdocs build --no-directory-urls`. After running the command you get a folder called `site`, which is the help documentation that can be viewed offline.

View File

@ -1,4 +1,59 @@
# Add Component
!!! info "Work in Progress"
This part of manual still work in progress.
In the 3D view, click `Add - Components` to expand the Add Components menu. The menu is shown on the left side of the image below.
![](../imgs/component-adder.png)
The right side of the image above shows the interface for adding some components, which will be described in turn, from top to bottom on the right side are: Add Checkpoint, Add Nong Extra Point, Add Nong Ventilator, Add Ventilator Series, Add Sector Pair.
## General Components
In the Add Components menu, under the `Basic Components` category, you can add general components. For most components, adding an component requires specifying the sector it belongs to, indicating that the component is only active in that sector. However, there are some exceptions:
* PS_FourFlame: the 4-flame platform at the start of the level, which is globally unique and therefore has no sector attribute.
* PE_Ballon: the ship at the end of the level, globally unique and therefore has no sector properties.
* PC_TwoFlames: the checkpoint for the sector, with a sector attribute. However, it should be noted that its sector attribute refers to which sector it is going to check, for example, specifying a sector attribute of 1 means that it is the checkpoint of the first sector, i.e. the start of the second sector, and the second sector will be opened after passing it.
* PR_Resetpoint: the reset point of the sector with the sector attribute. However, it is important to note that the sector attribute indicates which sector it is the respawn point of. It follows that when PC_TwoFlames and PR_Resetpoint appear in pairs, PR_Resetpoint is always labeled 1 greater than PC_TwoFlames.
!!! info "Automatic name conflict detection"
A portion of the components have unique names in a Ballance map, e.g., there is and can only be one start point and one ending ship, only one checkpoint and one respawn point can exist in the same sector, etc.
BBP provides a name detection function when creating these components, and if the name already exists, it will be shown in text below when creating to remind users not to create duplicates. As shown in the upper right corner of the display image above as an example, it is trying to add a PC_TwoFlames that already exists and receives a warning.
## Add Nong Components
In the Add Components menu, under the `Nong Components` category, you can add nong components. We only provide two common types of nong components: Nong Extra Point and Nong Ventilator.
### Nong Extra Point
To add a nong extra point, you need to specify the number of nong extra point and the sector number of it. It also will automatically rotate the nong extra point with a slight degree one by one to make the nong extra point look better in the game.
### Nong Ventilator
Nong ventilator are also added by specifying the number of nong ventilators and the sector number of ventilators. The difference is that we have provided some preset values for constructing nong ventilator that will just blow up wood or stone balls, and if you are not satisfied with these preset values, you can still enter the number yourself.
!!! info "Ventilator arrays are also possible"
Did you know that nong ventilators are also possible by setting the offset to 0 when adding a ventilator series? The nong ventilator creation here is just providing some preset values.
## Add Series Components
In the Add Components menu, under the `Series Components` category, you can add series components (aka. component array). We only provide two types of commonly used series: fTilting Block Series and Ventilator Series.
### Tilting Block Series
Tilting Block Series requires you to provide the number of tilting blocks and the number of tilting blocks, and you can also adjust the spacing between adjacent tilting blocks, the default spacing is taken from the game.
### Ventilator Series
The ventilator series also requires sector number and count of ventilators, however it provides a 3D offset so that you can build a vertical or horizontal ventilator series. The default offset values are taken from the in-game values for vertical ventilator series.
## Add Components Pair
In the Add Components menu, under the `Components Pair` category, you can add pairs of components. Currently, only one type of pairs can be added: Sector Pair.
### Sector Pair
To add a Sector Pair, you need to enter a Sector number and it will automatically generate a pair of the checkpoint and respawn point components for you. For example, if you enter 1, it will automatically generate a 4-flame start point and a respawn point for Sector 1, if you enter 2, it will generate a checkpoint for Sector 1 and a respawn point for Sector 2, and so on.
!!! info "Automatic name conflict detection"
Similar to normal component additions, sector pair additions have the same name conflict detection. As an example, the lower right corner of the image above shows that the sector pair for sector 1 already exists and does not need to be added.

View File

@ -1,4 +1,29 @@
# Configure Plugin
!!! info "Work in Progress"
This part of manual still work in progress.
!!! info "The plugin must be configured first"
Some of the configurations of the BBP plugin are closely related to the use of the plugin, and only when the BBP plugin is correctly configured can the full functionality of the BBP plugin be used.
**Whether installing for the first time or updating**, reconfiguring the plugin is essential to ensure that the settings are correct.
## Open the Configuration Panel
Open Blender, select `Edit - Preferences`, in the window that opens go to the `Add-ons` tab and find the BBP plugin in the list. Its name is `Ballance Blender Plugin`. Make sure that the checkbox next to the name is checked, which means that the plugin is enabled. Click on the triangular arrow to the left of the checkbox to expand the plugin details to enter the configuration panel as shown in the figure.
![](../imgs/config-plugin.png)
## Start to Configure
The BBP plugin currently has 2 settings to configure.
### External Texture Folder
Please fill in the `Texture` directory of Ballance, from which the plugin will use the external texture files (i.e. the ones Ballance originally came with). Click on the folder button on the right to browse the folders and select it.
This is crucial for BBP to work properly, and only if it is filled out correctly will BBP not make errors during operation.
### No Component Collection
When importing and exporting BM files, objects that are in a collection with this name will be forced to be specified as No Component. leaving this blank means that this feature is not needed. This feature is usually used for forced element model replacement.
!!! warning "This setting is not required at this time"
Since BBP 4.0 supports native import/export of Virtools files, the BM file import/export function is no longer used. Therefore, this field is no longer useful and does not need to be filled in.

View File

@ -1,4 +1,28 @@
# Group Operation
!!! info "Work in Progress"
This part of manual still work in progress.
## Select by Group
`Ballance - Select by Virtools Group` provides a feature to filter by Virtools grouping data.
The feature starts with 5 different selection strategies that match Blender's selection methods exactly (Set, Extend, Subtract, Invert, Intersect). Simply use it like a Blender selection. Then, select the name of the group you need and start a selection or filter.
!!! note "About pattern selection"
If you can, use the Subtract or Intersect modes whenever possible. Because this avoids analyzing too many objects. For example, selecting a general range first and then filtering using Intersect mode is more efficient than using Set mode directly.
## Quick Grouping
The BBP plugin adds the ability to quickly group objects in 2 places. The first is the object context menu: you can select a series of objects and right click to find the quick grouping feature in the object context menu. Second is the Objects menu in Outline view: you can right-click on a selection of objects in the Outline view to find the quick grouping feature. Both menus are shown below.
![](../imgs/grouping.png)
### Group into...
Groups the selection into the group of your choice.
### Ungroup from...
Ungroups the selection from the group of your choice.
### Clear All Groups
Clear all groups for the selection. You will be asked to confirm this before executing to avoid misbehavior.

View File

@ -1,4 +1,58 @@
# Import and Export Virtools Document
!!! info "Work in Progress"
This part of manual still work in progress.
!!! warning "This is experimental content"
Native importing and exporting of Virtools documents is experimental content for the BBP plugin, it may have many problems, see the [Report Issue](./report-bugs.md) section to learn more. When problems are encountered, please report them. the authors of the BBP plugin are not responsible for any consequences resulting from problems with the BBP plugin.
## Import Virtools File
Virtools files can be imported by clicking `File - Import - Virtools File`. Importing supports CMO, VMO and NMO files. Clicking on it will bring up the file opening window and show the import settings in the sidebar. First of all, you need to select the Virtools file to be imported, and then configure the import settings in the sidebar. After configuring the import settings, you can click Import to start the import, and wait for the status bar at the bottom of Blender to indicate that the import is complete.
### Conflict Options
The Conflict Options section indicates what to do when the importer encounters duplicate object names. There are 4 levels, for Object, Mesh, Material and Texture. There are 2 ways to handle it: Rename and Use Current. When Rename is selected and a duplicate name is encountered, a suffix will be added to the name to make it unique. By choosing Use Current, the import of the item from the file will be ignored and the item with the same name will be used instead, which already exists in the Blender document.
!!! info "Differences from Virtools conflict resolution"
Compared to the conflict resolution dialog in Virtools, the conflict resolution options provided by the BBP plugin do not support replacement, and the granularity is not fine-tuned to individual instances, but only for an entire type. So you can't set a different conflict resolution for each instance of a conflict individually. However, this setting is sufficient for most scenarios.
The default values for the options in the Conflict Options section are the solutions that are usually selected for import. Of course, special settings are needed for special import situations, e.g. if you are importing an externally exported element model from the original version, you may be able to use Use Current option in material options instead of making a copy. The correct use of the conflict options is a matter of mapping experience and is not taught in this manual.
### Virtools Params
It is well known that Virtools uses a system-based multi-byte character encoding to process documents, and is therefore prone to what is known as garbling; Blender itself does not suffer from garbling, however, if we do not read a Virtools document with the correct encoding, the characters stored in it may still appear garbled when the Virtools document is imported into Blender. The Encodings property in the Virtools Params section specifies the encodings for reading Virtools documents. Multiple encodings can be specified, separated by a `;` (semicolon). Some common encodings are listed below:
* cp1252: Western European encoding used by Ballance.
* gb2312: The default encoding for Chinese Windows system.
The encoding attribute is very important. If the wrong encoding is set, the names of the various objects imported into Blender will be unrecognizable, or will cause the program to make an error.
!!! info "What encodings are available?"
Since BBP version 4.1, the names of the encodings we use are basically just copied from the Python encoding names. Most of the commonly used encoding names in Most of the commonly used encoding names in Python are mapped, with only a few particularly rare encodings unsupported, and for specific supported encodings it is necessary to check the source code. See [Python documentation](https://docs.python.org/3/library/codecs.html#standard-encodings) for information on Python's supported encodings. Encodings are not case-sensitive.
!!! warning "Warning about migration from older versions"
Starting with BBP version 4.1, the version number of LibCmo, the underlying library used by BBP's Virtools document import module, has been bumped to 0.2. Before this version, the encoding attribute was a platform-dependent setting. Under Windows, the [Windows Code Page](https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers) number is required here. Under other operating systems, LibCmo uses iconv for character encoding decoding, so the legal [iconv encoding identifier](hhttps://www.gnu.org/software/libiconv/) is required.
This all changed with LibCmo 0.2, from which LibCmo uses Python-like universal encoding names. It is platform-independent, you no longer need to check whether the operating system you are using is Windows or Linux, the encoded characters are the same string for all platforms. This also means that if you have customized your encoding settings before, you need to be careful to convert them to the new universal encoding name, because the old encoding name may not have a corresponding mapping under the universal encoding name system, for example, `1252` specified on Windows before should be written as `cp1252` under the new universal encoding name, and the original encoding name won't be recognized correctly on the new system.
## Export Virtools File
Virtools files can be exported by clicking `File - Export - Virtools File`. Clicking on it will bring up the file opening window and show you the export settings in the sidebar. First of all, you need to select the location of the exported Virtools file, then configure the export settings in the sidebar, after configuring the export settings, you can click Export to start the export, and wait for the status bar at the bottom of Blender to indicate that the export is complete.
### Export Target
The Export Target section is used to determine which objects you need to export to a Virtools document. You can choose to export a collection or an object and select the corresponding collection or object below. Note that selecting a collection will export the objects in the internal collection as well, i.e. exporting nested collections is supported.
### Virtools Params
The Virtools Params section is similar to the one in the importing Virtools document; the Encodings property determines the encoding used when exporting a Virtools document.
The Global Texture Save Option determines how textures that are set to Use Global are actually saved. In general, setting it to Raw Data will 100% guarantee that the saved Virtools document will contain the correct texture, but it may be larger, while setting it to External will minimize the size of the file, but there may be problems with the exported document not finding the texture file. We recommend that you specify how each material should be saved individually when you set it up, rather than relying on the global option to set it up. This option is for re-editing old maps that rely on the Global Texture Saving Option. It should also be noted that even though there is a Use Global option in this option, please **don't** select it or it will result in an error, because obviously you can't have a global option that then uses the global option's settings.
The Use Compress property specifies whether saved documents are stored compressed. Compression can significantly reduce the size of a document, and on modern computer platforms, the performance loss caused by compression is almost negligible. When Use Compress is selected, an additional Compress Level attribute is displayed, which specifies the level of compression; the higher the value, the greater the compression rate and the smaller the file.
### Ballance Params
The Ballance Params section contains parameters that optimize the export process for Ballance-specific content.
Successive Sector is an option to work around a bug that occurs when exporting groups of sectors. For some reason, if there are no elements in a sector (actually, no objects are grouped in a sector group), the export plugin thinks that the sector group doesn't exist and misses the export. And since Ballance determines the final sector, i.e. the sector where the spaceships appears, by incrementing the number of sectors from 1 to the last sector group that exists, the combination of the two causes Ballance to incorrectly determine the number of sectors in the map, and thus display the spaceships in the wrong sector, which is an export bug. when this option is checked, the exported document will be pre-defined according to the number of sectors specified in the Ballance Map information in the current Blender file. When this option is checked, the exported document will pre-create all of the sectors according to the number of sectors specified in the Ballance map information in the current Blender file before exporting, so that you don't miss creating some sector groups, and the spaceships will be displayed in the correct sectors.
This option is usually checked when exporting playable maps, if you just want to export some models then you need to turn this option off, otherwise it will create a lot of useless sector groups in the final file.

View File

@ -1,7 +1,7 @@
# Ballance Blender Plugin User Manual
!!! info "May Outdated"
This document has been translated from other languages and may not always be up to date.
!!! info "May be Outdated"
This document is translated from other languages and may not always be up to date.
Welcome to the Ballance Blender Plugin, the user manual for the free and open source Ballance map creation suite.
@ -19,6 +19,7 @@ Therefore, choosing Blender with BBP for mapping is not only choosing freedom an
## Features
* [Virtools Properties](./virtools-properties.md)
* [Ballance Properties](./ballance-properties.md)
* [Import and Export Virtools Document](./import-export-virtools.md)
* [Group Operation](./group-operations.md)
* [Legacy Alignment](./legacy-align.md)

View File

@ -1,4 +1,57 @@
# Install Plugin
!!! info "Work in Progress"
This part of manual still work in progress.
## Determining the Version
The principle of BBP's Blender support is to support the latest **LTS** version, and to spend some time migrating the plugin after the latest LTS version is released. The current plugin version **4.0** is based on Blender version **4.2.x**.
Theoretically, BBP will work fine on other versions of Blender if no major changes have been made. For example you can try to run BBP plugin based on Blender 3.6 LTS on Blender 4.0. However, the developers of BBP do not deal with bugs that only appear in non-LTS versions. before installing the plugin, please select the appropriate version.
## Uninstall the Old Plugin
If you have used BBP before then you need to uninstall it first. Older versions of BBP are usually installed in the following locations:
* `<Blender>/3.6/scripts/addons/ballance_blender_plugin`: BBP 3.0 or lower version.
* `<Blender>/3.6/scripts/addons_contrib/ballance_blender_plugin`: BBP 3.0 or lower version.
* `<Blender>/3.6/scripts/addons/bbp_ng`: BBP 4.0 internal test version
* `%APPDATA%/Blender Foundation/Blender/3.6/scripts/addons/bbp_ng`: BBP 4.0 internal test version
* `%APPDATA%/Blender Foundation/Blender/4.2/extensions/user_default/bbp_ng`: BBP 4.0 or higher version
You just need to disable the plugin in Blender first (uncheck the box in front of the plugin name) and then delete these folders (if they exist) to uninstall the plugin completely. The `<Blender>` in the path refers to the location of your Blender installation. The `3.6` and `4.2` in the path are the version numbers of your Blender installation, which need to be adjusted according to the version you have installed, and subsequent occurrences of version numbers should be understood as the same meanings.
!!! warning "Should not use Blender's plugin uninstall feature"
It is not possible to uninstall BBP using the plugin uninstall function on the Blender plugins page, because BBP loads the Virtools file read/write library BMap into Blender as soon as it is loaded by Blender (whether it is enabled or not). if you remove it while Blender is running, you will get an access denied error. Therefore you must manually delete the plugin directory after closing Blender.
If you are really not sure where the plugin is installed, you can find the `File` property in the Addons page of Blender's Preferences, and the folder it points to where the file is located is the folder to be deleted.
!!! info "`ballance_blender_plugin` and `bbp_ng`"
`ballance_blender_plugin` is the module name of the old version of the BBP plugin (before version 4.0) and `bbp_ng` is the module name of the new version of the BBP plugin (after and including version 4.0). Both are provided in order to ensure that the user actually deleted the old version of the plugin.
!!! info "`addons` and `addons_contrib`"
After Blender version 3.6 LTS, i.e. BBP version 3.3, Blender no longer supports Testing type plugins. As a result, the `addons_contrib` folder, which was dedicated to installing Testing plugins, is no longer used, and plugins need to be installed uniformly in `addons`. Both are provided to ensure that the user actually deletes the old version of the plugin.
!!! info "`addons` and `extensions`"
In Blender version 4.2 LTS, Blender uses Extensions instead of Addons to describe plugins. This has resulted in a change in where plugins are installed. Both are provided in order to ensure that the user actually removes the old version of the plugin.
## Download Plugin
You can download the latest plugin via [the Release page of the GitHub codebase for this project](https://github.com/yyc12345/BallanceBlenderHelper/releases). Plugins are provided as ZIP archives.
In addition, you can also get this plugin in the mapping tutorial web disk provided by yyc12345:
* Overworld: [Mega](https://mega.nz/#F!CV5SyapR!LbduTW51xmkDO4EDxMfH9w) (located in `Mapping` directory)
* Chinese Region Only: [Baidu Web Disk](https://pan.baidu.com/s/1QgWz7A7TEit09nPUeQtL7w?pwd=hf2u) (Extract code: hf2u, located under `制图插件(新)`)
!!! warning “Do not download this repository directly for use”
Please do not download this project's repository directly for use. First of all, because the latest commit is not guaranteed to be stable and available. The second reason is that this project contains some C++ code that needs to be compiled, and must be compiled before it can be used. See [Compile and Distribute Plugin](./compile-distribute-plugin.md) for more information.
## Install Plugin
Open Blender, click `Edit - Preferences`, in the window that opens go to the `Add-ons` tab, click on the arrow at the top right of the window and then click on the `Install from Disk.... ` button, select the ZIP archive you just downloaded, and the installation will be completed. If you don't see it in the list you can click the Refresh button or restart Blender.
You can also choose to install the plugin manually (if the above installation method fails), go to `%APPDATA%/Blender Foundation/Blender/4.2/extensions/user_default`, create a folder named `bbp_ng` and go inside it, extract the downloaded ZIP archive to this folder, start Blender and you will find BBP in the list of addons.
The name of BBP plugin in the list is `Ballance Blender Plugin`, when you find it, you can enable the plugin by checking the box on the left side of the name. The Preferences window after the plugin is installed is shown below.
![](../imgs/config-plugin.png)
After **installing or updating** the plugin, be sure to [configure plugin](./configure-plugin.md) before using it, see the next section for details.

View File

@ -1,4 +1,28 @@
# Legacy Alignment
!!! info "Work in Progress"
This part of manual still work in progress.
`Ballance - 3ds Max Align` provides an alignment function similar to the way alignment works in 3ds Max.
The so-called legacy alignment feature is a perfect reimplementation of the 3ds Max alignment operations in Blender. It makes it possible for many mappers who switch from 3ds Max to Blender to get up to speed faster and provides some convenient alignment operations. The image below shows legacy alignment in action.
![](../imgs/legacy-align.png)
## Usgae
Legacy alignment supports aligning multiple objects to a single object by first selecting the objects to be aligned in turn, then selecting the reference object at the end of the alignment (i.e. making it the active object), and then clicking `Ballance - 3ds Max Align` to bring up the legacy alignment panel, after which you can start the alignment operation.
## Introduction of the Panel
In the panel, `Align Axis` specifies the axis you want to align to, you can multi-select here to specify more than one axis, without specifying any axis you will not be able to do the alignment operation, and thus you will not be able to click the `Apply` button.
`Current Object` is the alignment reference object, which is the active object in the scene, usually the last object you selected. This option specifies what value you need to reference for alignment, with `Min` (minimum value on axis), `Center (Bounding Box)` (center of the bounding box), `Center (Axis)` (origin of the object), and `Max` (maximum value on axis) available. These options are consistent with the alignment options in 3ds Max.
The `Target Objects` are the objects that are being aligned, there may be many of them, in this option it is also specified what values you need to refer to them for alignment. The options have the same meaning as `Current Object`.
The `Apply` button, when clicked, will press the current page's configuration into the operation stack and reset the settings above, allowing you to start a new round of alignment operations without having to perform a legacy alignment again. The number of operations in the stack is shown below the `Apply` button.
!!! info "What the Apply button does"
Understanding this part is not useful for mapping, and you don't need to read what's in this box unless you're interested.
By design, Blender doesn't support so-called "operations inside the Operator", but with a few tricks we simulated Apply effect similar to the one in 3ds Max.
The Apply button is actually a specially displayed BoolProperty that listens to its value change event and, while avoiding recursive calls, records the current setting in a hidden CollectionProperty and resets its own value and displayed properties to make a visual "apply". Operator processes the alignment requirements accumulated in the CollectionProperty in turn when executing.

View File

@ -1,4 +1,29 @@
# Naming Convention
!!! info "Work in Progress"
This part of manual still work in progress.
## Auto Grouping and Renaming
In the outline view, right-click on any collection to get the Auto Grouping and Renaming menu.
![](../imgs/naming-convention.png)
This plugin currently supports two naming convention.
One is the Mapping Toolchain Standards described in the [Technical Information](./tech-infos.md) section, which is named `YYC Tools Chains` in this plugin.
The second is the naming standard used by [Imengyu/Ballance](https://github.com/imengyu/Ballance), named `Imengyu Ballance` in this plugin.
These functions will ultimately only show a generalized message of success or failure. If you need to see in detail why a certain object is not converted, click `Window - Toggle System Console` and the plugin has more detailed output there.
### Rename by Group
Renames an object to an appropriate name based on its current grouping information.
This is often used when migrating original maps. Some Ballance-derived programs do not have a Virtools Group concept and therefore rely on names for grouping information.
### Convert Name
Switch between different naming standards.
Typically used to convert between different Ballance-derived programs.
### Auto Grouping
Auto-fill the grouping information for an object according to a given naming convention.
Note that the original grouping information will be overwritten.
If you follow certain naming conventions during the mapping process, this feature can do the grouping for you automatically.

View File

@ -1,4 +1,81 @@
# Add Rail
!!! info "Work in Progress"
This part of manual still work in progress.
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)
The right side of the picture above shows the interface for adding some rails, and we will introduce them in turn later, from top to bottom on the right side are: Add Rail Section, Add Straight Rail, Add Side Rail, Add Arc Rail, and Add Spiral Rail.
!!! info "Rails with non-standard data"
BBP's rail adding menu is designed for new players to add rails quickly, and is not designed for skilled mapper to add rails. For cases where you need rails with non-standard data, such as rails with non-standard spacing or non-standard section, you need to build the rail section with Blender's own Circle operation, and then generate the entire rail with the Extrude, Bridge, or Spiral modifiers. In this way, you can control all the parameters of each step to meet your specific needs for rail parameters.
!!! info "Source of the rail parameters"
The various parameters of rails used in the Rails Add menu are derived from actual in-game measurements and from the experience of several mappers in the Ballance community over more than a decade.
The rail section radius and gauge are derived from years of mapping experience and measurements. Side rail tilt degree data is calculated by BallanceBug. Mono and double rail transition sinking data from the calculation by Imbalanced Dream. Spiral rail spacing is measured from Level 9 and Level 13.
## Rail Sections
In the Add Rails menu, under the `Sections` category, you can add rail sections. A rail section is the outline of a rail, and the creation of a rail section is usually the first step in the creation of various types of shaped rails, e.g. rails made by lofting, extruding etc.
### Rail Section
To create a rail section, you can choose in the panel to create a mono or double rail section.
When creating a mono rail section, the octagonal rail section is automatically created with the flat side edge facing up, while the double rail section is not (vertex tip facing up). If you need to change this behavior, you need to go into edit mode after creation and manually rotate the vertices of the rail section so that the flat side or vertex tip of the rail section faces up.
### Transition Section
The mono and double rail transition section will create a rail section that is suitable for mono and double rail transitions. This rail section is created without specifying any parameters.
## Straight Rails
In the Add Rails menu, under the `Straight Rails` category, you can add straight rails.
### Straight Rail
A Straight Rail is a straight rail of rail. To create a straight rail you need to specify a Length. You can also choose to create a straight double rail or a mono rail.
When creating a mono rail, similar to a section, the flat side of the rail section is automatically turned upwards, and this behavior can be modified by entering edit mode after creation and then rotating.
The creation of straight rails also supports the capping properties, which are controlled by the Start Cap and End Cap options located under Rail Cap, which are checked to cap the corresponding end. Capping is the process of automatically adding face for the end of a rail and correctly handling its normal. This is usually done to ensure that the rail is aesthetically pleasing where it comes into contact with other surfaces or objects, rail-to-rail joints do not need to be capped.
### Transition Rail
Mono and double transition rail can often be seen as an advanced use of Transition Section creation, where the section created by the transition section is extruded and the normals are taken care of to produce the result created by this option. To create a mono or double transition rail you need to specify a Length, which is also supported by the capping property.
### Side Rail
Side rails are created by specifying a Side Type which can be either Normal (for paper or wood balls) or Stone Specific (for stone ball specific). Side rails for paper and wood balls are the usual side rails that stone balls cannot pass through. Stone Specific side rails are side rails that are more tilted and can be passed by stone balls, and of course, paper and wood balls.
In addition to the side rail type, side rail creation also requires the Length and capping attributes.
## Curve Rails
In the Add Rails menu, under the `Curve Rails` category, you can add curved rails.
### Arc Rail
The first thing you need to do with an arc rail is to specify the Angle and Radius, which indicates how much the rail will turn at what radius. Generally speaking, angles of 90, 180, and 270 degrees are more common, but any number of degrees can be specified. The radius is usually adjusted as needed. For the double rail arc, the radius is the distence between arc rail rotation center to the double rail section center. For the mono rail arc rail, is the distence between arc rail rotation center to the mono rail section center.
Steps of the arc rail, the number of steps indicates the number of segments of the arc rail, the larger the number, the smoother the arc rail looks, relatively, the vertices will be more, the storage space and rendering requirements are also higher, so you need to choose a reasonable value.
Arc rails also support double-rail mono-rail selection, you can create mono-rail arc rails and double-rail arc rails. The capping attribute is also supported.
### Spiral Rail
Spiral rail, or spiral double rail, is similar to arc rail in that you need to specify Radius, which is the radius of rotation, but not the angle, since it always rotates in 360 degrees.
Spiral rails have an Iterations property, which indicates how many times the rail will spiral up, and a Screw property, which indicates the distance between each iteration.
The spiral rail also needs to set the Steps property, which has the same meaning as the arc rail. However, it should be noted that the number of steps refers to the number of steps in each iteration, not the overall number of steps. Therefore, when adjusting the iteration attribute, you do not need to change the Steps attribute again.
Side Spiral Rail also has a capping property.
### Side Spiral Rail
Side Spiral Rail, similar to spiral rail, but the ball is rolled along the side, similar to side rail.
Side Spiral Rail does not have a Screw property, because Side Spiral Rail is designed so that adjacent spins share a common edge, so the screw is fixed.
The Radius, Iterations and Steps attributes in the Side Spiral Rail settings have the same meaning as the spiral rail. Side Spiral Rail also have a capping attribute.

View File

@ -1,4 +1,29 @@
# Report Issue
!!! info "Work in Progress"
This part of manual still work in progress.
## What Can Go Wrong
BBP is not perfect, and since BBP's Virtools file import/export module is written in C++, BBP is more prone to errors than other plugins, and the consequences of errors can be more serious (including but not limited to memory leaks, accidental deletion of user files, etc.).
In Blender, you will observe if the plugin execution goes wrong:
* The expected effect is not achieved
* A large stack of output text pops up at the mouse that you can't read
* After opening the console using `Window - Toggle System Console`, you can observe the Python exception output.
## What Part Went Wrong
For the BBP plugin, if you observe something like `BMap operation failed` in the Python exception output, or the `IronPad.log` file in the `<Plugin-Install-Location>/PyBMap` folder, it means that The BBP plugin's BMap section, written in C++, is in error, and **you need to immediately save your current Blender document and exit Blender**. Because the plugin is in an abnormal state at this point, you should not continue any operations.
If there is no such thing as the above, then this is just a normal Python code execution error and you don't need to worry too much about it, but the error is still fatal and it is recommended to exit Blender and report the error after doing all the necessary operations.
## Where to Report
If you have a GitHub account, you can create and report issues in [Issue page of BBP repository](https://github.com/yyc12345/BallanceBlenderHelper/issues).
If you can't do that and you have proper way to contact with plugin author, then reporting directly to the plugin author is fine.
## The Content of the Report
First of all you need to describe in detail how you raise this error and what are the results of this error. If you can upload the documentation that led to the error, please try to do so (if it's not convenient to post it publicly, you can send it to the author through a private way such as email).
You also need to provide the Python stack report output in the Blender console (use `Window - Toggle System Console` to open the console). If your error is an error in the BMap section, you also need to provide the `IronPad.log` and `IronPad.dmp` files in the `<Plugin-Install-Location>/PyBMap` folder to make it easier for developers to locate the error.

View File

@ -1,4 +1,12 @@
# Technical Information
!!! info "Work in Progress"
This part of manual still work in progress.
* BM File Specification: https://github.com/yyc12345/gist/blob/master/BMFileSpec/BMSpec_ZH.md
* Mapping toolchain standards and format of files in the `meshes' folder: https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md
* Format of the JSON file for BMERevenge: https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_v2.0_ZH.md
This plugin works with the `fake-bpy-module` module to implement type hinting to speed up development. Use the following command to install Blender's type hinting library.
* Blender 3.6: `pip install fake-bpy-module-latest==20230627`
* Blender 4.2: `pip install fake-bpy-module-latest==20240716`
The main reason for doing this is that `fake-bpy-module` doesn't release an official package for the given Blender version, so I had to install it by choosing the daily build closest to the release time of the corresponding Blender version.

View File

@ -1,4 +1,57 @@
# UV Mapping
!!! info "Work in Progress"
This part of manual still work in progress.
## Rail Mapping
The menu `Ballance - Rail UV` in the 3D view provides the ability to map rails.
To start mapping rails, you need to select the rails to be mapped (multiple selections are possible) and then click on this menu. Then you only need to select the material used for the rail. This will clear all materials for the selected object and assign all materials used on each face to the material you specify. Then you will set the UV of the selected object in the same way as the rails are post-processed in Ballance, so that the rails will have the same appearance as in the game by performing this operation.
!!! info "Rails UV doesn't really matter"
In fact, all objects grouped in the `Phys_FloorRails` group undergo a post-processing when Ballance loads the level. What the post-processing method does is to reset the UVs of these objects according to a specific mapping function (`TT_ReflectionMapping` is called in Virtools). So the UVs of the rails don't really matter, because Ballance will set them uniformly for you when you enter the game, and even if you don't set any UV data to the rails, the rails will look correct when you enter the game.
Therefore, this feature is usually applied to set the UVs for objects that are not grouped in the `Phys_FloorRails` group, but you still want them to show the appearance of the rails material. Or for rendering Ballance map previews in Blender, etc.
## Mapping Along Edges
Mapping Along Edges is the most important feature of the BBP plugin, the most powerful and at the same time the most difficult to understand. It is widely used for setting UVs of custom structures or a line of floor, wood, etc. The menu `Ballance - Flatten UV` in the 3D view offers the Mapping Along Edges feature. It only works in **Edit Mode**, so you need to be in Edit Mode (and entering Face selection mode at the same time, since Mapping Along Edges operates on faces) to use it.
Mapping Along Edges is usually used in combination with [Select Face Loops](https://docs.blender.org/manual/en/4.2/modeling/meshes/selecting/loops.html), [Shortest Path](https://docs.blender.org/manual/en/4.2/modeling/meshes/selecting/linked.html#bpy-ops-mesh-shortest-path-select) and other functions. You need to first select a series of faces, for example a series of consecutive Ballance floor sides, and then click `Ballance - Flatten UV` to start mapping along edges.
The configuration screen for Mapping Along Edges is shown below, with Scale Size mode on the left and Ref. Point (refernece point) mode on the right, we'll explain the difference between these two modes later.
![](../imgs/flatten-uv.png)
Mapping Along Edges, as the name implies, means UV mapping along a certain edge. The specific operation is to use linear algebra to transform the 3D coordinates of the vertices under a new coordinate system using a transition matrix. In this new coordinate system:
* The origin is the vertex with the index specified by the Reference Edge, as shown below. The figure below identifies the vertex index of the face in UV and 3D in yellow font, and the Reference Edge is 1 in the figure, then the vertex with the vertex index of 1 in the current face is used as the origin of the new coordinate system.
* The Y-axis (i.e., the V-axis, XYZ corresponds to UVW, and will not be commented subsequently) is the line from the vertex specified by Reference Edge to the next vertex, i.e., the edge from vertex 1 to vertex 2, i.e., the edge with the serial number of 1, as shown in the following figure, which identifies the serial number of the edge of the face in purple font in the following figure in UV and 3D. Edge 1 is a vector that starts at vertex 1 and ends at vertex 2. It has a direction, is a vector, and needs to be normalized when used as a coordinate axis.
* The Z-axis is obtained by cross-multiplying and normalizing the edge specified by Reference Edge (in this case, Edge 1) with its next neighboring edge (in this case, Edge 2). In the case of a three-point covariance where the cross-multiplication yields a zero vector, an attempt is made to use the normal data of the face instead.
* The X-axis is obtained by cross-multiplying and normalizing the previously calculated Y-axis and Z-axis. The XYZ in the new coordinate system still needs to satisfy the requirements of the right-handed coordinate system.
![](../imgs/flatten-uv-mechanism.png)
After setting up the new coordinate system and building the transition matrix and transforming every vertex to the new coordinate system, we can discard the Z component and map XY to UV and mirror the vertices on the -U axis to the +U axis, which is why Flatten UV doesn't support concave polygons. The reason for mirroring is to prevent UV mapping errors, for example, in the above image, the upper edge stripe of the floor side mapping is located in the V-axis, if we flip the UV vertices in the above image along the V-axis (you can try it for better understanding), it will result in the final mapping display does not meet our expectation, i.e., the edges specified by Reference Edge do not show the floor side stripe.
!!! info "Reference Edge actually refers to"
The Reference Edge actually refers to the index of the edge that needs to be mapped to the V-axis. Because this edge is directional, it also determines the origin in the new coordinate system.
In short words, Flatten UV allows the user to specify an edge and then flatten it along the V-axis of the UV.
After determining the coordinate system, we also need to know how far this UV mapping needs to be expanded, specifically what the scaling value of this UV mapping should be. For the V-axis direction, due to the Ballance mapping charcateristic (Ballance mapping always extend along the V-axis, and in Ballance, 5 in 3D is equivalent to 1 in UV), we can know the UV:3D relationship is 1:5. While for the U-axis direction, we can't determine it, that's the work of Scale Mode property, which lets the user decide the scaling of the U-axis direction.
The Scale Size mode allows the user to directly specify the scale value, for example, the default value of 5 means that 5 in the 3D world is equal to 1 in the UV world, while the Ref. Point mode allows the user to specify the U value of the UV of a reference point in the surface, and lets the plugin calculate the scaling on the U-axis by itself. For example, if we switch the Scale Size mode in the image above to Ref. Point mode and specify Reference Point as 2 and Reference Point UV as 1 we can also apply the same texture, let's explain the principle here. First of all, Reference Point specifies the reference point, this reference point is relative to the vertex specified by Reference Edge, that is, the reference system origin of the offset, so here, Reference Edge is 1, Reference Point is 2, then the actual reference point for the vertex 3. Reference Point UV specifies a U value of 1 in the UV value of this point, i.e., it can be seen as forcing vertex 3 to be placed at a U-axis of 1. BBP will calculate the corresponding U-axis scaling value based on this point and apply it to all vertices.
Scale Size mode is usually used for floor side mapping, since these faces have fixed U-axis scaling values. The Ref. Point mode is typically used for maps where the scaling value cannot be determined (usually due to model deformation that causes the scaling value to fluctuate around the standard range of scaling values), such as the upper surface of a sink floor generated from a curve with bevel shape, as shown in the following figure. We can be sure that the UV of the center of the sink floor must be 0.5, but it is not convenient to determine its scaling value because it is difficult to calculate it, so we just use the Ref. Point mode. The top half of the figure below shows the mapping result in the Ref. Point mode, and the bottom half is in the Scale Size mode. We can observe that in the Scale Size mode, the UV of the center of the sink floor is not exactly 0.5, which results in the center of the sink floor not being dark enough on the display. The Ref. Point mode, on the other hand, accurately sets the UV of the center of the sink floor to 0.5.
![](../imgs/flatten-uv-scale-mode.png)
Finally, Flatten Mode specifies the unfolding mode, Raw means that the connection between faces is not considered at all, and each face is treated as an independent content, Floor means that the continuity of adjacent faces in the V-axis direction is taken into account, and adjacent faces will be made adjacent in the UVs as much as possible, which can avoid the problem of visual duplications caused by Raw unfolding in the case of too many subdivisions of the road surface. Floor is often used in floor mapping, so it's called Floor. Wood is similar to Floor, except that it not only considers continuity on the V-axis, but also on the U-axis, which is often used in concave and convex wood mapping, and is the reason of its name.
The figure below shows the distribution of UV mapping for three different unfolding modes. At the top is the Raw mode, where you can see that the Reference Edge of each face is expanded at the UV coordinates of the origin. In the middle is the Floor mode, where you can see that BBP unfolded a series of consecutive faces along the V-axis, instead of stacking them all at the origin as in the Raw mode. At the bottom is the Wood unfolding mode, which is unfolding a convex wood, and you can see that the continuity of the faces has been taken into account on both the V and U axes.
![](../imgs/flatten-uv-flatten-mode.png)
!!! info "Floor and Wood Flatten Mode failed"
Floor and Wood mode have more limitations than Raw modes, they only support rectangle faces, and they require a lot of modeling operations, usually only the geometry generated by batch operations (e.g. subdividing, lofting, etc.) will be correctly recognized by Floor and Wood.
If Floor and Wood unfolding modes fail and the resulting unfolded maps are completely unacceptable, try modeling in a more prescriptive way or switch to manual mapping.

View File

@ -1,4 +1,59 @@
# Virtools Properties
!!! info "Work in Progress"
This part of manual still work in progress.
## 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.
![](../imgs/virtools-group.png)
In the `Virtools Group` panel, you can click Add to group objects. After clicking the Add button, you can select Predefined and then select one of all legal Ballance group names to add. Or select Custom and enter the group name you want to add. The selected Virtools group can also be deleted by clicking the Delete button. Finally, all group data for this object can be deleted at once by clicking the Trash icon button (which will let you confirm before deleting).
BBP also provides access to Virtools groups in Blender's other menus, see [Group Operation](./group-operations.md).
## Virtools Material
The plugin adds a new property to every Blender material, called Virtools Material, which bridges the gap between Virtools materials and Blender materials. Go to the `Material` properties panel and select a material to find the `Virtools Material` panel.
![](../imgs/virtools-material.png)
You can set material properties in the `Virtools Material` panel, just like in Virtools. All material parameters in the `Virtools Material` panel are maps of the material parameters in Virtools and will be accurately reflected in the final saved Virtools document.
The `Virtools Material` panel provides a preset function, which can be started by clicking the `Preset` button at the top. The preset function allows the user to use some preset material settings, such as the material data for the top surface of the road, the sides, etc., for ease of use. Note that using a preset does not affect the material's texture options, and you will still need to set the material's texture manually once the preset has been applied.
The `Virtools Material` panel also provides the ability to convert the material data in the `Virtools Material` panel to the Blender material for a visual effect in Blender. Click the `Apply` button at the top to perform this function. When you save a Virtools document in Blender, the material data in the Virtools document will be retrieved from the values specified in the `Virtools Material` panel and not from the Blender material. This means that a proper procedure for setting up a material is to edit the material parameters in the `Virtools Material` panel and then use the `Apply` button to convert them to the Blender material, instead of editing the Blender material directly.
The `Virtools Material` panel provides material repair functionality, which is inherited from the [Ballance Virtools Plugin](https://github.com/yyc12345/BallanceVirtoolsHelper). The Material repair button is located to the right of the `Preset` button and the `Apply` button, and is a button with a wrench icon. Clicking it requires reconfirmation to prevent misuse. The material repair function determines which type of material the current material is based on the filename of the texture file it references, and then modifies the other parameters to make it visually appealing based on our preset repair settings (taken from the game). This is usually used for fixing objects that look like they have the wrong material in the game, such as a black Stopper, etc.
!!! info "There is also a global material fix function"
In the 3D view, the menu `Ballance - Fix Material` is similar to the material fix function, but it will fix all materials within the current document. Don't use this feature unless you are sure that all materials in the current document need to be fixed, as it may set some of the originally special, correct materials back to generic values that you don't want.
The Global Material Repair function also needs to be reconfirmed after clicking on it before it can be used, to prevent misuse.
The Texture property in the `Virtools Material` panel not only allows you to select a texture within a document by clicking on it, but also opens the Texture File Browser by clicking on the folder button on the right side, which allows you to select the texture you want directly from the file system (much faster than selecting it from the Shading tab). The file browser is navigated in Ballance's Texture directory by default, to make it easier to select Ballance materials.
## Virtools Texture
The BBP plugin adds a new property to all Blender textures (actually Images) called Virtools Texture, which creates a link between Virtools textures and Blender images.
Unlike Blender materials, due to Blender's implementation there is no separate properties panel for textures, so we can only access Virtools texture properties in an indirect way in the `Virtools Material` panel. First find the `Virtools Material` panel by referring to the instructions in the `Virtools Material` chapter, then select a texture or open a texture in the material slot in the `Virtools Material` panel, and you'll see that the Virtools texture attributes are displayed additionally underneath the texture attributes of the material, as shown in the highlighted portion of the image below.
![](../imgs/virtools-texture.png)
Among them, Save Option indicates how the texture is stored in Virtools, and these are the common storage methods:
* External: The file only stores the name of the referenced file. All Ballance native textures should use this mode.
* Raw Data: The texture is stored inside the file, the disadvantage is that it will lead to a large file. All non-native Ballance maps should use this mode.
* Use Global: Use global settings. We do not recommend using this unless you are modifying an existing map. We recommend explicitly specifying how individual textures are stored right here, rather than using global values. The global setting is determined when exporting the Virtools document.
The Video Format indicates the rendering mode of the texture in Virtools, and these are the commonly used modes:
* 32 Bits ARGB8888: The storage mode for all kinds of textures with transparency, such as the column gradient part.
* 16 Bits ARGB1555: The way to store all kinds of maps without transparency, such as floor.
## Virtools Mesh
The BBP plugin adds a new property to all Blender meshes called Virtools Mesh. go to the `Data` properties panel to find the `Virtools Mesh` panel.
![](../imgs/virtools-mesh.png)
The Virtools Mesh is currently only used as a compatibility feature. It has only one property, Lit Mode, that can be set. Most early maps had black floor issue because they didn't know how to set the material correctly, so the Lit Mode was often set to Prelit to get the floor to show up properly. This attribute exists for compatibility with this compromise and the user usually does not need to set this option.

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -0,0 +1,29 @@
# Ballance属性
Ballance属性有别于Virtools属性它是专门为Ballance制图服务的一系列属性。这些属性寄宿于场景在同一场景中制图不会涉及Blender中的场景切换这些属性不会改变。在`Scene`属性面板可以找到Ballance属性相关的面板如下图所示它们分别是
* `Ballance Elements`面板红色箭头对应Ballance机关
* `BME Materials`面板绿色箭头对应BME材质
* `Ballance Map`面板蓝色箭头对应Ballance地图信息
其中只有Ballance地图信息是你需要重点关注的其它属性在通常情况下不需要关注除非地图中的某些材质或网格出现错误后才需要关注这些属性。
![](../imgs/ballance-properties.png)
## Ballance地图信息
Ballance地图信息目前只有一个选项Sector地图小节数。此属性指示了当前地图的最终期望小节数。这个属性主要是用于解决导出地图Bug的具体Bug内容可参考[导入导出Virtools文档](./import-export-virtools.md)章节。
你需要做的唯一一件事就是在导出最终地图前检查此字段是否是你期望的小节数。需要注意的是尽管你可以在创建地图的一开始就设置此字段然而BBP中的其它一些功能可能会修改此字段比如添加机关导入Virtools文档等。例如当你的地图小节指定为3你正在添加一个归属于第4小节的机关那么此值会自动增加到4。同理在导入一个总共4小节的Ballance地图时此值也会增加到4如果先前值小于4的话。这么做主要是为了用户可以在没有感知到此值的情况下使用此插件尤其是在对某些现有地图进行略微修改时。然而如此操作可能会在某些情况下不能满足用户的需求所以仍建议你在导出前检查此字段。
## Ballance机关
Ballance机关记录了你所有使用BBP添加机关功能添加的机关的网格。在3D文件中通常而言网格占据了最大的数据量因此减少网格的数量即通过在各个相同形状的物体之间共享网格可以大幅减少地图文件所占用的大小。当你使用BBP添加机关相关功能时会首先尝试从这里获取机关的网格如果没有对应网格则加载并记录在这里。
这个面板只供查看不可编辑。当你不小心修改了BBP添加的机关的网格它们本不该被修改想要恢复其原有形状时只需点击`Reset Ballance Elements`即可将列表中的所有机关网格重置为正确状态。
## BME材质
与Ballance机关类似记录了使用BME添加路面时使用的材质。这也是为了可以复用材质而设计的这样就不需要每创建一个物体就创建一套与之相关的材质大大减少了重复材质的数量。
这个面板只供查看不可编辑。当你不小心修改了BME相关的材质它们本不该被修改想要恢复其原有材质时只需点击`Reset BME Materials`即可将列表中的所有材质重置为正确状态。

View File

@ -2,7 +2,7 @@
## 开始生成
在3D视图中点击`Add - Floors`可展开添加机关菜单。菜单如下图所示。
在3D视图中点击`Add - Floors`可展开添加路面菜单。菜单如下图所示。
![](../imgs/bme-adder.png)

View File

@ -20,7 +20,11 @@ BBP内置了一系列自定义图标以及其组件BME需要的用于描述
## 打包
`bbp_ng`文件夹压缩成ZIP文件即可完成打包工作。需要注意的是下列文件或文件夹不应被打包
从Blender 4.2 LTS开始插件使用Blender自带的打包功能进行打包
假定在项目根目录下执行命令,最终输出文件为`redist/bbp_ng.zip`,那么在命令行窗口中执行`blender --command extension build --source-dir bbp_ng --output-filepath redist/bbp_ng.zip`命令即可完成打包。其中`blender`为Blender的可执行程序。
Blender会根据`blender_manifest.toml`的指示,在排除下列文件的情况下将插件打包:
* `bbp_ng/raw_icons`:原始图片文件夹。
* `bbp_ng/raw_jsons`原始JSON文件夹。
@ -30,10 +34,6 @@ BBP内置了一系列自定义图标以及其组件BME需要的用于描述
* `bbp_ng/icons/.gitkeep`:文件夹占位符
* `bbp_ng/jsons/.gitkeep`:文件夹占位符
打包后的ZIP文件打开后如果有且只有`bbp_ng`一个文件夹,则代表打包成功。切勿直接将`bbp_ng` **内部的文件** 直接打包到ZIP文件中。
这样打包后的ZIP文件既可以直接通过Blender插件的安装功能直接安装也可以解压在插件目录下完成安装。
## 生成帮助文档
虽然本项目会利用GitHub Page功能提供帮助文档但有时你可能需要提供帮助文档的离线版本本节将会介绍如何生成离线版本的帮助文档。

View File

@ -7,7 +7,7 @@
## 打开配置面板
开启Blender选择`Edit - Preferences`,在打开的窗口中转到`Add-ons`选项卡,在`Community`分类下找到BBP插件名称为`Object: Ballance Blender Plugin`。请确保其左侧的勾已被选中,代表插件已被启用。点击勾左侧的三角箭头展开插件详细信息,如图所示,进入配置面板。
开启Blender选择`Edit - Preferences`,在打开的窗口中转到`Add-ons`选项卡,在列表中找到BBP插件名称为`Ballance Blender Plugin`。请确保其左侧的勾已被选中,代表插件已被启用。点击勾左侧的三角箭头展开插件详细信息,如图所示,进入配置面板。
![](../imgs/config-plugin.png)

View File

@ -20,15 +20,18 @@ Conflict Options冲突解决选项章节指示了当导入器遇到物体
众所周知Virtools使用基于系统的多字节字符编码来处理文档因而很容易出现所谓乱码问题。Blender本身不会出现乱码问题然而如果我们不能以正确的编码读取Virtools文档则当Virtools文档被导入Blender时其中存储的字符仍然可能会呈现乱码状态。Virtools ParamsVirtools参数章节的Encodings编码属性用于指定读取Virtools文档的编码。可以指定多个编码多个编码之间用`;`(分号)分隔。下面列出一些常用的编码:
* 1252Windows下Ballance所用的西欧编码
* 936Windows下中文Windows系统默认编码
* CP1252非Windows下Ballance所用的西欧编码
* CP936非Windows下中文默认编码
* cp1252Ballance所用的西欧编码
* gb2312中文Windows系统默认编码
编码属性非常重要如果设置了错误的编码导入Blender的各类物体的名称会出现不可认知的情况
编码属性非常重要如果设置了错误的编码导入Blender的各类物体的名称会出现不可认知的情况,又或者会导致程序出错。
!!! warning "编码是一个平台相关的设定"
根据BBP的Virtools文档导入模块使用的底层库LibCmo的实现编码属性是一个平台相关的设定。在Windows下这里需要填写的是[Windows代码页](https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers)数字。而在其它操作系统下LibCmo使用iconv进行字符编码解码因此需要使用合法的[iconv编码标识符](https://www.gnu.org/software/libiconv/)
!!! info "有哪些编码可以使用?"
BBP 4.1版本后我们使用编码的名字基本上就是照抄Python的编码名。大多数Python中常用的编码名都有映射只有一些特别罕见的编码没有支持对于具体支持的编码则需要查看源码。有关Python支持的编码请查看[Python相关文档](https://docs.python.org/3/library/codecs.html#standard-encodings)。编码名不区分大小写
!!! warning "从旧版本迁移的警告"
自BBP 4.1版本开始BBP的Virtools文档导入模块使用的底层库LibCmo版本号提升为0.2。在此版本前编码属性是一个平台相关的设定。在Windows下这里需要填写的是[Windows代码页](https://learn.microsoft.com/en-us/windows/win32/intl/code-page-identifiers)数字。而在其它操作系统下LibCmo使用iconv进行字符编码解码因此需要使用合法的[iconv编码标识符](https://www.gnu.org/software/libiconv/)。
这一切在LibCmo 0.2后发生了改变从这个版本开始LibCmo采用了类似Python的统一编码名。它是平台无关的你不再需要检查你正在使用的操作系统是Windows还是Linux所有平台的编码字符均为相同的字符串。这也就意味着如果您之前自定义过编码设置你需要注意将他们转换到新的统一编码名因为旧的编码名可能在统一编码名下没有对应映射例如之前在Windows上指定的`1252`,在新的统一编码名下应当被书写为`cp1252`,原编码在新系统下无法被正确识别。
## 导出Virtools文档
@ -46,3 +49,10 @@ Global Texture Save Option全局贴图保存选项决定了那些设置了
Use Compress使用压缩属性指定保存的文档是否压缩存储。压缩可以显著减少文档体积且在现代计算机平台上压缩所造成的性能损失几乎可以忽略不计。当选择使用压缩后一个额外的Compress Level压缩等级属性将会显示用于指定压缩的级别数值越高压缩率越大文件越小。
### Ballance参数
Ballance ParamsBallance参数章节包含针对Ballance特有内容对导出过程进行优化的参数。
Successive Sector小节连续是一个解决导出小节组时出现的Bug的选项。由于某些原因如果一个小节中没有任何机关实际上是某小节组中没有归入任何物体导出插件会认为该小节组不存在因而遗漏导出。且由于Ballance对最终小节即飞船出现小节的判定是从1开始递增寻找最后一个存在的小节组所以二者叠加会导致Ballance错误地认定地图的小节数从而在错误的小节显示飞船这也就是导出Bug。当勾选此选项后导出文档时会预先按照当前Blender文件中Ballance地图信息中指定的小节数预先创建所有小节组然后再进行导出这样就不会遗漏创建某些小节组飞船也会在正确的小节显示。
这个选项通常在导出可游玩的地图时选中,如果你只是想导出一些模型,那么需要关闭此选项,否则会在最终文件中产生许多无用的小节组。

View File

@ -16,6 +16,7 @@ Ballance Blender Plugin后文简称BBP是一款关注Ballance自制地图
## 特性
* [Virtools属性](./virtools-properties.md)
* [Ballance属性](./ballance-properties.md)
* [导入导出Virtools文档](./import-export-virtools.md)
* [按组操作](./group-operations.md)
* [传统对齐](./legacy-align.md)

View File

@ -2,19 +2,26 @@
## 明确版本
BBP对Blender支持的原则是支持当前最新的 **LTS** 版本在最新的LTS版本释出之后会花一些时间迁移插件。当前插件版本 **4.0**基于Blender **3.6.x** 版本。
BBP对Blender支持的原则是支持当前最新的 **LTS** 版本在最新的LTS版本释出之后会花一些时间迁移插件。当前插件版本 **4.0**基于Blender **4.2.x** 版本。
理论上而言如果Blender没有做出重大改动那么BBP可以在其它版本上正常运行。例如你可以尝试在Blender 4.0上运行基于Blender 3.6的BBP插件。但BBP的开发者不会处理仅在非LTS版本中才出现的Bug。在安装插件之前请先选择适合的版本。
理论上而言如果Blender没有做出重大改动那么BBP可以在其它版本上正常运行。例如你可以尝试在Blender 4.0上运行基于Blender 3.6 LTS的BBP插件。但BBP的开发者不会处理仅在非LTS版本中才出现的Bug。在安装插件之前请先选择适合的版本。
## 卸载旧插件
如果你之前使用过BBP那么你需要首先卸载它。旧版的BBP通常被安装在下列的位置中
* `Blender/3.6/scripts/addons/ballance_blender_plugin`
* `Blender/3.6/scripts/addons_contrib/ballance_blender_plugin`
* `Blender/3.6/scripts/addons/bbp_ng`
* `<Blender>/3.6/scripts/addons/ballance_blender_plugin`BBP 3.0或更低版本
* `<Blender>/3.6/scripts/addons_contrib/ballance_blender_plugin`BBP 3.0或更低版本
* `<Blender>/3.6/scripts/addons/bbp_ng`BBP 4.0内测版本
* `%APPDATA%/Blender Foundation/Blender/3.6/scripts/addons/bbp_ng`BBP 4.0内测版本
* `%APPDATA%/Blender Foundation/Blender/4.2/extensions/user_default/bbp_ng`BBP 4.0或更高版本
你只需要删除这些文件夹(如果它们存在的话)即可完全卸载插件。路径中的`Blender`指代你的Blender安装位置。路径中的`3.6`是你安装的Blender的版本号需要根据你安装的版本进行调整本手册均以`3.6`为例
你只需要先在Blender中关闭插件把插件名前面的勾取消然后再删除这些文件夹(如果它们存在的话)即可完全卸载插件。路径中的`<Blender>`指代你的Blender安装位置。路径中的`3.6``4.2`是你安装的Blender的版本号需要根据你安装的版本进行调整后续出现的版本号也按此理解
!!! warning "不应使用Blender的插件卸载功能"
不能使用Blender插件页面的插件卸载功能卸载BBP因为BBP只要被Blender加载无论是否启用都会将Virtools文件读写库BMap加载进Blender。若在Blender运行期间删除会出现拒绝访问错误。因此您必须在关闭Blender后手动删除插件目录。
如果您实在无法确定插件安装到了哪里可以在Blender的偏好设置中的插件页面里找到`File`属性,其指向文件所在的文件夹就是要删除的文件夹。
!!! info "`ballance_blender_plugin``bbp_ng`"
`ballance_blender_plugin`是旧版BBP插件4.0版本前)的模块名,`bbp_ng`是新版BBP插件4.0版本后包括4.0版本)的模块名。为了保证用户确实删除了旧版插件,所以同时提供了这两者。
@ -22,6 +29,9 @@ BBP对Blender支持的原则是支持当前最新的 **LTS** 版本,在最新
!!! info "`addons``addons_contrib`"
在Blender 3.6 LTS版本即BBP 3.3版本之后Blender不再支持Testing类型插件。因而导致安装Testing插件专用的`addons_contrib`文件夹不再使用,插件需要被统一安装在`addons`中。为了保证用户确实删除了旧版插件,所以同时提供了这两者。
!!! info "`addons``extensions`"
在Blender 4.2 LTS版本Blender使用扩展Extensions而非插件Addons来描述插件。因而导致安装插件的位置也发生了变化。为了保证用户确实删除了旧版插件所以同时提供了这两者。
## 下载插件
你可以通过[本工程的GitHub代码库的Release页面](https://github.com/yyc12345/BallanceBlenderHelper/releases)下载最新的插件。插件是以ZIP压缩包形式提供的。
@ -36,11 +46,11 @@ BBP对Blender支持的原则是支持当前最新的 **LTS** 版本,在最新
## 安装插件
开启Blender选择`Edit - Preferences`,在打开的窗口中转到`Add-ons`选项卡,点击`Install...`按钮选择刚刚下载完毕的ZIP压缩包即可安装完成。若没有在列表中看到可选择刷新按钮或重启Blender。
开启Blender选择`Edit - Preferences`,在打开的窗口中转到`Add-ons`选项卡,点击窗口右上方的箭头,然后点击`Install from Disk...`按钮选择刚刚下载完毕的ZIP压缩包即可安装完成。若没有在列表中看到可点击刷新按钮或重启Blender。
你也可以选择手动安装插件(如果上述安装方法失败了的话),转到`Blender/3.6/scripts/addons`将下载好的ZIP压缩包内容解压到此文件夹下启动Blender即可在插件列表中找到BBP。
你也可以选择手动安装插件(如果上述安装方法失败了的话),转到`%APPDATA%/Blender Foundation/Blender/4.2/extensions/user_default`,创建一个名为`bbp_ng`的文件夹并进入将下载好的ZIP压缩包内容解压到此文件夹下启动Blender即可在插件列表中找到BBP。
BBP插件位于`Community`类别下,名称为`Object: Ballance Blender Plugin`,找到后勾选名称左侧的勾即可启用插件。插件安装成功后的偏好设置页面如下图所示。
BBP插件在列表中的名称为`Ballance Blender Plugin`,找到后勾选名称左侧的勾即可启用插件。插件安装成功后的偏好设置窗口如下图所示。
![](../imgs/config-plugin.png)

View File

@ -8,7 +8,7 @@
## 使用方法
传统对齐支持将多个物体对一个物体的对齐,操作方法是先依次选中需要被对齐的物体,然后在最后选中对齐参考对象(也就是使其为活动物体),然后点击`Ballance - 3ds Max Align`,即可弹出传统对齐面板,之后便可开始对齐操作。
传统对齐支持将多个物体对一个物体的对齐,操作方法是先依次选中需要被对齐的物体,然后在最后选中对齐参考对象(也就是使其为活动物体),然后点击`Ballance - 3ds Max Align`,即可弹出传统对齐面板,之后便可开始对齐操作。
## 面板介绍

View File

@ -7,7 +7,7 @@
![](../imgs/naming-convention.png)
本插件目前支持两种命名标准。
其一为技术信息章节已经阐述的制图链标准,在本插件中的名称为`YYC Tools Chains`
其一为[技术信息](./tech-infos.md)章节阐述的制图链标准,在本插件中的名称为`YYC Tools Chains`
其二为[Imengyu/Ballance](https://github.com/imengyu/Ballance)所用命名标准,在本插件中的名称为`Imengyu Ballance`
这些功能最终只会展示成功与否的一个概括性消息。如果你需要详细查看某个物体为什么不能转换,请点击`Window - Toggle System Console`,插件在那里有更详细的输出。

View File

@ -1,10 +1,10 @@
# 添加钢轨
在3D视图中点击`Add - Components`可展开添加钢轨菜单。菜单如下图左侧所示。
在3D视图中点击`Add - Rails`可展开添加钢轨菜单。菜单如下图左侧所示。
![](../imgs/rail-adder.png)
上图右侧则展示了一些机关添加的界面会在后续依次介绍它们右侧从上到下分别是添加Rail Section钢轨截面添加Straight Rail直钢轨添加Side Rail侧轨添加Arc Rail圆弧轨添加Spiral Rail螺旋轨
上图右侧则展示了一些钢轨添加的界面会在后续依次介绍它们右侧从上到下分别是添加Rail Section钢轨截面添加Straight Rail直钢轨添加Side Rail侧轨添加Arc Rail圆弧轨添加Spiral Rail螺旋轨
!!! info "非标准数据的钢轨"
BBP的钢轨添加菜单是为新手玩家快速添加钢轨而设计的并不是为老手添加钢轨而设计的。对于需要非标准数据的钢轨的情况例如具有非标准间距或非标准截面的钢轨你需要通过Blender自带的创建圆操作构建钢轨截面然后通过挤出桥接又或者螺旋修改器生成整个钢轨。在这样的创作过程中你可以随意控制每个步骤的所有参数以满足你对钢轨参数的特殊需求。
@ -16,7 +16,7 @@
## 钢轨截面
在添加机关菜单中,`Sections`分类下的是钢轨截面的添加。钢轨截面是钢轨的轮廓,钢轨截面的创建通常是各类异形钢轨的创建的开始步骤,例如通过放样,挤出等操作制作钢轨。
在添加钢轨菜单中,`Sections`分类下的是钢轨截面的添加。钢轨截面是钢轨的轮廓,钢轨截面的创建通常是各类异形钢轨的创建的开始步骤,例如通过放样,挤出等操作制作钢轨。
### Rail Section
@ -30,7 +30,7 @@
## 直线钢轨
在添加机关菜单中,`Straight Rails`分类下的是直线钢轨的添加。
在添加钢轨菜单中,`Straight Rails`分类下的是直线钢轨的添加。
### Straight Rail
@ -52,7 +52,7 @@
## 曲线钢轨
在添加机关菜单中,`Curve Rails`分类下的是曲线钢轨的添加。
在添加钢轨菜单中,`Curve Rails`分类下的是曲线钢轨的添加。
### Arc Rail
@ -78,4 +78,4 @@
侧边螺旋轨没有螺距属性,因为侧边螺旋轨在设计上,相邻的旋进是共用一条边的,因此螺距是固定的。
侧边螺旋轨设定中的Radius半径Iterations迭代和Steps步数属性含义均与螺旋轨一致。螺旋轨也有封盖属性。
侧边螺旋轨设定中的Radius半径Iterations迭代和Steps步数属性含义均与螺旋轨一致。侧边螺旋轨也有封盖属性。

View File

@ -12,7 +12,7 @@ BBP不是完美的由于BBP的Virtools文件导入导出模块是由C++编写
## 哪部分出错了
对于BBP插件而言如果你在Python异常输出中观察到类似于`BMap operation failed`的字样,或者在`Blender/3.6/scripts/addons/bbp_ng/PyBMap`文件夹下观察到了`IronPad.log`文件则说明BBP插件的由C++编写的BMap部分出错了**你需要立即保存你当前的Blender文档并退出Blender。** 因为此时插件已处于非正常状态,你不应继续任何操作。
对于BBP插件而言如果你在Python异常输出中观察到类似于`BMap operation failed`的字样,或者在`<插件安装位置>/PyBMap`文件夹下观察到了`IronPad.log`文件则说明BBP插件的由C++编写的BMap部分出错了**你需要立即保存你当前的Blender文档并退出Blender。** 因为此时插件已处于非正常状态,你不应继续任何操作。
如果并没有上述情况那么这就只是普通的Python代码执行错误不需要过度担心但错误仍然是致命的建议做完所有必要的操作后退出Blender并报告错误。
@ -20,10 +20,10 @@ BBP不是完美的由于BBP的Virtools文件导入导出模块是由C++编写
如果你有GitHub账户你可以在[BBP的存储库的Issue页面](https://github.com/yyc12345/BallanceBlenderHelper/issues)中创建并汇报问题。
如果做不到,则直接汇报给插件作者也是可以的。
如果做不到,且你有合适的渠道可以联系到插件作者,则直接汇报给插件作者也是可以的。
## 报告的内容
首先你需要详细描述你是如何发这个错误的,这个错误有什么结果。如果可以上传导致错误的文档,请尽量上传(如果不方便公开发布,可以通过邮件等私有渠道发送给作者)。
首先你需要详细描述你是如何发这个错误的,这个错误有什么结果。如果可以上传导致错误的文档,请尽量上传(如果不方便公开发布,可以通过邮件等私有渠道发送给作者)。
你还需要提供Blender控制台中输出的Python堆栈报告使用`Window - Toggle System Console`打开控制台。如果你的错误是BMap部分的错误你还需要提供`Blender/3.6/scripts/addons/bbp_ng/PyBMap`文件夹下的`IronPad.log``IronPad.dmp`文件以方便开发者定位错误。
你还需要提供Blender控制台中输出的Python堆栈报告使用`Window - Toggle System Console`打开控制台。如果你的错误是BMap部分的错误你还需要提供`<插件安装位置>/PyBMap`文件夹下的`IronPad.log``IronPad.dmp`文件以方便开发者定位错误。

View File

@ -4,4 +4,9 @@
* 制图工具链标准及`meshes`文件夹下的文件的格式https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md
* BMERevenge的JSON文件的格式https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_v2.0_ZH.md
本插件配合了`fake-bpy-module`模块来实现类型提示以加快开发速度。本插件目前基于Blender 3.6,因此使用`pip install fake-bpy-module-latest==20230627`来安装Blender的类型提示库。 这主要是因为`fake-bpy-module`没有发布官方的适用于Blender 3.6的包因此我只能通过选择最接近Blender 3.6版本发布时间的每日编译版本来安装它
本插件配合了`fake-bpy-module`模块来实现类型提示以加快开发速度。使用如下命令来安装Blender的类型提示库
* Blender 3.6: `pip install fake-bpy-module-latest==20230627`
* Blender 4.2: `pip install fake-bpy-module-latest==20240716`
这么做主要是因为`fake-bpy-module`没有发布官方的适用于指定Blender版本的包因此我只能通过选择最接近Blender对应版本发布时间的每日编译版本来安装它。

View File

@ -15,7 +15,7 @@
沿边沿贴图是BBP插件的最重要的功能最强大的功能同时也是最难以理解的功能。它广泛应用于自定义结构或一行路面木板的UV的设置。3D视图中的菜单`Ballance - Flatten UV`提供的就是沿边沿贴图功能。它只能在 **编辑模式** 下工作,因此你需要进入编辑模式(并同时进入面选择模式,因为沿边沿贴图是按面操作的)才能使用它。
沿边沿贴图通常与[选择循环面](https://docs.blender.org/manual/en/3.6/modeling/meshes/selecting/loops.html)[选择最短路径](https://docs.blender.org/manual/en/3.6/modeling/meshes/selecting/linked.html#bpy-ops-mesh-shortest-path-select)等功能一起使用。你需要先选择一系列面例如一系列连续的Ballance路面侧边然后点击`Ballance - Flatten UV`开始进行沿边沿贴图。
沿边沿贴图通常与[选择循环面](https://docs.blender.org/manual/en/4.2/modeling/meshes/selecting/loops.html)[选择最短路径](https://docs.blender.org/manual/en/4.2/modeling/meshes/selecting/linked.html#bpy-ops-mesh-shortest-path-select)等功能一起使用。你需要先选择一系列面例如一系列连续的Ballance路面侧边然后点击`Ballance - Flatten UV`开始进行沿边沿贴图。
沿边沿贴图的配置界面如下图所示左侧是Scale Size缩放数值模式右侧是Ref. Point参考点模式我们稍后会说明这两个模式的区别。
@ -23,14 +23,14 @@
沿边沿贴图顾名思义其含义就是沿着某条边进行UV贴图。具体的操作就是利用线性代数的方法使用过渡矩阵将顶点的三维坐标转换到一个新的坐标系下。在这个新的坐标系中
* 原点是Reference Edge参考边指定的号的顶点如下图所示。下图在UV和3D中用黄色字体标识了面的顶点序号图中的Reference Edge为1则当前面中顶点序号为1的顶点作为新坐标系的原点。
* 原点是Reference Edge参考边指定的号的顶点如下图所示。下图在UV和3D中用黄色字体标识了面的顶点序号图中的Reference Edge为1则当前面中顶点序号为1的顶点作为新坐标系的原点。
* Y轴即V轴XYZ对应UVW后续不再注释为Reference Edge指定的顶点到下一个顶点的连线即顶点1到顶点2的边也就是序号为1的边如下图所示下图UV和3D中用紫色字体标识了面的边序号。边1是以顶点1为起始点顶点2为终点的一个向量。它有方向性是向量用作坐标轴时需要归一化。
* Z轴则是通过Reference Edge指定的边在这里是边1和其下一条相邻边在这里是边2做叉乘并归一化得出。如果发生三点共线叉乘出零向量的情况则会尝试使用面的法线数据取而代之。
* X轴由之前计算出的Y轴和Z轴做叉乘并归一化后得出。新坐标系下的XYZ仍然需要满足右手坐标系的要求。
![](../imgs/flatten-uv-mechanism.png)
建立完新坐标系并构建完过渡矩阵后并将每一个顶点都转换到新坐标系下后我们便可丢弃Z分量将XY映射到UV上并同时将处于-U轴侧的顶点镜像到+U轴侧这也是Flatten UV不支持凹多边形的原因。做镜像的原因是为了防止UV映射出错例如上图中路面侧边贴图的上沿花纹位于V轴如果我们将上图中UV顶点沿V轴翻转你可以试一试加深理解则会导致最终贴图显示不符合我们预期即Reference Edge指定的边并不显示路面侧边花纹。
建立完新坐标系并构建完过渡矩阵后并将每一个顶点都转换到新坐标系下后我们便可丢弃Z分量将XY映射到UV上并同时将处于-U轴侧的顶点镜像到+U轴侧这也是Flatten UV不支持凹多边形的原因。做镜像的原因是为了防止UV映射出错例如上图中路面侧边贴图的上沿花纹位于V轴如果我们将上图中UV顶点沿V轴翻转你可以试一试加深理解则会导致最终贴图显示不符合我们预期即Reference Edge指定的边并不显示路面侧边花纹。
!!! info "Reference Edge的实指"
Reference Edge实际上指代的就是那条需要被贴靠到V轴上的边的序号。又因为这条边具有方向性同时也就决定了新坐标系中的原点。

View File

@ -20,7 +20,7 @@ BBP还在Blender的其它菜单提供了对Virtools组的访问具体内容
`Virtools Material`面板提供了预设功能,点击顶部的`Preset`按钮即可开始进行预设。预设功能允许用户使用一些预设的材质设置,例如路面顶面,侧面的材质数据等,方便使用。需要注意的是,使用预设不会影响材质的贴图选项,当应用预设后,你仍然需要手动设置材质的贴图。
`Virtools Material`面板同样提供把`Virtools Material`面板中的材质数据反应到Blender材质上的功能以在Blender中获得可视的效果。点击顶部的`Apply`按钮即可执行此功能。当你在Blender中保存Virtools文档时Virtools文档中的材质数据将从`Virtools Material`面板中指定的数值获取而不会从Blender材质中获取。这意味一个正确的材质设置过程是先在`Virtools Material`面板中编辑材质参数,然后使用`Apply`按钮将其反映到Blender材质上而不是直接去编辑Blender材质。
`Virtools Material`面板同样提供把`Virtools Material`面板中的材质数据转换到Blender材质上的功能以在Blender中获得可视的效果。点击顶部的`Apply`按钮即可执行此功能。当你在Blender中保存Virtools文档时Virtools文档中的材质数据将从`Virtools Material`面板中指定的数值获取而不会从Blender材质中获取。这意味一个正确的材质设置过程是先在`Virtools Material`面板中编辑材质参数,然后使用`Apply`按钮将其转换到Blender材质上而不是直接去编辑Blender材质。
`Virtools Material`面板提供了材质修复功能,这个功能来源于[Ballance Virtools Plugin](https://github.com/yyc12345/BallanceVirtoolsHelper)。材质修复按钮位于`Preset`按钮和`Apply`按钮的右侧是一个带有扳手图标的按钮。点击后需要再次确认才能使用防止误操作。材质修复功能会根据当前材质引用的贴图文件的文件名判定它是哪一种类型的材质再根据我们预设的修复设定从游戏中获取将其他参数修改得符合视觉要求。这通常用于一些游戏中看起来材质错误的物体的修复例如发黑的Stopper等。
@ -29,7 +29,7 @@ BBP还在Blender的其它菜单提供了对Virtools组的访问具体内容
全局材质修复功能点击后也需再次确认才能使用,以防止误操作。
`Virtools Material`面板中的Texture贴图属性不仅可以通过点击它来选择文档内的材质还可以通过点击右侧的文件夹按钮打开贴图文件浏览器直接从文件系统中选择你想要的贴图比从Shading菜单中选取更加快速。文件浏览器默认位于Ballance的Texture目录下以方便Ballance材质的选取。
`Virtools Material`面板中的Texture贴图属性不仅可以通过点击它来选择文档内的贴图还可以通过点击右侧的文件夹按钮打开贴图文件浏览器直接从文件系统中选择你想要的贴图比从Shading菜单中选取更加快速。文件浏览器默认位于Ballance的Texture目录下以方便Ballance材质的选取。
## Virtools贴图
@ -43,7 +43,7 @@ BBP插件为所有Blender贴图实际上是Image添加了新的属性
* External外部存储文件只存储引用的文件名。所有Ballance原生贴图都应该使用此模式。
* Raw Data原始数据贴图存储在文件内缺点是会导致文件很大。所有非原生Ballance贴图都应该使用此模式。
* Use Global使用全局设定。除非是正在修改地图否则我们不建议使用此方式。我们建议在这里就明确指定各个贴图的存储方式而不要使用全局值。全局设定在导出Virtools文档时被确定下来。
* Use Global使用全局设定。除非是正在修改一张已经存在地图否则我们不建议使用此方式。我们建议在这里就明确指定各个贴图的存储方式而不要使用全局值。全局设定在导出Virtools文档时被确定下来。
而Video Format表示贴图在Virtools中的渲染模式常用的模式有这几种
@ -52,7 +52,7 @@ BBP插件为所有Blender贴图实际上是Image添加了新的属性
## Virtools网格
BBP插件为所有Blender贴图实际上是Image添加了新的属性称为Virtools Mesh。转到`Data`属性面板,即可以找到`Virtools Mesh`面板。
BBP插件为所有Blender网格添加了新的属性称为Virtools Mesh。转到`Data`属性面板,即可以找到`Virtools Mesh`面板。
![](../imgs/virtools-mesh.png)

View File

@ -11,6 +11,7 @@ nav:
- 'Install Plugin': 'en/install-plugin.md'
- 'Configure Plugin': 'en/configure-plugin.md'
- 'Virtools Properties': 'en/virtools-properties.md'
- 'Ballance Properties': 'en/ballance-properties.md'
- 'Import and Export Virtools Document': 'en/import-export-virtools.md'
- 'Group Operation': 'en/group-operations.md'
- 'Legacy Alignment': 'en/legacy-align.md'
@ -27,6 +28,7 @@ nav:
- '安装插件': 'zh-cn/install-plugin.md'
- '配置插件': 'zh-cn/configure-plugin.md'
- 'Virtools属性': 'zh-cn/virtools-properties.md'
- 'Ballance属性': 'zh-cn/ballance-properties.md'
- '导入导出Virtools文档': 'zh-cn/import-export-virtools.md'
- '按组操作': 'zh-cn/group-operations.md'
- '传统对齐': 'zh-cn/legacy-align.md'