18 Commits

Author SHA1 Message Date
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
8bad1a487c add swing series adder 2024-02-18 22:11:06 +08:00
0f18559d3f chore: switch doc gen from ng to master 2024-02-13 12:19:20 +08:00
5a5053440c update doc english part 2024-02-13 12:06:16 +08:00
02a1222210 feat: flatten uv backward search mode
- support backward successive face searching to resolve some failed flatten uv in floor mode.
2024-02-13 10:58:44 +08:00
f8c344f65e doc: add doc.
- finish uv section of doc.
- fix issue about the wrong calc of coordinate system axis vector in flatten uv (change it from left hand to right hand).
- fix rail uv display text.
2024-02-12 22:39:44 +08:00
0bec108dcb feat: add fix material feature.
bring ballance virtools helper used fix_texture (renamed as fix material to correspond with the real object operated by this function) into blender plugin.
2024-02-12 11:42:09 +08:00
997839a187 feat: select object after creation of floor, rail and component 2024-02-11 17:11:05 +08:00
da71d5560c doc: update manual 2024-02-11 16:38:06 +08:00
02082bf99e fix issue. add doc.
- fix TwoFlames Resetpoint naming error.
- add doc parts.
2024-02-08 23:21:53 +08:00
c82c094519 add shadow group as the essential group for floor type. 2024-02-08 13:50:56 +08:00
8ec101e1f1 update flatten uv
- add `failed` counter for special flatten uv.
- ignore invalid face when checking them to prevent potential recursive checking.
2024-01-22 22:25:04 +08:00
8499c25b67 add redist folder. fix doc and readme 2024-01-14 21:25:12 +08:00
200ac40648 update doc 2024-01-14 17:08:53 +08:00
78 changed files with 1635 additions and 279 deletions

View File

@ -2,17 +2,17 @@ name: Publish docs via GitHub Pages
on:
push:
branches:
- ng
- master
jobs:
build:
name: Deploy docs
runs-on: ubuntu-latest
steps:
- name: Checkout ng
- name: Checkout master
uses: actions/checkout@v4
with:
ref: ng
ref: master
- name: Install MkDocs
run: |

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# disable distribution build folder
redist/

View File

@ -4,11 +4,4 @@
BBP NG, abbr **B**allance **B**lender **P**lugin **N**ext **G**eneration.
## Brief Introduction
The next generation of original Ballance Blender Helper. This plugin is fully rewritten.
This plugin still work in progress. The development will be pushed into `ng` branch in main repository. For legacy plugin user, please visit `master` branch directly.
## Develop Help
Use `pip install fake-bpy-module-latest==20230627` to install the type hint library for Blender. Because fake-bpy-module do not release official 3.6 package, we need install it by choosing the most closest version of Blender 3.6 release. That what I found.
For an introduction to this plugin, installing it, compiling it, reporting bugs, etc., see the GitHub Page of this project: https://yyc12345.github.io/BallanceBlenderHelper

View File

@ -4,7 +4,4 @@
BBP NG又名**B**allance **B**lender **P**lugin **N**ext **G**eneration下一代Ballance Blender插件
## 简介
下一代的Ballance Blender插件。此插件完全重写了上一代插件。
此插件仍然在开发过程中。开发内容会被推送到主仓库的`ng`分支中。对于旧插件的用户,请直接访问`master`分支。
有关此插件的介绍安装编译汇报错误等请参阅本项目的GitHub Page页面https://yyc12345.github.io/BallanceBlenderHelper

View File

@ -14,18 +14,18 @@ class BBP_PG_bme_adder_cfgs(bpy.types.PropertyGroup):
soft_min = 0, soft_max = 32,
step = 1,
default = 1,
)
) # type: ignore
prop_float: bpy.props.FloatProperty(
name = 'Single Float', description = 'Single Float',
min = 0.0, max = 1024.0,
soft_min = 0.0, soft_max = 512.0,
step = 50, # Step is in UI, in [1, 100] (WARNING: actual value is /100). So we choose 50, mean 0.5
default = 5.0,
)
) # type: ignore
prop_bool: bpy.props.BoolProperty(
name = 'Single Bool', description = 'Single Bool',
default = True
)
) # type: ignore
class BBP_OT_add_bme_struct(bpy.types.Operator):
"""Add BME Struct"""
@ -55,7 +55,7 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
description = "Internal flag.",
options = {'HIDDEN', 'SKIP_SAVE'},
default = False
)
) # type: ignore
## A BME struct cfgs descriptor cache list
# Not only the descriptor self, also the cfg associated index in bme_struct_cfgs
@ -133,13 +133,13 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
description = "BME struct type",
items = _g_EnumHelper_BmeStructType.generate_items(),
update = bme_struct_type_updated
)
) # type: ignore
bme_struct_cfgs : bpy.props.CollectionProperty(
name = "Cfgs",
description = "Cfg collection.",
type = BBP_PG_bme_adder_cfgs,
)
) # type: ignore
@classmethod
def poll(self, context):
@ -180,6 +180,8 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
# move to cursor
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
# select created object
UTIL_functions.select_certain_objects((obj, ))
return {'FINISHED'}
def draw(self, context):
@ -237,10 +239,10 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
#endregion
def register():
def register() -> None:
bpy.utils.register_class(BBP_PG_bme_adder_cfgs)
bpy.utils.register_class(BBP_OT_add_bme_struct)
def unregister():
def unregister() -> None:
bpy.utils.unregister_class(BBP_OT_add_bme_struct)
bpy.utils.unregister_class(BBP_PG_bme_adder_cfgs)

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
@ -12,7 +12,7 @@ class ComponentSectorParam():
min = 1, max = 999,
soft_min = 1, soft_max = 8,
default = 1,
)
) # type: ignore
def general_get_component_sector(self) -> int:
return self.component_sector
@ -27,7 +27,7 @@ class ComponentCountParam():
min = 1, max = 64,
soft_min = 1, soft_max = 32,
default = 1,
)
) # type: ignore
def general_get_component_count(self) -> int:
return self.component_count
@ -94,38 +94,83 @@ def _check_component_existance(comp_type: PROP_ballance_element.BallanceElementT
if expect_name in bpy.data.objects: return expect_name
else: return None
def _general_create_component(
comp_type: PROP_ballance_element.BallanceElementType,
comp_sector: int,
comp_count: int,
comp_offset: typing.Callable[[int], mathutils.Matrix]
) -> None:
class _GeneralComponentCreator():
"""
The assist class for general component creation function.
Because we need select all created component, thus we need collect all created object into a list.
This is the reason why we create this class.
"""
General component creation function.
@param comp_type[in] The component type created.
@param comp_sector[in] The sector param which passed to other functions. For non-sector component, pass any number.
@param comp_count[in] The count of created component. For single component creation, please pass 1.
@param comp_offset[in] The function pointer which receive 1 argument indicating the index of object which we want to get its offset.
You can pass `lambda _: mathutils.Matrix.Identity(4)` to get zero offset for every items.
You can pass `lambda _: mathutils.Matrix( xxx )` to get same offset for every items.
You can pass `lambda i: mathutils.Matrix( func(i) )` to get index based offset for each items.
The offset is the offset to the origin point, not the previous object.
"""
# 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
for i in range(comp_count):
# get mesh from element context, and create with empty name first. we assign name later.
obj: bpy.types.Object = bpy.data.objects.new('', creator.get_element(comp_type))
# assign virtools group, object name by we gotten element info.
_set_component_by_info(obj, ele_info)
# add into scene and move to cursor
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
# move with extra offset by calling offset getter
obj.matrix_world = obj.matrix_world @ comp_offset(i)
## The list storing all created component within this creation.
__mObjList: list[bpy.types.Object]
def __init__(self):
self.__mObjList = []
def create_component(self,
comp_type: PROP_ballance_element.BallanceElementType,
comp_sector: int,
comp_count: int,
comp_offset: typing.Callable[[int], mathutils.Matrix]
) -> None:
"""
General component creation function.
@param comp_type[in] The component type created.
@param comp_sector[in] The sector param which passed to other functions. For non-sector component, pass any number.
@param comp_count[in] The count of created component. For single component creation, please pass 1.
@param comp_offset[in] The function pointer which receive 1 argument indicating the index of object which we want to get its offset.
You can pass `lambda _: mathutils.Matrix.Identity(4)` to get zero offset for every items.
You can pass `lambda _: mathutils.Matrix( xxx )` to get same offset for every items.
You can pass `lambda i: mathutils.Matrix( func(i) )` to get index based offset for each items.
The offset is the offset to the origin point, not the previous object.
@return The created component instance.
"""
# get element info first
ele_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector)
# create blc element context
with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator:
# object creation counter
for i in range(comp_count):
# get mesh from element context, and create with empty name first. we assign name later.
obj: bpy.types.Object = bpy.data.objects.new('', creator.get_element(comp_type))
# assign virtools group, object name by we gotten element info.
_set_component_by_info(obj, ele_info)
# add into scene and move to cursor
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
# move with extra offset by calling offset getter
obj.matrix_world = obj.matrix_world @ comp_offset(i)
# put into created object list
self.__mObjList.append(obj)
# enlarge scene sector field for non-PS (start point) PE (end point) component
# read from scene and create var for enlarged sector count
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
enlarged_sector: int
# check component type to get enlarged value
match(ele_info.mBasicType):
case UTIL_naming_convension.BallanceObjectType.COMPONENT:
enlarged_sector = comp_sector
case UTIL_naming_convension.BallanceObjectType.CHECKPOINT:
# checkpoint 1 means that there is sector 2, so we plus 1 for it.
enlarged_sector = comp_sector + 1
case UTIL_naming_convension.BallanceObjectType.RESETPOINT:
enlarged_sector = comp_sector
case _:
# this component is not a sector based component
# so we do not change it (use original value)
enlarged_sector = map_info.mSectorCount
# enlarge it
map_info.mSectorCount = max(map_info.mSectorCount, enlarged_sector)
PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info)
def finish_component(self) -> None:
"""
Finish up component creation.
Just deselect all objects and select all created components.
"""
UTIL_functions.select_certain_objects(tuple(self.__mObjList))
#endregion
@ -156,7 +201,7 @@ class BBP_OT_add_component(bpy.types.Operator, ComponentSectorParam):
name = "Type",
description = "This component type",
items = _g_EnumHelper_Component.generate_items(),
)
) # type: ignore
def invoke(self, context, event):
wm = context.window_manager
@ -179,12 +224,14 @@ class BBP_OT_add_component(bpy.types.Operator, ComponentSectorParam):
def execute(self, context):
# call general creator
_general_create_component(
creator: _GeneralComponentCreator = _GeneralComponentCreator()
creator.create_component(
_g_EnumHelper_Component.get_selection(self.component_type),
self.general_get_component_sector(),
1, # only create one
lambda _: mathutils.Matrix.Identity(4)
)
creator.finish_component()
return {'FINISHED'}
@staticmethod
@ -218,12 +265,14 @@ class BBP_OT_add_nong_extra_point(bpy.types.Operator, ComponentSectorParam, Comp
# calc percent first
percent: float = 1.0 / self.general_get_component_count()
# create elements
_general_create_component(
creator: _GeneralComponentCreator = _GeneralComponentCreator()
creator.create_component(
PROP_ballance_element.BallanceElementType.P_Extra_Point,
self.general_get_component_sector(),
self.general_get_component_count(),
lambda i: mathutils.Matrix.Rotation(percent * i * math.pi * 2, 4, 'Z')
)
creator.finish_component()
return {'FINISHED'}
@staticmethod
@ -243,22 +292,22 @@ class BBP_OT_add_nong_ventilator(bpy.types.Operator, ComponentSectorParam, Compo
ventilator_count_source: bpy.props.EnumProperty(
name = "Ventilator Count Source",
items = (
items = [
('DEFINED', "Predefined", "Pre-defined ventilator count."),
('CUSTOM', "Custom", "User specified ventilator count."),
),
)
],
) # type: ignore
preset_vetilator_count: bpy.props.EnumProperty(
name = "Preset Count",
description = "Pick preset ventilator count.",
items = (
items = [
# (token, display name, descriptions, icon, index)
('PAPER', 'Paper', 'The ventilator count (1) can push paper ball up.'),
('WOOD', 'Wood', 'The ventilator count (6) can push wood ball up.'),
('STONE', 'Stone', 'The ventilator count (32) can push stone ball up.'),
),
)
],
) # type: ignore
def draw(self, context):
layout = self.layout
@ -286,12 +335,14 @@ class BBP_OT_add_nong_ventilator(bpy.types.Operator, ComponentSectorParam, Compo
case _: raise UTIL_functions.BBPException('invalid enumprop data')
# create elements without any move
_general_create_component(
creator: _GeneralComponentCreator = _GeneralComponentCreator()
creator.create_component(
PROP_ballance_element.BallanceElementType.P_Modul_18,
self.general_get_component_sector(),
count,
lambda _: mathutils.Matrix.Identity(4)
)
creator.finish_component()
return {'FINISHED'}
@staticmethod
@ -319,7 +370,7 @@ class BBP_OT_add_tilting_block_series(bpy.types.Operator, ComponentSectorParam,
min = 0.0, max = 100.0,
soft_min = 0.0, soft_max = 12.0,
default = 6.0022,
)
) # type: ignore
def draw(self, context):
layout = self.layout
@ -332,13 +383,14 @@ class BBP_OT_add_tilting_block_series(bpy.types.Operator, ComponentSectorParam,
# get span first
span: float = self.component_span
# create elements
_general_create_component(
creator: _GeneralComponentCreator = _GeneralComponentCreator()
creator.create_component(
PROP_ballance_element.BallanceElementType.P_Modul_41,
self.general_get_component_sector(),
self.general_get_component_count(),
lambda i: mathutils.Matrix.Translation(mathutils.Vector((span * i, 0.0, 0.0))) # move with extra delta in x axis
)
creator.finish_component()
return {'FINISHED'}
@staticmethod
@ -350,6 +402,64 @@ class BBP_OT_add_tilting_block_series(bpy.types.Operator, ComponentSectorParam,
)
)
class BBP_OT_add_swing_series(bpy.types.Operator, ComponentSectorParam, ComponentCountParam):
"""Add Swing Series"""
bl_idname = "bbp.add_swing_series"
bl_label = "Swing Series"
bl_options = {'REGISTER', 'UNDO'}
component_span: bpy.props.FloatProperty(
name = "Span",
description = "The distance between each swing",
min = 0.0, max = 100.0,
soft_min = 0.0, soft_max = 30.0,
default = 15.0,
) # type: ignore
staggered_swing: bpy.props.BoolProperty(
name = 'Staggered',
description = 'Whether place Swing staggered. Staggered Swing accept any ball however Non-Staggered Swing only accept Wood and Paper ball.',
default = True
) # type: ignore
def draw(self, context):
layout = self.layout
self.draw_component_sector_params(layout)
self.draw_component_count_params(layout)
layout.prop(self, 'component_span')
layout.prop(self, 'staggered_swing')
def execute(self, context):
# create objects and move it by delta
# get span first
span: float = self.component_span
staggered: bool = self.staggered_swing
# create elements
creator: _GeneralComponentCreator = _GeneralComponentCreator()
creator.create_component(
PROP_ballance_element.BallanceElementType.P_Modul_08,
self.general_get_component_sector(),
self.general_get_component_count(),
lambda i: mathutils.Matrix.LocRotScale(
# move with extra delta in x axis
mathutils.Vector((span * i, 0.0, 0.0)),
# and rotate 90 degree for even one if staggered placement.
mathutils.Euler((0, 0, math.radians(180) if (staggered and (i % 2 == 0)) else 0)),
None
)
)
creator.finish_component()
return {'FINISHED'}
@staticmethod
def draw_blc_menu(layout: bpy.types.UILayout):
layout.operator(
BBP_OT_add_swing_series.bl_idname,
icon_value = UTIL_icons_manager.get_component_icon(
PROP_ballance_element.get_ballance_element_name(PROP_ballance_element.BallanceElementType.P_Modul_08)
)
)
class BBP_OT_add_ventilator_series(bpy.types.Operator, ComponentSectorParam, ComponentCountParam):
"""Add Ventilator Series"""
bl_idname = "bbp.add_ventilator_series"
@ -363,7 +473,7 @@ class BBP_OT_add_ventilator_series(bpy.types.Operator, ComponentSectorParam, Com
min = 0.0, max = 100.0,
soft_min = 0.0, soft_max = 50.0,
default = (0.0, 0.0, 15.0),
)
) # type: ignore
def draw(self, context):
layout = self.layout
@ -376,13 +486,14 @@ class BBP_OT_add_ventilator_series(bpy.types.Operator, ComponentSectorParam, Com
# get translation first
translation: mathutils.Vector = mathutils.Vector(self.component_translation)
# create elements
_general_create_component(
creator: _GeneralComponentCreator = _GeneralComponentCreator()
creator.create_component(
PROP_ballance_element.BallanceElementType.P_Modul_18,
self.general_get_component_sector(),
self.general_get_component_count(),
lambda i: mathutils.Matrix.Translation(i * translation) # move with extra translation
)
creator.finish_component()
return {'FINISHED'}
@staticmethod
@ -450,20 +561,21 @@ class BBP_OT_add_sector_component_pair(bpy.types.Operator, ComponentSectorParam)
# add elements
# create checkpoint
_general_create_component(
creator: _GeneralComponentCreator = _GeneralComponentCreator()
creator.create_component(
checkp_ty,
checkp_sector,
1, # only create one
lambda _: mathutils.Matrix.Identity(4)
)
# create resetpoint
_general_create_component(
creator.create_component(
resetp_ty,
resetp_sector,
1, # only create one
lambda _: mathutils.Matrix.Translation(mathutils.Vector((0.0, 0.0, resetp_offset))) # apply resetpoint offset
)
creator.finish_component()
return {'FINISHED'}
@staticmethod
@ -477,17 +589,19 @@ class BBP_OT_add_sector_component_pair(bpy.types.Operator, ComponentSectorParam)
#endregion
def register():
def register() -> None:
bpy.utils.register_class(BBP_OT_add_component)
bpy.utils.register_class(BBP_OT_add_nong_extra_point)
bpy.utils.register_class(BBP_OT_add_nong_ventilator)
bpy.utils.register_class(BBP_OT_add_tilting_block_series)
bpy.utils.register_class(BBP_OT_add_swing_series)
bpy.utils.register_class(BBP_OT_add_ventilator_series)
bpy.utils.register_class(BBP_OT_add_sector_component_pair)
def unregister():
def unregister() -> None:
bpy.utils.unregister_class(BBP_OT_add_sector_component_pair)
bpy.utils.unregister_class(BBP_OT_add_ventilator_series)
bpy.utils.unregister_class(BBP_OT_add_swing_series)
bpy.utils.unregister_class(BBP_OT_add_tilting_block_series)
bpy.utils.unregister_class(BBP_OT_add_nong_ventilator)
bpy.utils.unregister_class(BBP_OT_add_nong_extra_point)

View File

@ -492,6 +492,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)
# select created object
UTIL_functions.select_certain_objects((obj, ))
# return rail
return obj
@ -680,7 +682,7 @@ def _create_screw_rail(
#endregion
def register():
def register() -> None:
bpy.utils.register_class(BBP_OT_add_rail_section)
bpy.utils.register_class(BBP_OT_add_transition_section)
@ -693,7 +695,7 @@ def register():
bpy.utils.register_class(BBP_OT_add_side_spiral_rail)
def unregister():
def unregister() -> None:
bpy.utils.unregister_class(BBP_OT_add_side_spiral_rail)
bpy.utils.unregister_class(BBP_OT_add_spiral_rail)
bpy.utils.unregister_class(BBP_OT_add_arc_rail)

View File

@ -1,9 +1,9 @@
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_ballance_map_info
from .PyBMap import bmap_wrapper as bmap
# define global tex save opt blender enum prop helper
@ -20,19 +20,26 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
description = "Decide how texture saved if texture is specified as Use Global as its Save Options.",
items = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.generate_items(),
default = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.to_selection(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL)
)
) # type: ignore
use_compress: bpy.props.BoolProperty(
name="Use Compress",
description = "Whether use ZLib to compress result when saving composition.",
default = True,
)
) # type: ignore
compress_level: bpy.props.IntProperty(
name = "Compress Level",
description = "The ZLib compress level used by Virtools Engine when saving composition.",
min = 1, max = 9,
default = 5,
)
) # type: ignore
successive_sector: bpy.props.BoolProperty(
name="Successive Sector",
description = "Whether order exporter to use document specified sector count to make sure sector is successive.",
default = True,
) # type: ignore
@classmethod
def poll(self, context):
@ -59,6 +66,7 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
_g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.get_selection(self.texture_save_opt),
self.use_compress,
self.compress_level,
self.successive_sector,
objls
)
@ -84,6 +92,14 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
if self.use_compress:
box.prop(self, 'compress_level')
# show sector info to notice user
layout.separator()
layout.label(text = 'Ballance Params')
box = layout.box()
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
box.prop(self, 'successive_sector')
box.label(text = f'Map Sectors: {map_info.mSectorCount}')
_TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject]
_TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh]
_TMaterialPair = tuple[bpy.types.Material, bmap.BMMaterial]
@ -95,6 +111,7 @@ def _export_virtools(
texture_save_opt_: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS,
use_compress_: bool,
compress_level_: int,
successive_sector_: bool,
export_objects: tuple[bpy.types.Object, ...]
) -> None:
@ -114,7 +131,7 @@ def _export_virtools(
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)
_export_virtools_groups(writer, progress, successive_sector_, obj3d_crets)
mesh_crets: tuple[_TMeshPair, ...] = _export_virtools_3dobjects(
writer, progress, obj3d_crets)
# export mesh
@ -163,6 +180,7 @@ def _prepare_virtools_3dobjects(
def _export_virtools_groups(
writer: bmap.BMFileWriter,
progress: ProgressReport,
successive_sector: bool,
obj3d_crets: tuple[_TObj3dPair, ...]
) -> None:
# create virtools group
@ -170,8 +188,24 @@ 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:
map_info: PROP_ballance_map_info.RawBallanceMapInfo
map_info = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
for i in range(map_info.mSectorCount):
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 +214,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
@ -471,73 +505,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

@ -2,8 +2,8 @@ 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_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):
@ -344,10 +344,12 @@ 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")
@ -357,6 +359,12 @@ def _import_virtools_groups(
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)
@ -370,6 +378,11 @@ 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()

View File

@ -0,0 +1,36 @@
import bpy
from . import PROP_virtools_material, PROP_preferences
class BBP_OT_fix_all_material(bpy.types.Operator):
"""Fix All Materials by Its Referred Ballance Texture Name."""
bl_idname = "bbp.fix_all_material"
bl_label = "Fix Material"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_confirm(self, event)
def execute(self, context):
# do work and count
counter_all: int = 0
counter_suc: int = 0
for mtl in bpy.data.materials:
counter_all += 1
if PROP_virtools_material.fix_material(mtl):
PROP_virtools_material.apply_to_blender_material(mtl)
counter_suc += 1
# report and return
self.report({'INFO'}, f'Fix {counter_suc}/{counter_all} materials.')
return {'FINISHED'}
def register() -> None:
bpy.utils.register_class(BBP_OT_fix_all_material)
def unregister() -> None:
bpy.utils.unregister_class(BBP_OT_fix_all_material)

View File

@ -264,10 +264,10 @@ def _get_object_ref_point(obj: bpy.types.Object, corners: tuple[mathutils.Vector
#endregion
def register():
def register() -> None:
bpy.utils.register_class(BBP_PG_legacy_align_history)
bpy.utils.register_class(BBP_OT_legacy_align)
def unregister():
def unregister() -> None:
bpy.utils.unregister_class(BBP_OT_legacy_align)
bpy.utils.unregister_class(BBP_PG_legacy_align_history)

View File

@ -89,12 +89,12 @@ def _rename_core(
UTIL_icons_manager.BlenderPresetIcons.Info.value
)
def register():
def register() -> None:
bpy.utils.register_class(BBP_OT_regulate_objects_name)
bpy.utils.register_class(BBP_OT_auto_grouping)
bpy.utils.register_class(BBP_OT_convert_to_imengyu)
def unregister():
def unregister() -> None:
bpy.utils.unregister_class(BBP_OT_convert_to_imengyu)
bpy.utils.unregister_class(BBP_OT_auto_grouping)
bpy.utils.unregister_class(BBP_OT_regulate_objects_name)

View File

@ -183,14 +183,14 @@ class BBP_OT_clear_objects_virtools_group(bpy.types.Operator):
#endregion
def register():
def register() -> None:
bpy.utils.register_class(BBP_OT_select_object_by_virtools_group)
bpy.utils.register_class(BBP_OT_add_objects_virtools_group)
bpy.utils.register_class(BBP_OT_rm_objects_virtools_group)
bpy.utils.register_class(BBP_OT_clear_objects_virtools_group)
def unregister():
def unregister() -> None:
bpy.utils.unregister_class(BBP_OT_clear_objects_virtools_group)
bpy.utils.unregister_class(BBP_OT_rm_objects_virtools_group)
bpy.utils.unregister_class(BBP_OT_add_objects_virtools_group)

View File

@ -16,6 +16,23 @@ class FlattenMethod(enum.IntEnum):
# Not only V axis, but also U axis' continuity will been make sure.
Wood = enum.auto()
class NeighborType(enum.IntEnum):
"""
NeighborType is used by special flatten uv to describe the direction of neighbor.
Normally we find neighbor by +V, +U direction (in UV world), these neighbors are "forward" neighbors and marked as Forward.
But if we try finding neighbor by -V, -U direction, we call these neighbors are "backward" neighbors,
and marked as VerticalBackward or HorizontalBackward by its direction.
The UV of Backward neighbor need to be processed specially so we need distinguish them with Forward neighbors.
"""
# +V, +U direction neighbor.
Forward = enum.auto()
# -V direction neighbor.
VerticalBackward = enum.auto()
# -U direction neighbor.
HorizontalBackward = enum.auto()
class FlattenParam():
mReferenceEdge: int
mUseRefPoint: bool
@ -283,9 +300,26 @@ def _specific_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerIte
# prepare a function to check whether face is valid
def face_validator(f: bmesh.types.BMFace) -> bool:
# specify using external failed counter
nonlocal failed
# a valid face must be
# selected, not processed, and should be rectangle
return f.select and (not f.tag) and (len(f.loops) == 4)
# we check selection first
# then check tag. if tag == True, it mean this face has been processed.
if not f.select or f.tag: return False
# now this face can be processed, we need check whether it is rectangle
if len(f.loops) == 4:
# yes it is rectangle
return True
else:
# no, it is not rectangle
# we need mark its tag as True to prevent any possible recursive checking
# because it definately can not be processed in future.
f.tag = True
# then we report this face failed
failed = failed + 1
# return false
return False
# prepare face getter which will be used when stack is empty
face_getter: typing.Iterator[bmesh.types.BMFace] = filter(
lambda f: face_validator(f),
@ -321,7 +355,7 @@ def _specific_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerIte
return neighbor_f
# prepare face stack.
# NOTE: all face inserted into this stack should be marked as processed first.
face_stack: collections.deque[tuple[bmesh.types.BMFace, mathutils.Vector]] = collections.deque()
face_stack: collections.deque[tuple[bmesh.types.BMFace, mathutils.Vector, NeighborType]] = collections.deque()
# start process faces
while True:
# if no item in face stack, pick one from face getter and mark it as processed
@ -330,14 +364,13 @@ def _specific_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerIte
try:
f = next(face_getter)
f.tag = True
face_stack.append((f, mathutils.Vector((0, 0))))
face_stack.append((f, mathutils.Vector((0, 0)), NeighborType.Forward))
except StopIteration:
break
# pick one face from stack and process it
(face, face_offset) = face_stack.pop()
(face, face_offset, face_backward) = face_stack.pop()
_flatten_face_uv(face, uv_layer, flatten_param, face_offset)
print(face_offset)
# get 4 point uv because we need use them later
# NOTE: 4 uv point following this order
@ -363,9 +396,9 @@ def _specific_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerIte
uv2 = _get_face_vertex_uv(face, uv_layer, ind2)
uv3 = _get_face_vertex_uv(face, uv_layer, ind3)
# insert horizontal neighbor if we are wood flatten uv
# correct rectangle shape when in wood mode
if flatten_param.mFlattenMethod == FlattenMethod.Wood:
# first, make its uv geometry to rectangle from a trapezium.
# make its uv geometry to rectangle from a trapezium.
# get the average U factor from its right edge.
# and make top + bottom uv edge be parallel with U axis by using left edge V factor.
average_u = (uv2[0] + uv3[0]) / 2
@ -373,22 +406,67 @@ def _specific_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerIte
uv3 = (average_u, uv0[1])
_set_face_vertex_uv(face, uv_layer, ind2, uv2)
_set_face_vertex_uv(face, uv_layer, ind3, uv3)
# then, try getting its right neighbor
# do backward correction
# in backward mode, we can not know how many space backward one will occupied,
# thus we can not pass it by offset because we don't know the offset,
# so we only can patch it after computing its real size.
if face_backward != NeighborType.Forward:
if face_backward == NeighborType.VerticalBackward:
# in vertical backward patch,
# minus self height for all uv.
self_height: float = uv1[1] - uv0[1]
uv0 = (uv0[0], uv0[1] - self_height)
uv1 = (uv1[0], uv1[1] - self_height)
uv2 = (uv2[0], uv2[1] - self_height)
uv3 = (uv3[0], uv3[1] - self_height)
if face_backward == NeighborType.HorizontalBackward:
# in horizontal backward patch, minus self width for all uv.
# because we have process rectangle shape issue before this,
# so we can pick uv2 or uv3 to get width directly.
self_width: float = uv3[0] - uv0[0]
uv0 = (uv0[0] - self_width, uv0[1])
uv1 = (uv1[0] - self_width, uv1[1])
uv2 = (uv2[0] - self_width, uv2[1])
uv3 = (uv3[0] - self_width, uv3[1])
# set modified uv to geometry
_set_face_vertex_uv(face, uv_layer, ind0, uv0)
_set_face_vertex_uv(face, uv_layer, ind1, uv1)
_set_face_vertex_uv(face, uv_layer, ind2, uv2)
_set_face_vertex_uv(face, uv_layer, ind3, uv3)
# insert horizontal neighbor only in wood mode.
if flatten_param.mFlattenMethod == FlattenMethod.Wood:
# insert right neighbor (forward)
r_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind2, ind0)
if r_face is not None:
# mark it as processed
r_face.tag = True
# insert face with extra horizontal offset.
face_stack.append((r_face, mathutils.Vector((uv3[0], uv3[1]))))
face_stack.append((r_face, mathutils.Vector((uv3[0], uv3[1])), NeighborType.Forward))
# insert left neighbor (backward)
# swap the index param of neighbor getter
l_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind0, ind2)
if l_face is not None:
l_face.tag = True
# pass origin pos, and order backward correction
face_stack.append((l_face, mathutils.Vector((uv0[0], uv0[1])), NeighborType.HorizontalBackward))
# insert vertical neighbor
# insert top neighbor (forward)
t_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind1, ind3)
if t_face is not None:
# mark it as processed
t_face.tag = True
# insert face with extra vertical offset.
face_stack.append((t_face, mathutils.Vector((uv1[0], uv1[1]))))
face_stack.append((t_face, mathutils.Vector((uv1[0], uv1[1])), NeighborType.Forward))
# insert bottom neighbor (backward)
# swap the index param of neighbor getter
b_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind3, ind1)
if b_face is not None:
b_face.tag = True
# pass origin pos, and order backward correction
face_stack.append((b_face, mathutils.Vector((uv0[0], uv0[1])), NeighborType.VerticalBackward))
return failed
@ -420,10 +498,13 @@ def _flatten_face_uv(face: bmesh.types.BMFace, uv_layer: bmesh.types.BMLayerItem
vec1.normalize()
# get z axis
new_z_axis: mathutils.Vector = new_y_axis.cross(vec1)
new_z_axis: mathutils.Vector = vec1.cross(new_y_axis)
new_z_axis.normalize()
if not any(round(v, 7) for v in new_z_axis): # if z is a zero vector, use face normal instead
# if z is a zero vector, use face normal instead
# please note we need use inverted face normal.
if not any(round(v, 7) for v in new_z_axis):
new_z_axis = typing.cast(mathutils.Vector, face.normal).normalized()
new_z_axis.negate()
# get x axis
new_x_axis: mathutils.Vector = new_y_axis.cross(new_z_axis)

View File

@ -6,7 +6,7 @@ from . import UTIL_virtools_types, UTIL_icons_manager, UTIL_functions
class BBP_OT_rail_uv(bpy.types.Operator):
"""Create UV for Rail as Ballance Showen (TT_ReflectionMapping)"""
bl_idname = "bbp.rail_uv"
bl_label = "Create Rail UV"
bl_label = "Rail UV"
bl_options = {'UNDO'}
@classmethod

View File

@ -393,7 +393,7 @@ class BBP_PT_ballance_elements(bpy.types.Panel):
#endregion
def register():
def register() -> None:
# register all classes
bpy.utils.register_class(BBP_PG_ballance_element)
bpy.utils.register_class(BBP_UL_ballance_elements)
@ -404,7 +404,7 @@ def register():
bpy.types.Scene.ballance_elements = bpy.props.CollectionProperty(type = BBP_PG_ballance_element)
bpy.types.Scene.active_ballance_elements = bpy.props.IntProperty()
def unregister():
def unregister() -> None:
# del from scene metadata
del bpy.types.Scene.active_ballance_elements
del bpy.types.Scene.ballance_elements

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

@ -283,7 +283,7 @@ class BBP_PT_bme_materials(bpy.types.Panel):
#endregion
def register():
def register() -> None:
# register all classes
bpy.utils.register_class(BBP_PG_bme_material)
bpy.utils.register_class(BBP_UL_bme_materials)
@ -294,7 +294,7 @@ def register():
bpy.types.Scene.bme_materials = bpy.props.CollectionProperty(type = BBP_PG_bme_material)
bpy.types.Scene.active_bme_materials = bpy.props.IntProperty()
def unregister():
def unregister() -> None:
# del from scene metadata
del bpy.types.Scene.active_bme_materials
del bpy.types.Scene.bme_materials

View File

@ -42,10 +42,10 @@ def get_export_object() -> bpy.types.Object:
def draw_export_object(layout: bpy.types.UILayout) -> None:
layout.prop(get_ptrprop_resolver(), 'export_object')
def register():
def register() -> None:
bpy.utils.register_class(BBP_PG_ptrprop_resolver)
bpy.types.Scene.bbp_ptrprop_resolver = bpy.props.PointerProperty(type = BBP_PG_ptrprop_resolver)
def unregister():
def unregister() -> None:
del bpy.types.Scene.bbp_ptrprop_resolver
bpy.utils.unregister_class(BBP_PG_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
@ -396,7 +396,7 @@ class BBP_PT_virtools_groups(bpy.types.Panel):
#endregion
def register():
def register() -> None:
# register all classes
bpy.utils.register_class(BBP_PG_virtools_group)
bpy.utils.register_class(BBP_UL_virtools_groups)
@ -405,14 +405,14 @@ def register():
bpy.utils.register_class(BBP_OT_clear_virtools_groups)
bpy.utils.register_class(BBP_PT_virtools_groups)
# add into scene metadata
# add into object metadata
bpy.types.Object.virtools_groups = bpy.props.CollectionProperty(type = BBP_PG_virtools_group)
bpy.types.Object.active_virtools_groups = bpy.props.IntProperty()
def unregister():
# del from scene metadata
del bpy.types.Scene.active_virtools_groups
del bpy.types.Scene.virtools_groups
def unregister() -> None:
# del from object metadata
del bpy.types.Object.active_virtools_groups
del bpy.types.Object.virtools_groups
bpy.utils.unregister_class(BBP_PT_virtools_groups)
bpy.utils.unregister_class(BBP_OT_clear_virtools_groups)

View File

@ -1,5 +1,5 @@
import bpy
import typing, enum, copy
import typing, enum, copy, os
from . import UTIL_virtools_types, UTIL_functions, UTIL_ballance_texture, UTIL_file_browser
from . import PROP_virtools_texture, PROP_preferences
@ -133,7 +133,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
max = 1.0,
size = 3,
default = RawVirtoolsMaterial.cDefaultAmbient.to_const_rgb()
)
) # type: ignore
diffuse: bpy.props.FloatVectorProperty(
name = "Diffuse",
@ -143,7 +143,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
max = 1.0,
size = 4,
default = RawVirtoolsMaterial.cDefaultDiffuse.to_const_rgba()
)
) # type: ignore
specular: bpy.props.FloatVectorProperty(
name = "Specular",
@ -153,7 +153,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
max = 1.0,
size = 3,
default = RawVirtoolsMaterial.cDefaultSpecular.to_const_rgb()
)
) # type: ignore
emissive: bpy.props.FloatVectorProperty(
name = "Emissive",
@ -163,7 +163,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
max = 1.0,
size = 3,
default = RawVirtoolsMaterial.cDefaultEmissive.to_const_rgb()
)
) # type: ignore
specular_power: bpy.props.FloatProperty(
name = "Power",
@ -171,13 +171,13 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
min = 0.0,
max = 100.0,
default = RawVirtoolsMaterial.cDefaultSpecularPower
)
) # type: ignore
texture: bpy.props.PointerProperty(
type = bpy.types.Image,
name = "Texture",
description = "Texture of the material"
)
) # type: ignore
texture_border_color: bpy.props.FloatVectorProperty(
name = "Border Color",
@ -187,89 +187,89 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
max = 1.0,
size = 4,
default = RawVirtoolsMaterial.cDefaultTextureBorderColor.to_const_rgba()
)
) # type: ignore
texture_blend_mode: bpy.props.EnumProperty(
name = "Texture Blend",
description = "Texture blend mode",
items = _g_Helper_VXTEXTURE_BLENDMODE.generate_items(),
default = _g_Helper_VXTEXTURE_BLENDMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureBlendMode)
)
) # type: ignore
texture_min_mode: bpy.props.EnumProperty(
name = "Filter Min",
description = "Texture filter mode when the texture is minified",
items = _g_Helper_VXTEXTURE_FILTERMODE.generate_items(),
default = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureMinMode)
)
) # type: ignore
texture_mag_mode: bpy.props.EnumProperty(
name = "Filter Mag",
description = "Texture filter mode when the texture is magnified",
items = _g_Helper_VXTEXTURE_FILTERMODE.generate_items(),
default = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureMagMode)
)
) # type: ignore
texture_address_mode: bpy.props.EnumProperty(
name = "Address Mode",
description = "The address mode controls how the texture coordinates outside the range 0..1",
items = _g_Helper_VXTEXTURE_ADDRESSMODE.generate_items(),
default = _g_Helper_VXTEXTURE_ADDRESSMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureAddressMode)
)
) # type: ignore
source_blend: bpy.props.EnumProperty(
name = "Source Blend",
description = "Source blend factor",
items = _g_Helper_VXBLEND_MODE.generate_items(),
default = _g_Helper_VXBLEND_MODE.to_selection(RawVirtoolsMaterial.cDefaultSourceBlend)
)
) # type: ignore
dest_blend: bpy.props.EnumProperty(
name = "Destination Blend",
description = "Destination blend factor",
items = _g_Helper_VXBLEND_MODE.generate_items(),
default = _g_Helper_VXBLEND_MODE.to_selection(RawVirtoolsMaterial.cDefaultDestBlend)
)
) # type: ignore
fill_mode: bpy.props.EnumProperty(
name = "Fill Mode",
description = "Fill mode",
items = _g_Helper_VXFILL_MODE.generate_items(),
default = _g_Helper_VXFILL_MODE.to_selection(RawVirtoolsMaterial.cDefaultFillMode)
)
) # type: ignore
shade_mode: bpy.props.EnumProperty(
name = "Shade Mode",
description = "Shade mode",
items = _g_Helper_VXSHADE_MODE.generate_items(),
default = _g_Helper_VXSHADE_MODE.to_selection(RawVirtoolsMaterial.cDefaultShadeMode)
)
) # type: ignore
enable_alpha_test: bpy.props.BoolProperty(
name = "Alpha Test",
description = "Whether the alpha test is enabled",
default = RawVirtoolsMaterial.cDefaultEnableAlphaTest
)
) # type: ignore
enable_alpha_blend: bpy.props.BoolProperty(
name = "Blend",
description = "Whether alpha blending is enabled or not.",
default = RawVirtoolsMaterial.cDefaultEnableAlphaBlend
)
) # type: ignore
enable_perspective_correction: bpy.props.BoolProperty(
name = "Perspective Correction",
description = "Whether texture perspective correction is enabled",
default = RawVirtoolsMaterial.cDefaultEnablePerspectiveCorrection
)
) # type: ignore
enable_z_write: bpy.props.BoolProperty(
name = "Z-Buffer Write",
description = "Whether writing in ZBuffer is enabled.",
default = RawVirtoolsMaterial.cDefaultEnableZWrite
)
) # type: ignore
enable_two_sided: bpy.props.BoolProperty(
name = "Both Sided",
description = "Whether the material is both sided or not",
default = RawVirtoolsMaterial.cDefaultEnableTwoSided
)
) # type: ignore
alpha_ref: bpy.props.IntProperty(
name = "Alpha Ref Value",
@ -277,21 +277,21 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
min = 0,
max = 255,
default = RawVirtoolsMaterial.cDefaultAlphaRef
)
) # type: ignore
alpha_func: bpy.props.EnumProperty(
name = "Alpha Test Function",
description = "Alpha comparision function",
items = _g_Helper_VXCMPFUNC.generate_items(),
default = _g_Helper_VXCMPFUNC.to_selection(RawVirtoolsMaterial.cDefaultAlphaFunc)
)
) # type: ignore
z_func: bpy.props.EnumProperty(
name = "Z Compare Function",
description = "Z Comparison function",
items = _g_Helper_VXCMPFUNC.generate_items(),
default = _g_Helper_VXCMPFUNC.to_selection(RawVirtoolsMaterial.cDefaultZFunc)
)
) # type: ignore
#region Getter Setter
@ -539,6 +539,330 @@ _g_Helper_MtlPreset: UTIL_functions.EnumPropHelper = UTIL_functions.EnumPropHelp
#endregion
#region Fix Material
def fix_material(mtl: bpy.types.Material) -> bool:
"""!
Fix single Blender material.
@remark The implementation of this function is copied from BallanceVirtoolsHelper/bvh/features/mapping/bmfile_fix_texture.cpp
@param mtl[in] The blender material need to be processed.
@return True if we do a fix, otherwise return False.
"""
# prepare return value first
ret: bool = False
# get raw mtl from this blender mtl first
rawmtl: RawVirtoolsMaterial = get_raw_virtools_material(mtl)
# if no associated texture, return
if rawmtl.mTexture is None: return ret
# get associated texture name
# we do not check whether it is ballance texture here, because the texture might be packed.
# packed ballance texture is not recognised as a valid ballance texture.
filename: str = os.path.basename(UTIL_ballance_texture.get_texture_filepath(rawmtl.mTexture))
# preset some field for raw mtl
# first, we need store its as opaque mode
rawmtl.mEnableAlphaTest = False
rawmtl.mEnableAlphaBlend = False
rawmtl.mEnableTwoSided = False
# and z write must be enabled in default
rawmtl.mEnableZWrite = True
rawmtl.mZFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
rawmtl.mAmbient.from_const_rgb
# route filename
match(filename):
# case 'atari.avi': pass
# case 'atari.bmp': pass
# case 'Ball_LightningSphere1.bmp': pass
# case 'Ball_LightningSphere2.bmp': pass
# case 'Ball_LightningSphere3.bmp': pass
case 'Ball_Paper.bmp':
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mEmissive.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
rawmtl.mSpecularPower = 0.0
ret = True
case 'Ball_Stone.bmp' | 'Ball_Wood.bmp':
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
rawmtl.mEmissive.from_const_rgb((60 / 255.0, 60 / 255.0, 60 / 255.0))
rawmtl.mSpecularPower = 0.0
ret = True
case 'Brick.bmp':
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mEmissive.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
rawmtl.mSpecularPower = 0.0
ret = True
# case 'Button01_deselect.tga': pass
# case 'Button01_select.tga': pass
# case 'Button01_special.tga': pass
case 'Column_beige.bmp':
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mDiffuse.from_const_rgb((233 / 255.0, 233 / 255.0, 233 / 255.0))
rawmtl.mSpecular.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mEmissive.from_const_rgb((80 / 255.0, 80 / 255.0, 80 / 255.0))
rawmtl.mSpecularPower = 0.0
ret = True
case 'Column_beige_fade.tga':
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mDiffuse.from_const_rgb((233 / 255.0, 233 / 255.0, 233 / 255.0))
rawmtl.mSpecular.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mEmissive.from_const_rgb((80 / 255.0, 80 / 255.0, 80 / 255.0))
rawmtl.mSpecularPower = 0.0
rawmtl.mEnableAlphaTest = False
rawmtl.mAlphaFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_GREATER
rawmtl.mAlphaRef = 1
rawmtl.mEnableAlphaBlend = True
rawmtl.mSourceBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_SRCALPHA
rawmtl.mDestBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_INVSRCALPHA
rawmtl.mEnableZWrite = True
rawmtl.mZFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
ret = True
case 'Column_blue.bmp':
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mDiffuse.from_const_rgb((209 / 255.0, 209 / 255.0, 209 / 255.0))
rawmtl.mSpecular.from_const_rgb((150 / 255.0, 150 / 255.0, 150 / 255.0))
rawmtl.mEmissive.from_const_rgb((80 / 255.0, 80 / 255.0, 80 / 255.0))
rawmtl.mSpecularPower = 31.0
ret = True
# case 'Cursor.tga': pass
# case 'Dome.bmp': pass
# case 'DomeEnvironment.bmp': pass
# case 'DomeShadow.tga': pass
# case 'ExtraBall.bmp': pass
# case 'ExtraParticle.bmp': pass
case 'E_Holzbeschlag.bmp':
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mDiffuse.from_const_rgb((186 / 255.0, 186 / 255.0, 186 / 255.0))
rawmtl.mSpecular.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mEmissive.from_const_rgb((65 / 255.0, 65 / 255.0, 65 / 255.0))
rawmtl.mSpecularPower = 0.0
ret = True
# case 'FloorGlow.bmp': pass
case 'Floor_Side.bmp':
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mDiffuse.from_const_rgb((122 / 255.0, 122 / 255.0, 122 / 255.0))
rawmtl.mSpecular.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mEmissive.from_const_rgb((104 / 255.0, 104 / 255.0, 104 / 255.0))
rawmtl.mSpecularPower = 0.0
ret = True
case 'Floor_Top_Border.bmp' | 'Floor_Top_Borderless.bmp' | 'Floor_Top_Checkpoint.bmp' | 'Floor_Top_Flat.bmp' | 'Floor_Top_Profil.bmp' | 'Floor_Top_ProfilFlat.bmp':
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((80 / 255.0, 80 / 255.0, 80 / 255.0))
rawmtl.mEmissive.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mSpecularPower = 100.0
ret = True
# case 'Font_1.tga': pass
# case 'Gravitylogo_intro.bmp': pass
# case 'HardShadow.bmp': pass
case 'Laterne_Glas.bmp':
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
rawmtl.mEmissive.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecularPower = 0.0
ret = True
case 'Laterne_Schatten.tga':
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
rawmtl.mDiffuse.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
rawmtl.mEmissive.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecularPower = 0.0
rawmtl.mEnableAlphaTest = True
rawmtl.mAlphaFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_GREATER
rawmtl.mAlphaRef = 1
rawmtl.mEnableAlphaBlend = True
rawmtl.mSourceBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_SRCALPHA
rawmtl.mDestBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_INVSRCALPHA
rawmtl.mEnableZWrite = True
rawmtl.mZFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
ret = True
case 'Laterne_Verlauf.tga':
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mDiffuse.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mSpecular.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mEmissive.from_const_rgb((59 / 255.0, 59 / 255.0, 59 / 255.0))
rawmtl.mSpecularPower = 0.0
rawmtl.mEnableAlphaTest = True
rawmtl.mAlphaFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_GREATER
rawmtl.mAlphaRef = 1
rawmtl.mEnableAlphaBlend = True
rawmtl.mSourceBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_SRCALPHA
rawmtl.mDestBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_INVSRCALPHA
rawmtl.mEnableZWrite = True
rawmtl.mZFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
rawmtl.mEnableTwoSided = True
ret = True
# case 'Logo.bmp': pass
case 'Metal_stained.bmp':
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
rawmtl.mEmissive.from_const_rgb((65 / 255.0, 65 / 255.0, 65 / 255.0))
rawmtl.mSpecularPower = 25.0
ret = True
# case 'Misc_Ufo.bmp': pass
# case 'Misc_UFO_Flash.bmp': pass
# case 'Modul03_Floor.bmp': pass
# case 'Modul03_Wall.bmp': pass
case 'Modul11_13_Wood.bmp':
rawmtl.mAmbient.from_const_rgb((9 / 255.0, 9 / 255.0, 9 / 255.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
rawmtl.mEmissive.from_const_rgb((70 / 255.0, 70 / 255.0, 70 / 255.0))
rawmtl.mSpecularPower = 50.0
ret = True
case 'Modul11_Wood.bmp':
rawmtl.mAmbient.from_const_rgb((9 / 255.0, 9 / 255.0, 9 / 255.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
rawmtl.mEmissive.from_const_rgb((50 / 255.0, 50 / 255.0, 50 / 255.0))
rawmtl.mSpecularPower = 50.0
ret = True
case 'Modul15.bmp':
rawmtl.mAmbient.from_const_rgb((16 / 255.0, 16 / 255.0, 16 / 255.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
rawmtl.mEmissive.from_const_rgb((70 / 255.0, 70 / 255.0, 70 / 255.0))
rawmtl.mSpecularPower = 100.0
ret = True
case 'Modul16.bmp':
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
rawmtl.mEmissive.from_const_rgb((85 / 255.0, 85 / 255.0, 85 / 255.0))
rawmtl.mSpecularPower = 100.0
ret = True
case 'Modul18.bmp':
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
rawmtl.mEmissive.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mSpecularPower = 25.0
ret = True
case 'Modul18_Gitter.tga':
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
rawmtl.mEmissive.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mSpecularPower = 25.0
rawmtl.mEnableAlphaTest = True
rawmtl.mAlphaFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_GREATER
rawmtl.mAlphaRef = 1
rawmtl.mEnableAlphaBlend = True
rawmtl.mSourceBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_SRCALPHA
rawmtl.mDestBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_INVSRCALPHA
rawmtl.mEnableZWrite = True
rawmtl.mZFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
ret = True
# case 'Modul30_d_Seiten.bmp': pass
# case 'Particle_Flames.bmp': pass
# case 'Particle_Smoke.bmp': pass
# case 'PE_Bal_balloons.bmp': pass
# case 'PE_Bal_platform.bmp': pass
# case 'PE_Ufo_env.bmp': pass
# case 'Pfeil.tga': pass
# case 'P_Extra_Life_Oil.bmp': pass
# case 'P_Extra_Life_Particle.bmp': pass
# case 'P_Extra_Life_Shadow.bmp': pass
case 'Rail_Environment.bmp':
rawmtl.mAmbient.from_const_rgb((0.0, 0.0, 0.0))
rawmtl.mDiffuse.from_const_rgb((100 / 255.0, 118 / 255.0, 133 / 255.0))
rawmtl.mSpecular.from_const_rgb((210 / 255.0, 210 / 255.0, 210 / 255.0))
rawmtl.mEmissive.from_const_rgb((124 / 255.0, 134 / 255.0, 150 / 255.0))
rawmtl.mSpecularPower = 10.0
ret = True
# case 'sandsack.bmp': pass
# case 'SkyLayer.bmp': pass
# case 'Sky_Vortex.bmp': pass
case 'Stick_Bottom.tga':
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
rawmtl.mDiffuse.from_const_rgb((100 / 255.0, 118 / 255.0, 133 / 255.0))
rawmtl.mSpecular.from_const_rgb((210 / 255.0, 210 / 255.0, 210 / 255.0))
rawmtl.mEmissive.from_const_rgb((124 / 255.0, 134 / 255.0, 150 / 255.0))
rawmtl.mSpecularPower = 13.0
rawmtl.mEnableAlphaTest = False
rawmtl.mAlphaFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_GREATER
rawmtl.mAlphaRef = 1
rawmtl.mEnableAlphaBlend = True
rawmtl.mSourceBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_SRCALPHA
rawmtl.mDestBlend = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_INVSRCALPHA
rawmtl.mEnableZWrite = True
rawmtl.mZFunc = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
ret = True
case 'Stick_Stripes.bmp':
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
rawmtl.mEmissive.from_const_rgb((106 / 255.0, 106 / 255.0, 106 / 255.0))
rawmtl.mSpecularPower = 13.0
ret = True
# case 'Target.bmp': pass
case 'Tower_Roof.bmp':
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
rawmtl.mDiffuse.from_const_rgb((218 / 255.0, 218 / 255.0, 218 / 255.0))
rawmtl.mSpecular.from_const_rgb((64 / 255.0, 64 / 255.0, 64 / 255.0))
rawmtl.mEmissive.from_const_rgb((103 / 255.0, 103 / 255.0, 103 / 255.0))
rawmtl.mSpecularPower = 100.0
ret = True
# case 'Trafo_Environment.bmp': pass
# case 'Trafo_FlashField.bmp': pass
# case 'Trafo_Shadow_Big.tga': pass
# case 'Tut_Pfeil01.tga': pass
# case 'Tut_Pfeil_Hoch.tga': pass
# case 'Wolken_intro.tga': pass
case 'Wood_Metal.bmp':
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((229 / 255.0, 229 / 255.0, 229 / 255.0))
rawmtl.mEmissive.from_const_rgb((40 / 255.0, 40 / 255.0, 40 / 255.0))
rawmtl.mSpecularPower = 0.0
ret = True
# case 'Wood_MetalStripes.bmp': pass
# case 'Wood_Misc.bmp': pass
# case 'Wood_Nailed.bmp': pass
# case 'Wood_Old.bmp': pass
case 'Wood_Panel.bmp':
rawmtl.mAmbient.from_const_rgb((2 / 255.0, 2 / 255.0, 2 / 255.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((59 / 255.0, 59 / 255.0, 59 / 255.0))
rawmtl.mEmissive.from_const_rgb((30 / 255.0, 30 / 255.0, 30 / 255.0))
rawmtl.mSpecularPower = 25.0
ret = True
# case 'Wood_Plain.bmp': pass
case 'Wood_Plain2.bmp':
rawmtl.mAmbient.from_const_rgb((25 / 255.0, 25 / 255.0, 25 / 255.0))
rawmtl.mDiffuse.from_const_rgb((1.0, 1.0, 1.0))
rawmtl.mSpecular.from_const_rgb((100 / 255.0, 100 / 255.0, 100 / 255.0))
rawmtl.mEmissive.from_const_rgb((50 / 255.0, 50 / 255.0, 50 / 255.0))
rawmtl.mSpecularPower = 50.0
ret = True
# case 'Wood_Raft.bmp': pass
case _: pass # no mathed
# if changed, set to blender mtl
if ret:
set_raw_virtools_material(mtl, rawmtl)
# return result
return ret
#endregion
#region Operators
class BBP_OT_apply_virtools_material(bpy.types.Operator):
@ -556,6 +880,37 @@ class BBP_OT_apply_virtools_material(bpy.types.Operator):
apply_to_blender_material(mtl)
return {'FINISHED'}
class BBP_OT_fix_single_material(bpy.types.Operator):
"""Fix Active Materials by Its Referred Ballance Texture Name."""
bl_idname = "bbp.fix_single_material"
bl_label = "Fix Material"
bl_options = {'UNDO'}
@classmethod
def poll(cls, context):
if context.material is None: return False
if not PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder(): return False
return True
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_confirm(self, event)
def execute(self, context):
# get mtl and try to fix
mtl: bpy.types.Material = context.material
ret: bool = fix_material(mtl)
# if suc, apply to blender mtl and show info
if ret:
apply_to_blender_material(mtl)
self.report({'INFO'}, 'Fix done.')
else:
# otherwise report warning
self.report({'WARNING'}, 'This material is not suit for fixer.')
return {'FINISHED'}
class BBP_OT_preset_virtools_material(bpy.types.Operator):
"""Preset Virtools Material with Original Ballance Data."""
bl_idname = "bbp.preset_virtools_material"
@ -566,7 +921,7 @@ class BBP_OT_preset_virtools_material(bpy.types.Operator):
name = "Preset",
description = "The preset which you want to apply.",
items = _g_Helper_MtlPreset.generate_items(),
)
) # type: ignore
@classmethod
def poll(cls, context):
@ -586,6 +941,8 @@ class BBP_OT_preset_virtools_material(bpy.types.Operator):
# apply preset to material
preset_virtools_material(mtl, expected_preset)
# and apply to blender
apply_to_blender_material(mtl)
return {'FINISHED'}
class BBP_OT_direct_set_virtools_texture(bpy.types.Operator, UTIL_file_browser.ImportBallanceImage):
@ -659,6 +1016,7 @@ class BBP_PT_virtools_material(bpy.types.Panel):
row = layout.row()
row.operator(BBP_OT_preset_virtools_material.bl_idname, text = 'Preset', icon = "PRESET")
row.operator(BBP_OT_apply_virtools_material.bl_idname, text = 'Apply', icon = "NODETREE")
row.operator(BBP_OT_fix_single_material.bl_idname, text = '', icon = "MODIFIER")
# draw data
layout.label(text="Color Parameters")
@ -715,9 +1073,10 @@ class BBP_PT_virtools_material(bpy.types.Panel):
if props.enable_z_write:
layout.prop(props, 'z_func')
def register():
def register() -> None:
bpy.utils.register_class(BBP_PG_virtools_material)
bpy.utils.register_class(BBP_OT_apply_virtools_material)
bpy.utils.register_class(BBP_OT_fix_single_material)
bpy.utils.register_class(BBP_OT_preset_virtools_material)
bpy.utils.register_class(BBP_OT_direct_set_virtools_texture)
bpy.utils.register_class(BBP_PT_virtools_material)
@ -725,12 +1084,13 @@ def register():
# add into material metadata
bpy.types.Material.virtools_material = bpy.props.PointerProperty(type = BBP_PG_virtools_material)
def unregister():
def unregister() -> None:
# del from material metadata
del bpy.types.Material.virtools_material
bpy.utils.unregister_class(BBP_PT_virtools_material)
bpy.utils.unregister_class(BBP_OT_direct_set_virtools_texture)
bpy.utils.unregister_class(BBP_OT_preset_virtools_material)
bpy.utils.unregister_class(BBP_OT_fix_single_material)
bpy.utils.unregister_class(BBP_OT_apply_virtools_material)
bpy.utils.unregister_class(BBP_PG_virtools_material)

View File

@ -69,14 +69,14 @@ class BBP_PT_virtools_mesh(bpy.types.Panel):
# Register
def register():
def register() -> None:
bpy.utils.register_class(BBP_PG_virtools_mesh)
bpy.utils.register_class(BBP_PT_virtools_mesh)
# add into mesh metadata
bpy.types.Mesh.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh)
def unregister():
def unregister() -> None:
# remove from metadata
del bpy.types.Mesh.virtools_mesh

View File

@ -192,13 +192,13 @@ def get_nonballance_texture_preset() -> RawVirtoolsTexture:
#endregion
def register():
def register() -> None:
bpy.utils.register_class(BBP_PG_virtools_texture)
# add into image metadata
bpy.types.Image.virtools_texture = bpy.props.PointerProperty(type = BBP_PG_virtools_texture)
def unregister():
def unregister() -> None:
# del from image metadata
del bpy.types.Image.virtools_texture

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

@ -68,6 +68,18 @@ def add_into_scene_and_move_to_cursor(obj: bpy.types.Object):
move_to_cursor(obj)
def select_certain_objects(objs: tuple[bpy.types.Object, ...]) -> None:
# deselect all objects first
bpy.ops.object.select_all(action = 'DESELECT')
# if no objects, return
if len(objs) == 0: return
# set selection for each object
for obj in objs:
obj.select_set(True)
# select first object as active object
bpy.context.view_layer.objects.active = objs[0]
class EnumPropHelper():
"""
These class contain all functions related to EnumProperty, including generating `items`,

View File

@ -66,7 +66,7 @@ def get_group_icon(name: str) -> int | None:
#endregion
def register():
def register() -> None:
global _g_IconsManager
global _g_EmptyIcon
global _g_BmeIconsMap, _g_ComponentIconsMap, _g_GroupIconsMap
@ -100,7 +100,7 @@ def register():
_g_GroupIconPrefix
)
def unregister():
def unregister() -> None:
global _g_IconsManager
global _g_EmptyIcon
global _g_BmeIconsMap, _g_ComponentIconsMap, _g_GroupIconsMap

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
@ -360,14 +395,16 @@ class VirtoolsGroupConvention():
gp.add_group('Phys_Floors')
gp.add_group('Sound_HitID_01')
gp.add_group('Sound_RollID_01')
# floor type also need group into shadow group.
gp.add_group('Shadow')
case BallanceObjectType.RAIL:
gp.add_group('Phys_FloorRails')
gp.add_group('Sound_HitID_02')
gp.add_group('Sound_RollID_02')
case BallanceObjectType.WOOD:
gp.add_group('Phys_Floors')
gp.add_group('Sound_HitID_03')
gp.add_group('Sound_RollID_03')
case BallanceObjectType.WOOD:
gp.add_group('Phys_Floors')
gp.add_group('Sound_HitID_02')
gp.add_group('Sound_RollID_02')
case BallanceObjectType.STOPPER:
gp.add_group('Phys_FloorStopper')
@ -375,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:
@ -458,9 +491,9 @@ class YYCToolchainConvention():
case BallanceObjectType.LEVEL_END:
return 'PE_Balloon_01'
case BallanceObjectType.CHECKPOINT:
return f'PR_Resetpoint_{info.mSector:0>2d}'
case BallanceObjectType.RESETPOINT:
return f'PC_TwoFlames_{info.mSector:0>2d}'
case BallanceObjectType.RESETPOINT:
return f'PR_Resetpoint_{info.mSector:0>2d}'
case BallanceObjectType.DEPTH_CUBE:
return 'DepthCubes_'

View File

@ -29,9 +29,11 @@ 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_group
from . import PROP_ballance_element, PROP_bme_material, PROP_ballance_map_info
from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools
from . import OP_UV_flatten_uv, OP_UV_rail_uv
from . import OP_MTL_fix_material
from . import OP_ADDS_component, OP_ADDS_bme, OP_ADDS_rail
from . import OP_OBJECT_legacy_align, OP_OBJECT_virtools_group, OP_OBJECT_naming_convention
@ -55,6 +57,9 @@ class BBP_MT_View3DMenu(bpy.types.Menu):
layout.separator()
layout.label(text = 'Select', icon = 'SELECT_SET')
layout.operator(OP_OBJECT_virtools_group.BBP_OT_select_object_by_virtools_group.bl_idname)
layout.separator()
layout.label(text = 'Material', icon = 'MATERIAL')
layout.operator(OP_MTL_fix_material.BBP_OT_fix_all_material.bl_idname)
class BBP_MT_AddBmeMenu(bpy.types.Menu):
"""Add Ballance Floor"""
@ -107,6 +112,7 @@ class BBP_MT_AddComponentsMenu(bpy.types.Menu):
layout.separator()
layout.label(text = "Series Components")
OP_ADDS_component.BBP_OT_add_tilting_block_series.draw_blc_menu(layout)
OP_ADDS_component.BBP_OT_add_swing_series.draw_blc_menu(layout)
OP_ADDS_component.BBP_OT_add_ventilator_series.draw_blc_menu(layout)
layout.separator()
@ -212,6 +218,7 @@ def register() -> None:
PROP_virtools_group.register()
PROP_ballance_element.register()
PROP_bme_material.register()
PROP_ballance_map_info.register()
OP_IMPORT_bmfile.register()
OP_EXPORT_bmfile.register()
@ -220,6 +227,9 @@ def register() -> None:
OP_UV_rail_uv.register()
OP_UV_flatten_uv.register()
OP_MTL_fix_material.register()
OP_ADDS_component.register()
OP_ADDS_bme.register()
OP_ADDS_rail.register()
@ -256,6 +266,9 @@ def unregister() -> None:
OP_ADDS_rail.unregister()
OP_ADDS_bme.unregister()
OP_ADDS_component.unregister()
OP_MTL_fix_material.unregister()
OP_UV_flatten_uv.unregister()
OP_UV_rail_uv.unregister()
@ -264,6 +277,7 @@ 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()

View File

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

@ -0,0 +1,4 @@
# Ballance Properties
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# Add Floor
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# Compile and Distribute Plugin
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# Add Component
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# Configure Plugin
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# Group Operation
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# Import and Export Virtools Document
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -1,7 +1,7 @@
# Ballance Blender Plugin User Manual
!!! info "Work in Progress"
This part of manual still work in progress.
!!! info "May Outdated"
This document has been translated from other languages and may not always be up to date.
Welcome to the Ballance Blender Plugin, the user manual for the free and open source Ballance map creation suite.
@ -13,22 +13,27 @@ Therefore, choosing Blender with BBP for mapping is not only choosing freedom an
## Getting Started
* Installing Plugin
* Configuring Plugin
* [Install Plugin](./install-plugin.md)
* [Configure Plugin](./configure-plugin.md)
## Basics
## Features
* Virtools Properties
* Importing and Exporting Virtools Documents
* Operating by Groups
* Advanced UV
* Adding Prefabricated Structures
* Adding Rails
* Adding Components
* [Virtools Properties](./virtools-properties.md)
* [Ballance Properties](./ballance-properties.md)
* [Import and Export Virtools Document](./import-export-virtools.md)
* [Group Operation](./group-operations.md)
* [Legacy Alignment](./legacy-align.md)
* [Naming Convention](./naming-convention.md)
* [UV Mapping](./uv-mapping.md)
* [Add Floor](./bme-adder.md)
* [Add Rail](./rail-adder.md)
* [Add Component](./component-adder.md)
## Advanced
## Misc
* Compiling and distributing plugins
* [Compile and Distribute Plugin](./compile-distribute-plugin.md)
* [Report Issue](./report-bugs.md)
* [Technical Information](./tech-infos.md)
!!! info "These are not all"
This manual only documents the relevant operations regarding this plugin and does not explain how to make a Ballance map here. For detailed information on how to make a Ballance map with Blender and BBP, please search for content on Bilibili or YouTube.

View File

@ -0,0 +1,4 @@
# Install Plugin
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# Legacy Alignment
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# Naming Convention
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# Add Rail
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# Report Issue
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# Technical Information
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# UV Mapping
!!! info "Work in Progress"
This part of manual still work in progress.

View File

@ -0,0 +1,4 @@
# Virtools Properties
!!! info "Work in Progress"
This part of manual still work in progress.

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 212 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 744 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 573 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

BIN
docs/docs/imgs/grouping.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 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文档章节。
你需要做的唯一一件事就是在导出最终地图前检查此字段是否是你期望的小节数。需要注意的是尽管你可以在创建地图的一开始就设置此字段然而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

@ -0,0 +1,30 @@
# 添加路面
## 开始生成
在3D视图中点击`Add - Floors`可展开添加机关菜单。菜单如下图所示。
![](../imgs/bme-adder.png)
点击菜单后可以在弹出的子菜单中查看所有受支持的路面类型。其名称和图标提示了它所要创建路面的样式与形状。
!!! info "BME是可扩展的"
BME的路面添加器是可扩展的菜单中的每一个项实际上都由一组JSON数据描述。您可以阅读[技术信息](./tech-infos.md)章节来了解我们是如何编写这些JSON的甚至您还可以根据你的需求自行扩展BME可创建的路面种类。
## 配置路面
点击其中一个路面类型将打开路面创建对话框这里我们展示的是Normal Platform平面平台如下图所示。在对话框里我们可以配置这个路面类型的各种属性例如长宽高等距离属性面的显示与否的属性等来定制它生成的几何模型以使其符合我们的要求。
![](../imgs/bme-adder-dialog.png)
在Normal Platform的对话框中我们首先可以看到它要求我们提供路面的长度宽度这决定了我们平台的大小下面还有对应的文字描述来帮助你理解这个属性具体是控制着什么。
然后它还需要我们提供这个平台的高度高度默认为5即Ballance中默认的路面高度大小小于5将创建类似“魔虬”地图中的薄路面大于5将创建类似“魔脓空间站”中非常高的路面墙体。
最后它指示我们需要配置这个路面哪些面需要显示。需要注意的是Top和Bottom指的是沿高度方向Z轴的顶面和底面而FrontBackLeftRight则是以头顶朝向-X轴眼睛朝向-Z轴俯视状态下的前后左右。您可能注意到这6个面按钮中间有一个透视的六面体实际上这六个面的选项的位置与这个透视六面体的六个面的位置是一一对应的。
## 小贴士
每个路面类型,其配置的条目数是不同的,因此对于不同路面类型,需要根据配置的提示文本来了解对应配置具体是做什么的。一些路面类型所需要设置的条目可能很多,另一些则根本没有配置条目。
路面类型配置的默认值被设置为创建此路面时最常用的值。每一次切换路面类型或重新创建时,都会将值重置为默认值。

View File

@ -1,3 +1,41 @@
# 编译与分发插件
本页面将指导你编译插件以及分发它。
## 编译LibCmo与BMap
BBP的Virtools文件原生导入导出功能依赖BMap以及其Python绑定PyBMap来实现。为了分发插件我们需要首先编译BMap及其前置LibCmo。而在编译前你需要先确认你需要的BMap版本。因为BBP并不总是使用最新的BMap例如你正在编译一个旧版的BBP其显然不可能依赖最新的BMap。BMap也在不断升级中其提供的功能也在不断变化不同版本的BMap是不兼容的。BBP通常会在发布时写明其所用的BMap版本如果BBP没有指出你可能需要寻找与BBP发布时最近的BMap版本来编译。
在明确版本后,你需要访问[LibCmo位于GitHub的存储库](https://github.com/yyc12345/libcmo21)。然后克隆项目使用Git命令转到对应版本或者直接下载对应版本的源码。然后按照LibCmo的编译手册编译得到BMap。在Windows上你通常会得到`BMap.dll``BMap.pdb`这两个文件。而在Linux上则会是`BMap.so`
然后我们需要配置PyBMap。PyBMap是随LibCmo一起提供的。请按照PyBMap的手册将编译得到的二进制BMap库和PyBMap结合在一起。即完成PyBMap配置。
然后我们需要将配置好的PyBMap拷贝到本项目的`bbp_ng/PyBMap`下即可完成此步。
## 生成缩略图和压缩JSON
BBP内置了一系列自定义图标以及其组件BME需要的用于描述结构的JSON文件。通过批量生成缩略图和压缩JSON的操作可以减小这些部分的大小使得其适合在Blender中加载也更方便分发。
转到`bbp_ng/tools`文件夹下,运行`python3 build_icons.py`将批量生成缩略图此功能需要PIL库请提前通过pip安装。其实际上是将`bbp_ng/raw_icons`目录下的原始图片生成对应的缩略图并存储于`bbp_ng/icons`文件夹下。运行`python3 build_jsons.py`将压缩JSON。其实际上是将`bbp_ng/raw_jsons`目录下的原始JSON文件读取压缩再写入到`bbp_ng/jsons`文件夹下。
## 打包
`bbp_ng`文件夹压缩成ZIP文件即可完成打包工作。需要注意的是下列文件或文件夹不应被打包
* `bbp_ng/raw_icons`:原始图片文件夹。
* `bbp_ng/raw_jsons`原始JSON文件夹。
* `bbp_ng/tools`:编译用工具。
* `bbp_ng/.style.yapf`:代码风格描述文件
* `bbp_ng/.gitignore`gitignore
* `bbp_ng/icons/.gitkeep`:文件夹占位符
* `bbp_ng/jsons/.gitkeep`:文件夹占位符
打包后的ZIP文件打开后如果有且只有`bbp_ng`一个文件夹,则代表打包成功。切勿直接将`bbp_ng` **内部的文件** 直接打包到ZIP文件中。
这样打包后的ZIP文件既可以直接通过Blender插件的安装功能直接安装也可以解压在插件目录下完成安装。
## 生成帮助文档
虽然本项目会利用GitHub Page功能提供帮助文档但有时你可能需要提供帮助文档的离线版本本节将会介绍如何生成离线版本的帮助文档。
首先你需要通过pip安装`mkdocs``pymdown-extensions`。然后转到`docs`文件夹下,运行`mkdocs build --no-directory-urls`。运行命令后得到一个名为`site`的文件夹,其中就是可以离线浏览的帮助文档。

View File

@ -0,0 +1,59 @@
# 添加机关
在3D视图中点击`Add - Components`可展开添加机关菜单。菜单如下图左侧所示。
![](../imgs/component-adder.png)
上图右侧则展示了一些机关添加的界面会在后续依次介绍它们右侧从上到下分别是添加Checkpoint检查点、添加Nong Extra Point脓分、添加Nong Ventilator脓风扇、添加Ventilator Series风扇阵列、添加Sector Pair盘点对
## 普通机关
在添加机关菜单中,`Basic Components`分类下的是普通机关的添加。对于大多数机关而言,添加机关需要指定其所属小节,表示这个机关只在这个小节中激活。然而有一些例外:
* PS_FourFlame关卡开始的4火焰盘点全局唯一因此没有小节属性。
* PE_Ballon关卡终点的飞船全局唯一因此没有小节属性。
* PC_TwoFlames小节的检查点具有小节属性。但需要注意的是其小节属性指的是其要检查哪个小节例如指定小节属性为1则表示它是第一小节的检查点即第二小节的开始通过后开启第二小节。
* PR_Resetpoint小节的重生点具有小节属性。但需要注意的是小节属性表示它是哪个小节的重生点。由此可知当PC_TwoFlames和PR_Resetpoint成对出现时PR_Resetpoint总比PC_TwoFlames的标号大1。
!!! info "自动名称冲突检测"
一部分物体在一张Ballance地图中名称是唯一的例如开头盘点与终点飞船有且只能有一个小节检查点与复活点同一小节只能存在一个等。
BBP在创建这些机关时提供了名称检测功能如果名称已存在会在创建时在下方用文字显示出来以提醒用户不要创建重复的内容。如上文展示图右上角显示为例其正在尝试添加一个已经存在的PC_TwoFlames并收到了警告。
## 添加脓机关
在添加机关菜单中,`Nong Components`分类下的是脓机关的添加。我们只提供两类常用脓机关的添加:脓分和脓风扇。
### Nong Extra Point
脓分添加需要指定脓分的小节号和个数。并且会自动帮用户对脓分实现一个逐个旋转的效果,以让游戏内的脓分显示的更好看。
### Nong Ventilator
脓风扇的添加同样要指定小节号和个数。不同的是我们提供了一些预设数值,这些预设数值构造的脓风扇可以恰好吹起木球或石球,如果你对这些预设数值不满意,仍可以自行输入数量。
!!! info "风扇阵列也可以"
你知道吗在添加风扇阵列时将偏移设为0也可以实现脓风扇。这里的脓风扇创建只是提供了一些预设数值罢了。
## 添加阵列机关
在添加机关菜单中,`Series Components`分类下的是阵列机关的添加。我们只提供两类常用阵列机关的添加:浮板阵列和风扇阵列。
### Tilting Block Series
浮板阵列需要提供小节号和浮板个数,并且还可以自由调整相邻浮板之间的间距,默认间距取自游戏内。
### Ventilator Series
风扇阵列也需要提供小节号和风扇个数,然而它提供三维的偏移量,这样你就可以构建竖直的风扇阵列或水平的风扇阵列。默认偏移数值取自游戏内竖直风扇阵列的数值。
## 添加机关对
在添加机关菜单中,`Components Pair`分类下的是机关对的添加。目前只有一种机关对:盘点对可添加。
### Sector Pair
盘点对需要你输入一个小节号它会为你自动生成盘点火与重生点机关对。例如输入1小节则会自动生成4火焰盘点火和第1小节的重生点输入2则会生成第1小节检查点和第2小节重生点以此类推。
!!! info "自动名称冲突检测"
与普通机关添加类似盘点对的添加也有名称冲突检测功能。上文图中右下角为例其显示第1小节的盘点对已经存在不需要添加。

View File

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

View File

@ -0,0 +1,29 @@
# 按组操作
## 按组选择
`Ballance - Select by Virtools Group`提供了一种按照Virtools归组数据进行筛选的功能。
该功能首先有5种不同的选择策略与Blender的选择方法完全匹配开始、扩选、相减、反转、相交。只需像Blender选择那样使用它。然后选择你需要的组的名称然后开始一次选择或筛选。
!!! note "关于模式选择"
如果可以,请尽可能使用相减或相交模式。因为这样可以避免分析过多的物体。例如先选定一个大致的范围,然后使用相交模式过滤,比直接使用开始模式效率更高。
## 快速归组
BBP插件在2个地方添加了为物体快速归组的功能。首先是物体上下文菜单你可以选择一系列物体然后右键在物体上下文菜单中找到快速归组功能。其次是大纲视图中的物体菜单你可以在大纲窗口中右键选择的物体找到快速归组功能。两者菜单如下图所示。
![](../imgs/grouping.png)
### Group into...
把选择物体归入你选择的组。
### Ungroup from...
把选择物体从你选择的组中取消归组。
### Clear All Groups
清空选择物体的所有归组信息。执行前会让你确认以免误操作。

View File

@ -0,0 +1,55 @@
# 导入导出Virtools文档
!!! warning "这是实验性内容"
原生导入导出Virtools文档是BBP插件的试验性内容它可能存在许多问题请参阅[报告问题](./report-bugs.md)章节来了解更多。当遇到问题时请及时汇报。BBP插件的作者不对因BBP插件问题导致的任何后果负责。
## 导入Virtools文档
点击`File - Import - Virtools File`可以导入Virtools文档。导入支持CMOVMONMO文件。点击后会弹出文件打开界面并在侧边栏展示导入设置。首先你需要选择导入的Virtools文档然后在侧边栏配置导入设置配置完导入设置后即可点击导入开始导入并等待Blender下方提示导入完成即可。
### 冲突解决选项
Conflict Options冲突解决选项章节指示了当导入器遇到物体名称重复的情况时该如何处理。分为4个等级分别针对Object物体Mesh网格Material材质Texture贴图。处理方式则有2种Rename重命名和Use Current使用当前。选择重命名后当遇到重复名称时将会自动为其添加名称后缀使其名称唯一化。而选择使用当前则会忽略从文件中导入此项转而使用Blender文档中已经存在的同名的项目。
!!! info "与Virtools冲突解决的不同"
相比较于在Virtools的冲突解决对话框BBP插件提供的冲突解决选项不支持替换功能同时其粒度也不支持精细到单个实例只能针对一整个类型进行设定。因此你无法单独为每一个冲突的实例设置不同的冲突解决方案。但目前这种设置已经能满足绝大对数的使用场景了。
冲突解决选项章节里的选项的默认值是通常导入时会选择的解决方案。当然针对特殊导入情况需要特殊设置,例如正在从外部导入一个从原版中导出的机关模型,则可能连材质选项都可以选择使用当前,而无需复制一份。正确使用冲突解决选项是制图经验所给予的,本手册不会对此进行教学。
### Virtools参数
众所周知Virtools使用基于系统的多字节字符编码来处理文档因而很容易出现所谓乱码问题。Blender本身不会出现乱码问题然而如果我们不能以正确的编码读取Virtools文档则当Virtools文档被导入Blender时其中存储的字符仍然可能会呈现乱码状态。Virtools ParamsVirtools参数章节的Encodings编码属性用于指定读取Virtools文档的编码。可以指定多个编码多个编码之间用`;`(分号)分隔。下面列出一些常用的编码:
* 1252Windows下Ballance所用的西欧编码
* 936Windows下中文Windows系统默认编码
* CP1252非Windows下Ballance所用的西欧编码
* CP936非Windows下中文默认编码
编码属性非常重要如果设置了错误的编码导入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/)。
## 导出Virtools文档
点击`File - Export - Virtools File`可以导出Virtools文档。点击后会弹出文件打开界面并在侧边栏展示导出设置。首先你需要选择导出的Virtools文档的位置然后在侧边栏配置导出设置配置完导出设置后即可点击导出开始导出并等待Blender下方提示导出完成即可。
### 导出目标
Export Target导出目标章节用于决定你需要将哪写物体导出到Virtools文档中。你可以选择导出一个集合或一个物体并在下面选择对应的集合或物体。需要注意的是选择集合的时候会将内部集合中的物体也一起导出即支持嵌套集合的导出。
### Virtools参数
Virtools ParamsVirtools参数章节与导入Virtools文档中的类似。Encodings编码属性决定了导出Virtools文档时所用的编码。
Global Texture Save Option全局贴图保存选项决定了那些设置了Use Global使用全局设定的贴图的真实保存方式。通常而言设置为Raw Data原始数据则可以100%保证保存的Virtools文档可以包含正确贴图但是其体积也可能会变大设置为External外部数据则可以尽可能减少文件大小但可能会出现导出的文档找不到贴图文件的问题。我们建议你在进行材质设置时就对每个材质单独指定应该如何保存而不是依赖全局选项来设置。这个选项是给那些依赖全局贴图保存选项的旧地图的再编辑来使用的。还需要注意的是尽管这个选项里有Use Global选项但请 **不要** 选择,否则会导致错误,因为显然你不能让一个全局选项再去使用全局选项的设置。
Use Compress使用压缩属性指定保存的文档是否压缩存储。压缩可以显著减少文档体积且在现代计算机平台上压缩所造成的性能损失几乎可以忽略不计。当选择使用压缩后一个额外的Compress Level压缩等级属性将会显示用于指定压缩的级别数值越高压缩率越大文件越小。
### Ballance参数
Ballance ParamsBallance参数章节包含针对Ballance特有内容对导出过程进行优化的参数。
Successive Sector小节连续是一个解决导出小节组时出现的Bug的选项。由于某些原因如果一个小节中没有任何机关实际上是某小节组中没有归入任何物体导出插件会认为该小节组不存在因而遗漏导出。且由于Ballance对最终小节即飞船出现小节的判定是从1开始递增寻找最后一个存在的小节组所以二者叠加会导致Ballance错误地认定地图的小节数从而在错误的小节显示飞船这也就是导出Bug。当勾选此选项后导出文档时会预先按照当前Blender文件中Ballance地图信息中指定的小节数预先创建所有小节组然后再进行导出这样就不会遗漏创建某些小节组飞船也会在正确的小节显示。
这个选项通常在导出可游玩的地图时选中,如果你只是想导出一些模型,那么需要关闭此选项,否则会在最终文件中产生许多无用的小节组。

View File

@ -1,8 +1,5 @@
# Ballance Blender Plugin用户手册
!!! info "制作中..."
手册的这部分还在制作当中。稍安勿躁。
欢迎来到Ballance Blender Plugin一个自由开源的Ballance地图创作套件的用户手册。
Ballance Blender Plugin后文简称BBP是一款关注Ballance自制地图创建的插件。它提供了相当丰富的功能从制图新手到老手均可利用此插件创作Ballance自制地图。BBP提供了导入导出Ballance地图所用格式的功能并为Ballance地图创作量身打造了一系列便利的功能对于新手可以借助预制路块快速拼接完成一张地图对于老手也提供了构建复杂结构所需的贴图工具。
@ -16,19 +13,23 @@ Ballance Blender Plugin后文简称BBP是一款关注Ballance自制地图
* [安装插件](./install-plugin.md)
* [配置插件](./configure-plugin.md)
## 基础
## 特性
* Virtools属性
* 导入导出Virtools文档
* 按组操作
* 高级贴图
* 添加预制结构
* 添加钢轨
* 添加机关
* [Virtools属性](./virtools-properties.md)
* [Ballance属性](./ballance-properties.md)
* [导入导出Virtools文档](./import-export-virtools.md)
* [按组操作](./group-operations.md)
* [传统对齐](./legacy-align.md)
* [命名规则](./naming-convention.md)
* [UV贴图](./uv-mapping.md)
* [添加路面](./bme-adder.md)
* [添加钢轨](./rail-adder.md)
* [添加机关](./component-adder.md)
## 进阶
## 其它
* [编译与分发插件](./compile-distribute-plugin.md)
* [报告问题](./report-bugs.md)
* [技术信息](./tech-infos.md)
!!! info "这些并不是全部"

View File

@ -8,13 +8,13 @@ BBP对Blender支持的原则是支持当前最新的 **LTS** 版本,在最新
## 卸载旧插件
如果之前使用过BBP那么需要首先卸载它。旧版的BBP通常被安装在下列的位置中
如果之前使用过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`指代的Blender安装位置。路径中的`3.6`安装的Blender的版本号需要根据安装的版本进行调整,本手册均以`3.6`为例。
只需要删除这些文件夹(如果它们存在的话)即可完全卸载插件。路径中的`Blender`指代的Blender安装位置。路径中的`3.6`安装的Blender的版本号需要根据安装的版本进行调整,本手册均以`3.6`为例。
!!! info "`ballance_blender_plugin``bbp_ng`"
`ballance_blender_plugin`是旧版BBP插件4.0版本前)的模块名,`bbp_ng`是新版BBP插件4.0版本后包括4.0版本)的模块名。为了保证用户确实删除了旧版插件,所以同时提供了这两者。
@ -24,9 +24,9 @@ BBP对Blender支持的原则是支持当前最新的 **LTS** 版本,在最新
## 下载插件
可以通过[本工程的GitHub代码库的Release页面](https://github.com/yyc12345/BallanceBlenderHelper/releases)下载最新的插件。插件是以ZIP压缩包形式提供的。
可以通过[本工程的GitHub代码库的Release页面](https://github.com/yyc12345/BallanceBlenderHelper/releases)下载最新的插件。插件是以ZIP压缩包形式提供的。
此外,还可以在yyc12345提供的制图教程网盘中获得此插件
此外,还可以在yyc12345提供的制图教程网盘中获得此插件
* 中国特供:[百度网盘](https://pan.baidu.com/s/1QgWz7A7TEit09nPUeQtL7w?pwd=hf2u) 提取码hf2u位于`制图插件(新)`下)
* 非中国:[Mega](https://mega.nz/#F!CV5SyapR!LbduTW51xmkDO4EDxMfH9w) (位于`Mapping`目录下)
@ -36,11 +36,11 @@ BBP对Blender支持的原则是支持当前最新的 **LTS** 版本,在最新
## 安装插件
开启Blender选择`编辑 - 偏好设置`,在打开的窗口中转到`插件`选项卡,点击`安装`按钮选择刚刚下载完毕的ZIP压缩包即可安装完成。若没有在列表中看到可选择刷新按钮或重启Blender。
开启Blender选择`Edit - Preferences`,在打开的窗口中转到`Add-ons`选项卡,点击`Install...`按钮选择刚刚下载完毕的ZIP压缩包即可安装完成。若没有在列表中看到可选择刷新按钮或重启Blender。
也可以选择手动安装插件(如果上述安装方法失败了的话),转到`Blender/3.6/scripts/addons`将下载好的ZIP压缩包内容解压到此文件夹下启动Blender即可在插件列表中找到BBP。
也可以选择手动安装插件(如果上述安装方法失败了的话),转到`Blender/3.6/scripts/addons`将下载好的ZIP压缩包内容解压到此文件夹下启动Blender即可在插件列表中找到BBP。
BBP插件位于`社区`类别下,名称为`Object: Ballance Blender Plugin`,找到后勾选名称左侧的勾即可启用插件。插件安装成功后的偏好设置页面如下图所示。
BBP插件位于`Community`类别下,名称为`Object: Ballance Blender Plugin`,找到后勾选名称左侧的勾即可启用插件。插件安装成功后的偏好设置页面如下图所示。
![](../imgs/config-plugin.png)

View File

@ -0,0 +1,29 @@
# 传统对齐
`Ballance - 3ds Max Align`提供了一种类似于3ds Max中对齐方式的对齐功能。
所谓传统对齐功能是是将3ds Max中的对齐操作完美地在Blender中重新进行了实现。可以使得很多从3ds Max转来使用Blender的制图人可以更快地上手并且提供了一些便捷的对齐操作。下图展示了正在运作的传统对齐。
![](../imgs/legacy-align.png)
## 使用方法
传统对齐支持将多个物体对一个物体的对齐,操作方法是先依次选中需要被对齐的物体,然后在最后选中对齐参考对象(也就是使其成为活动物体),然后点击`Ballance - 3ds Max Align`,即可弹出传统对齐面板,之后便可开始对齐操作。
## 面板介绍
在面板中,`Align Axis`指定了你要对齐的轴,此处可以多选以指定多个轴,不指定任何轴将无法进行对齐操作,因而也无法点击`Apply`按钮。
`Current Object`是对齐参考物体,也就是场景中的活动物体,通常也就是你选择的最后一个物体。在这个选项里指定你需要参考其什么数值进行对齐,分别有`Min`(轴上最小值)、`Center (Bounding Box)`(碰撞箱的中心)、`Center (Axis)`(物体的原点)、`Max`轴上的最大值可选。这些选项与3ds Max中的对齐选项是一致的。
`Target Objects`是正在被对齐的物体,可能有很多个,在这个选项里也是指定你需要参考其什么数值进行对齐。选项与`Current Object`含义一致。
`Apply`按钮点击后将把当前页面的配置压入操作栈,并重置上面的设置,使得你可以开始新一轮对齐操作而无需再次执行传统对齐。操作栈中的操作个数在`Apply`按钮下方显示。
!!! info "Apply按钮做了什么"
了解这部分对制图没有用,除非你感兴趣,否则不需要阅读这个框里的内容。
在设计上Blender不支持所谓“在Operator内进行操作”但是我们通过一些小把戏模拟了一种类似于3ds Max中应用的效果。
Apply按钮实际上是一个特殊显示的BoolProperty通过监听其值变化事件在避免递归调用的情况下将当前设置记录在一个隐藏的CollectionProperty中并重置自身数值和显示的属性以达到“应用”的效果。Operator在执行过程中则是依次处理CollectionProperty中积攒的对齐要求。

View File

@ -0,0 +1,29 @@
# 命名规则
## 自动归组与重命名
在大纲视图中,对任意集合右键,可以得到自动归组与重命名菜单。
![](../imgs/naming-convention.png)
本插件目前支持两种命名标准。
其一为技术信息章节已经阐述的制图链标准,在本插件中的名称为`YYC Tools Chains`
其二为[Imengyu/Ballance](https://github.com/imengyu/Ballance)所用命名标准,在本插件中的名称为`Imengyu Ballance`
这些功能最终只会展示成功与否的一个概括性消息。如果你需要详细查看某个物体为什么不能转换,请点击`Window - Toggle System Console`,插件在那里有更详细的输出。
### Rename by Group
根据当前物体的归组信息,为其重命名为合适的名称。
这通常用在迁移原版地图的过程中。一些Ballance衍生程序没有Virtools组概念因此需要依赖名称来取得归组信息。
### Convert Name
在不同命名标准之间切换。
通常用于在不同Ballance衍生程序中进行转换。
### Auto Grouping
根据给定的命名标准,为物体自动填充归组信息。
需要注意的是,原有的归组信息会被覆盖。
在制图过程中,如果你遵守了某些命名标准,则此功能可以为你自动完成归组功能。

View File

@ -0,0 +1,81 @@
# 添加钢轨
在3D视图中点击`Add - Components`可展开添加钢轨菜单。菜单如下图左侧所示。
![](../imgs/rail-adder.png)
上图右侧则展示了一些机关添加的界面会在后续依次介绍它们右侧从上到下分别是添加Rail Section钢轨截面添加Straight Rail直钢轨添加Side Rail侧轨添加Arc Rail圆弧轨添加Spiral Rail螺旋轨
!!! info "非标准数据的钢轨"
BBP的钢轨添加菜单是为新手玩家快速添加钢轨而设计的并不是为老手添加钢轨而设计的。对于需要非标准数据的钢轨的情况例如具有非标准间距或非标准截面的钢轨你需要通过Blender自带的创建圆操作构建钢轨截面然后通过挤出桥接又或者螺旋修改器生成整个钢轨。在这样的创作过程中你可以随意控制每个步骤的所有参数以满足你对钢轨参数的特殊需求。
!!! info "钢轨数据的来源"
钢轨添加菜单的所使用的钢轨各类参数来源于游戏中的实际测量和Ballance社区中多位制图玩家十几年来总结的经验数据。
钢轨截面半径与轨距由多年制图经验总结和测量所得。侧轨倾斜数据来源BallanceBug计算。单双轨转换下沉数据来源失衡之梦计算。螺旋轨间距来自第九关和第十三关测量。
## 钢轨截面
在添加机关菜单中,`Sections`分类下的是钢轨截面的添加。钢轨截面是钢轨的轮廓,钢轨截面的创建通常是各类异形钢轨的创建的开始步骤,例如通过放样,挤出等操作制作钢轨。
### Rail Section
钢轨截面创建一个钢轨截面,你可以在面板中选择创建一个单轨或双轨截面。
当创建单轨截面时,会自动将八边形的钢轨截面的平头朝上,双轨截面则不会。如果你需要修改这种行为,你需要在创建后进入编辑模式,手动旋转钢轨截面顶点使其钢轨截面的平头或尖头朝上。
### Transition Section
单双轨转换截面将创建一个适用于单双轨转换的钢轨截面。这个轨道截面的创建不需要指定任何参数。
## 直线钢轨
在添加机关菜单中,`Straight Rails`分类下的是直线钢轨的添加。
### Straight Rail
直钢轨是一段直来直去的钢轨。创建直钢轨需要为其指定Length长度。你也可以选择创建直的双轨或单轨。
当创建单轨时,与截面类似,会自动将钢轨截面的平头朝上,修改这一行为的操作则也是创建后进入编辑模式再旋转即可。
直钢轨的创建还支持封盖属性这些特性由位于Rail Cap钢轨封盖下的Start Cap始端封盖和End Cap末端封盖选项控制勾选后对应端将进行封盖。封盖即为钢轨的端面自动补面并正确处理其法线问题这通常用于确保钢轨与其它路面或物体接触的部分的美观钢轨与钢轨之间的连接端无需封盖。
### Transition Rail
单双轨转换轨通常可被视为是Transition Section创建的进阶使用将Transition Section创建的截面挤出并处理好法线问题即可得到此选项创建的结果。创建单双轨转换轨需要为其指定Length长度其也支持封盖属性。
### Side Rail
侧轨创建首先需要指定Side Type侧轨类型可以选择Normal纸球木球用侧轨或Stone Specific石球专用侧轨。纸球木球用侧轨就是通常意义上的侧轨石球不可以通过。石球专用侧轨是倾斜度更大的侧轨石球也可以通过当然纸球和木球也可以。
除了侧轨类型外侧轨创建也需要Length长度和封盖属性。
## 曲线钢轨
在添加机关菜单中,`Curve Rails`分类下的是曲线钢轨的添加。
### Arc Rail
圆弧轨首先需要指定Angle角度和Radius半径表示这个圆弧轨将会以多少的半径转过多少角度。通常来说角度以90度180度270度比较常见当然也可以指定任意度数。半径则通常按需调整。对于双轨圆弧轨半径是圆弧轨旋转圆心到双轨截面两轨中心连线的中点的距离对于单轨圆弧轨半径是圆弧轨旋转中心到单轨截面的中心的距离。
圆弧轨的Steps步数步数表示这个圆弧轨的分段数数字越大圆弧轨看起来越平滑相对的顶点也会更多对存储空间和渲染的要求也越高因此需要选择一个合理的数值。
圆弧轨同样支持双轨单轨选择,可以创建单轨圆弧轨和双轨圆弧轨。也支持封盖属性。
### Spiral Rail
螺旋轨也就是螺旋双轨其与圆弧轨类似需要指定Radius半径表示其旋转半径但不需要指定角度因为它总是旋转一圈。
螺旋轨有Iterations迭代属性表示这个螺旋轨将要螺旋上升几圈。Screw螺距属性则表示每一个迭代之间的距离是多少。
螺旋轨也需要设置Steps步数属性含义与圆弧轨一致。但需要注意的是步数指的是每一个迭代内的步数个数并不是总体的步数。因此调整迭代属性的时候不需要再调整步数属性。
螺旋轨也有封盖属性。
### Side Spiral Rail
侧边螺旋轨,与螺旋轨类似,但是球是沿侧边滚动的,类似于侧轨。
侧边螺旋轨没有螺距属性,因为侧边螺旋轨在设计上,相邻的旋进是共用一条边的,因此螺距是固定的。
侧边螺旋轨设定中的Radius半径Iterations迭代和Steps步数属性含义均与螺旋轨一致。螺旋轨也有封盖属性。

View File

@ -0,0 +1,29 @@
# 报告问题
## 什么会出错
BBP不是完美的由于BBP的Virtools文件导入导出模块是由C++编写的因此BBP比其他插件更容易出错且出错的后果可能会更严重包括但不限于内存泄漏误删除用户文件像[少前2事件](https://www.163.com/dy/article/IGUHP2TE0526D7OK.html)一样)等)。
在Blender中如果插件执行出错你将会观察到
* 期待的效果没有达成
* 鼠标处弹出一大堆你看不懂的堆栈输出文本
* 使用`Window - Toggle System Console`打开控制台后可以观察到Python的异常输出。
## 哪部分出错了
对于BBP插件而言如果你在Python异常输出中观察到类似于`BMap operation failed`的字样,或者在`Blender/3.6/scripts/addons/bbp_ng/PyBMap`文件夹下观察到了`IronPad.log`文件则说明BBP插件的由C++编写的BMap部分出错了**你需要立即保存你当前的Blender文档并退出Blender。** 因为此时插件已处于非正常状态,你不应继续任何操作。
如果并没有上述情况那么这就只是普通的Python代码执行错误不需要过度担心但错误仍然是致命的建议做完所有必要的操作后退出Blender并报告错误。
## 向何处报告
如果你有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`文件以方便开发者定位错误。

View File

@ -3,3 +3,5 @@
* BM文件标准https://github.com/yyc12345/gist/blob/master/BMFileSpec/BMSpec_ZH.md
* 制图工具链标准及`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版本发布时间的每日编译版本来安装它。

View File

@ -0,0 +1,57 @@
# UV贴图
## 钢轨贴图
3D视图中的菜单`Ballance - Rail UV`提供了给钢轨贴图的功能。
您需要先选择需要被贴图的钢轨可以多选然后再点击此菜单即可开始进行钢轨贴图。然后你只需要选择钢轨所用的Material材质属性即可。此功能将清空选择物体的所有材质并将各个面所用的材质全部指定为你所指定的材质。然后按照Ballance中对钢轨进行后处理的方式为你选中的物体设置其UV。因此通过本操作的执行可以使得钢轨具有和游戏内相同的外貌。
!!! info "钢轨UV其实并不重要"
事实上,所有归入`Phys_FloorRails`组的物体均会在Ballance加载关卡时进行一个后处理。后处理方法的具体操作就是按照某个特定的映射函数重设这些物体的UVVirtools中调用`TT_ReflectionMapping`。所以钢轨的UV实际上并不重要因为进入游戏后Ballance会为你统一设置即使你不设置任何UV数据给钢轨进入游戏后钢轨的样子都是正确的。
因此,此功能通常应用于对那些不归入`Phys_FloorRails`但仍希望其能显示钢轨材质外观的物体的UV进行设置。又或者用于在Blender中渲染Ballance地图预览图等。
## 沿边沿贴图
沿边沿贴图是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`开始进行沿边沿贴图。
沿边沿贴图的配置界面如下图所示左侧是Scale Size缩放数值模式右侧是Ref. Point参考点模式我们稍后会说明这两个模式的区别。
![](../imgs/flatten-uv.png)
沿边沿贴图顾名思义其含义就是沿着某条边进行UV贴图。具体的操作就是利用线性代数的方法使用过渡矩阵将顶点的三维坐标转换到一个新的坐标系下。在这个新的坐标系中
* 原点是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指定的边并不显示路面侧边花纹。
!!! info "Reference Edge的实指"
Reference Edge实际上指代的就是那条需要被贴靠到V轴上的边的序号。又因为这条边具有方向性同时也就决定了新坐标系中的原点。
Flatten UV简单来说就是让用户指定一条边然后把它沿着UV的V轴贴上去。
在确定好坐标系后我们还需要知道这个UV贴图需要展的多开具体来说就是这个UV贴图的缩放数值应该是多少。对于V轴方向由于Ballance的贴图特性Ballance贴图总是沿着V轴延伸以及Ballance中的3D的5等价于UV中的1我们可以知道这个UV:3D的关系是1:5。而对于U轴方向则无法确定这也就是Scale Mode属性的作用让用户决定U轴方向上的缩放。
Scale Size缩放数值模式就是直接让用户指定这个缩放数值例如默认值5代表着3D世界中的5等于UV世界中的1。Ref. Point参考点模式则允许用户指定面中某一个参考点的UV的U值让插件自行推算其U轴上的缩放。例如我们将上图中的缩放数值模式切换成参考点模式并指定Reference Point参考点为2Reference Point UV参考点UV为1也可以贴上相同的贴图我们来解释一下这里面的原理。首先Reference Point指定参考点这个参考点是相对于Reference Edge指定的顶点也就是参考系原点的偏移因此在这里Reference Edge为1Reference Point为2则实际上的参考点为顶点3。Reference Point UV指定了这个点的UV数值中U的数值为1也就是可以看作强行把顶点3放置在了U轴为1的位置。BBP会根据这一点计算出对应的U轴缩放值并应用到所有顶点上。
缩放数值模式通常用于路面侧边贴图因为这些面具有固定的U轴缩放数值。而参考点模式通常用于那些无法确定缩放数值通常是由于模型变形导致缩放数值在标准缩放数值范围附近波动的贴图例如由放样生成的凹路面的上表面如下图所示。我们能肯定凹路面的中心的UV的U一定是0.5但是不方便确定其缩放数值因为难以计算所以我们只需要使用参考点模式即可。下图的上半展示了参考点模式下的贴图结果下半部分则是缩放数值模式可以观察到在缩放数值模式下凹路面中心的UV并不是精准的0.5这会导致在显示上凹路面的中心不够黑。而参考点模式则精准地将凹路面中心的UV设置为0.5。
![](../imgs/flatten-uv-scale-mode.png)
最后Flatten Mode指定了展开模式。Raw表示完全不考虑面与面之间的联系把每个面当作独立的内容来处理。Floor表示考虑V轴方向上相邻面的连续性会尽可能将相邻的面使其在UV中也相邻这样就可以避免在路面细分过多的情况下使用Raw展开方式容易导致贴图在视觉上重复的问题。Floor展开方式常用在路面的贴图中因此称为Floor。Wood与Floor类似只是它不仅考虑V轴上的连续性还会考虑U轴上的连续性这通常用于凹木板凸木板的贴图中也是Wood的名称来源。
下图展示了三种不同展开模式的UV贴图分布。最上面是Raw展开模式可以看到每个面的Reference Edge都被展开在了UV坐标原点的位置。中间是Floor展开模式可以看到BBP将一系列连续的面沿着V轴方向展开了而不是像Raw展开模式那样全部都堆在原点。最下方则是Wood展开模式正在展开一个凸木板可以看到在V轴和U轴上都考虑了面的连续性。
![](../imgs/flatten-uv-flatten-mode.png)
!!! info "Floor和Wood展开模式失败了"
Floor和Wood相比Raw展开模式具有更多限制它们只支持四边面而且对建模的操作有很高要求通常只有通过批量操作例如细分放样等生成的几何结构才能被Floor和Wood正确识别。
如果Floor和Wood展开模式失败了且展开得到的贴图完全不可接受请尝试使用更加规范地方式建模或转为手动贴图。

View File

@ -0,0 +1,59 @@
# Virtools属性
## Virtools组
BBP插件为每一个Blender物体添加了新的属性被称为Virtools Group。与Virtools中的组具有相同的功能。选择一个物体`Object`属性面板可以找到`Virtools Group`面板。
![](../imgs/virtools-group.png)
`Virtools Group`面板中可以点击添加为物体归组。在点击添加按钮后可以选择预定义然后从所有合法的Ballance组名中选择一个添加。或选择自定义然后输入你想要的组名添加。也可以点击删除按钮删除选中的Virtools组。最后可以通过点击垃圾桶按钮一次性删除这个物体的所有组数据删除前会让你确认
BBP还在Blender的其它菜单提供了对Virtools组的访问具体内容请参阅[按组操作](./group-operations.md)。
## Virtools材质
插件为每一个Blender材质添加了新的属性被称为Virtools Material。它在Virtools材质与Blender材质之间架起沟通的桥梁。转到`Material`属性面板,选择一个材质,即可以找到`Virtools Material`面板。
![](../imgs/virtools-material.png)
可以在`Virtools Material`面板中设置材质属性就像在Virtools中操作一般。`Virtools Material`面板中所有的材质参数均为Virtools中材质参数的映射将准确地反映到最后保存的Virtools文档中。
`Virtools Material`面板提供了预设功能,点击顶部的`Preset`按钮即可开始进行预设。预设功能允许用户使用一些预设的材质设置,例如路面顶面,侧面的材质数据等,方便使用。需要注意的是,使用预设不会影响材质的贴图选项,当应用预设后,你仍然需要手动设置材质的贴图。
`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等。
!!! info "还有一个全局材质修复功能"
3D视图中菜单`Ballance - Fix Material`与材质修复功能类似,但其会修复当前文档内所有材质。除非你确定当前文档内所有材质都需要修复,否则不要使用这个功能,因为它可能会将一些原本特殊设置的,正确的材质设置回你不想要的通用数值。
全局材质修复功能点击后也需再次确认才能使用,以防止误操作。
`Virtools Material`面板中的Texture贴图属性不仅可以通过点击它来选择文档内的材质还可以通过点击右侧的文件夹按钮打开贴图文件浏览器直接从文件系统中选择你想要的贴图比从Shading菜单中选取更加快速。文件浏览器默认位于Ballance的Texture目录下以方便Ballance材质的选取。
## Virtools贴图
BBP插件为所有Blender贴图实际上是Image添加了新的属性称为Virtools Texture。它在Virtools贴图与Blender图片之间架起联系。
与Blender材质不同的是由于Blender的实现原因贴图没有单独的属性面板因此我们只能在`Virtools Material`面板中通过一种非直接的方式访问Virtools贴图属性。首先参照Virtools材质章节的说明找到`Virtools Material`面板,然后在`Virtools Material`面板中的材质插槽中选择一个贴图或打开一个贴图就可以发现在材质的贴图属性下方额外显示了Virtools贴图属性如下图高亮部分所示。
![](../imgs/virtools-texture.png)
其中Save Option表示贴图在Virtools中的存储方式常见的存储方式有这几种
* External外部存储文件只存储引用的文件名。所有Ballance原生贴图都应该使用此模式。
* Raw Data原始数据贴图存储在文件内缺点是会导致文件很大。所有非原生Ballance贴图都应该使用此模式。
* Use Global使用全局设定。除非是正在修改地图否则我们不建议使用此方式。我们建议在这里就明确指定各个贴图的存储方式而不要使用全局值。全局设定在导出Virtools文档时被确定下来。
而Video Format表示贴图在Virtools中的渲染模式常用的模式有这几种
* 32 Bits ARGB8888带有透明度的各类贴图的存储方式例如柱子渐变部分。
* 16 Bits ARGB1555不带有透明度的各类贴图的存储方式例如路面。
## Virtools网格
BBP插件为所有Blender网格添加了新的属性称为Virtools Mesh。转到`Data`属性面板,即可以找到`Virtools Mesh`面板。
![](../imgs/virtools-mesh.png)
Virtools网格目前只是作为兼容来使用的。其只有Lit Mode一个属性可以设置。多数早期地图由于不知道如何正确设置材质导致路面发黑所以经常将Lit Mode设置为Prelit以让路面正常显示。此属性是为了兼容这种设计而存在的用户通常无需设置此选项。

View File

@ -8,11 +8,37 @@ nav:
- 「 」: 'index.md'
- English:
- 'Start': 'en/index.md'
- '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'
- 'Naming Convention': 'en/naming-convention.md'
- 'UV Mapping': 'en/uv-mapping.md'
- 'Add Floor': 'en/bme-adder.md'
- 'Add Rail': 'en/rail-adder.md'
- 'Add Component': 'en/component-adder.md'
- 'Compile and Distribute Plugin': 'en/compile-distribute-plugin.md'
- 'Report Issue': 'en/report-bugs.md'
- 'Technical Information': 'en/tech-infos.md'
- 简体中文:
- '开始': 'zh-cn/index.md'
- '安装插件': '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'
- '命名规则': 'zh-cn/naming-convention.md'
- 'UV贴图': 'zh-cn/uv-mapping.md'
- '添加路面': 'zh-cn/bme-adder.md'
- '添加钢轨': 'zh-cn/rail-adder.md'
- '添加机关': 'zh-cn/component-adder.md'
- '编译与分发插件': 'zh-cn/compile-distribute-plugin.md'
- '报告问题': 'zh-cn/report-bugs.md'
- '技术信息': 'zh-cn/tech-infos.md'
theme: