Compare commits
18 Commits
v4.0-alpha
...
v4.0-alpha
Author | SHA1 | Date | |
---|---|---|---|
07298fd21c | |||
3a1a0fb0f6 | |||
3396947115 | |||
6cf2ab895d | |||
b039dd8b43 | |||
8bad1a487c | |||
0f18559d3f | |||
5a5053440c | |||
02a1222210 | |||
f8c344f65e | |||
0bec108dcb | |||
997839a187 | |||
da71d5560c | |||
02082bf99e | |||
c82c094519 | |||
8ec101e1f1 | |||
8499c25b67 | |||
200ac40648 |
6
.github/workflows/main.yml
vendored
@ -2,17 +2,17 @@ name: Publish docs via GitHub Pages
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- ng
|
||||
- master
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Deploy docs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout ng
|
||||
- name: Checkout master
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ng
|
||||
ref: master
|
||||
|
||||
- name: Install MkDocs
|
||||
run: |
|
||||
|
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
# disable distribution build folder
|
||||
redist/
|
@ -4,11 +4,4 @@
|
||||
|
||||
BBP NG, abbr **B**allance **B**lender **P**lugin **N**ext **G**eneration.
|
||||
|
||||
## Brief Introduction
|
||||
|
||||
The next generation of original Ballance Blender Helper. This plugin is fully rewritten.
|
||||
This plugin still work in progress. The development will be pushed into `ng` branch in main repository. For legacy plugin user, please visit `master` branch directly.
|
||||
|
||||
## Develop Help
|
||||
|
||||
Use `pip install fake-bpy-module-latest==20230627` to install the type hint library for Blender. Because fake-bpy-module do not release official 3.6 package, we need install it by choosing the most closest version of Blender 3.6 release. That what I found.
|
||||
For an introduction to this plugin, installing it, compiling it, reporting bugs, etc., see the GitHub Page of this project: https://yyc12345.github.io/BallanceBlenderHelper
|
||||
|
@ -4,7 +4,4 @@
|
||||
|
||||
BBP NG,又名**B**allance **B**lender **P**lugin **N**ext **G**eneration(下一代Ballance Blender插件)。
|
||||
|
||||
## 简介
|
||||
|
||||
下一代的Ballance Blender插件。此插件完全重写了上一代插件。
|
||||
此插件仍然在开发过程中。开发内容会被推送到主仓库的`ng`分支中。对于旧插件的用户,请直接访问`master`分支。
|
||||
有关此插件的介绍,安装,编译,汇报错误等,请参阅本项目的GitHub Page页面:https://yyc12345.github.io/BallanceBlenderHelper
|
||||
|
@ -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)
|
@ -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,7 +94,20 @@ def _check_component_existance(comp_type: PROP_ballance_element.BallanceElementT
|
||||
if expect_name in bpy.data.objects: return expect_name
|
||||
else: return None
|
||||
|
||||
def _general_create_component(
|
||||
class _GeneralComponentCreator():
|
||||
"""
|
||||
The assist class for general component creation function.
|
||||
Because we need select all created component, thus we need collect all created object into a list.
|
||||
This is the reason why we create this class.
|
||||
"""
|
||||
|
||||
## The list storing all created component within this creation.
|
||||
__mObjList: list[bpy.types.Object]
|
||||
|
||||
def __init__(self):
|
||||
self.__mObjList = []
|
||||
|
||||
def create_component(self,
|
||||
comp_type: PROP_ballance_element.BallanceElementType,
|
||||
comp_sector: int,
|
||||
comp_count: int,
|
||||
@ -111,9 +124,11 @@ def _general_create_component(
|
||||
You can pass `lambda _: mathutils.Matrix( xxx )` to get same offset for every items.
|
||||
You can pass `lambda i: mathutils.Matrix( func(i) )` to get index based offset for each items.
|
||||
The offset is the offset to the origin point, not the previous object.
|
||||
@return The created component instance.
|
||||
"""
|
||||
# get element info first
|
||||
ele_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector)
|
||||
|
||||
# create blc element context
|
||||
with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator:
|
||||
# object creation counter
|
||||
@ -126,6 +141,36 @@ def _general_create_component(
|
||||
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
||||
# move with extra offset by calling offset getter
|
||||
obj.matrix_world = obj.matrix_world @ comp_offset(i)
|
||||
# put into created object list
|
||||
self.__mObjList.append(obj)
|
||||
|
||||
# enlarge scene sector field for non-PS (start point) PE (end point) component
|
||||
# read from scene and create var for enlarged sector count
|
||||
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||
enlarged_sector: int
|
||||
# check component type to get enlarged value
|
||||
match(ele_info.mBasicType):
|
||||
case UTIL_naming_convension.BallanceObjectType.COMPONENT:
|
||||
enlarged_sector = comp_sector
|
||||
case UTIL_naming_convension.BallanceObjectType.CHECKPOINT:
|
||||
# checkpoint 1 means that there is sector 2, so we plus 1 for it.
|
||||
enlarged_sector = comp_sector + 1
|
||||
case UTIL_naming_convension.BallanceObjectType.RESETPOINT:
|
||||
enlarged_sector = comp_sector
|
||||
case _:
|
||||
# this component is not a sector based component
|
||||
# so we do not change it (use original value)
|
||||
enlarged_sector = map_info.mSectorCount
|
||||
# enlarge it
|
||||
map_info.mSectorCount = max(map_info.mSectorCount, enlarged_sector)
|
||||
PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info)
|
||||
|
||||
def finish_component(self) -> None:
|
||||
"""
|
||||
Finish up component creation.
|
||||
Just deselect all objects and select all created components.
|
||||
"""
|
||||
UTIL_functions.select_certain_objects(tuple(self.__mObjList))
|
||||
|
||||
#endregion
|
||||
|
||||
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
36
bbp_ng/OP_MTL_fix_material.py
Normal 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)
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
@ -374,21 +407,66 @@ def _specific_flatten_uv(bm: bmesh.types.BMesh, uv_layer: bmesh.types.BMLayerIte
|
||||
_set_face_vertex_uv(face, uv_layer, ind2, uv2)
|
||||
_set_face_vertex_uv(face, uv_layer, ind3, uv3)
|
||||
|
||||
# then, try getting its right neighbor
|
||||
# do backward correction
|
||||
# in backward mode, we can not know how many space backward one will occupied,
|
||||
# thus we can not pass it by offset because we don't know the offset,
|
||||
# so we only can patch it after computing its real size.
|
||||
if face_backward != NeighborType.Forward:
|
||||
if face_backward == NeighborType.VerticalBackward:
|
||||
# in vertical backward patch,
|
||||
# minus self height for all uv.
|
||||
self_height: float = uv1[1] - uv0[1]
|
||||
uv0 = (uv0[0], uv0[1] - self_height)
|
||||
uv1 = (uv1[0], uv1[1] - self_height)
|
||||
uv2 = (uv2[0], uv2[1] - self_height)
|
||||
uv3 = (uv3[0], uv3[1] - self_height)
|
||||
if face_backward == NeighborType.HorizontalBackward:
|
||||
# in horizontal backward patch, minus self width for all uv.
|
||||
# because we have process rectangle shape issue before this,
|
||||
# so we can pick uv2 or uv3 to get width directly.
|
||||
self_width: float = uv3[0] - uv0[0]
|
||||
uv0 = (uv0[0] - self_width, uv0[1])
|
||||
uv1 = (uv1[0] - self_width, uv1[1])
|
||||
uv2 = (uv2[0] - self_width, uv2[1])
|
||||
uv3 = (uv3[0] - self_width, uv3[1])
|
||||
# set modified uv to geometry
|
||||
_set_face_vertex_uv(face, uv_layer, ind0, uv0)
|
||||
_set_face_vertex_uv(face, uv_layer, ind1, uv1)
|
||||
_set_face_vertex_uv(face, uv_layer, ind2, uv2)
|
||||
_set_face_vertex_uv(face, uv_layer, ind3, uv3)
|
||||
|
||||
# insert horizontal neighbor only in wood mode.
|
||||
if flatten_param.mFlattenMethod == FlattenMethod.Wood:
|
||||
# insert right neighbor (forward)
|
||||
r_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind2, ind0)
|
||||
if r_face is not None:
|
||||
# mark it as processed
|
||||
r_face.tag = True
|
||||
# insert face with extra horizontal offset.
|
||||
face_stack.append((r_face, mathutils.Vector((uv3[0], uv3[1]))))
|
||||
face_stack.append((r_face, mathutils.Vector((uv3[0], uv3[1])), NeighborType.Forward))
|
||||
# insert left neighbor (backward)
|
||||
# swap the index param of neighbor getter
|
||||
l_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind0, ind2)
|
||||
if l_face is not None:
|
||||
l_face.tag = True
|
||||
# pass origin pos, and order backward correction
|
||||
face_stack.append((l_face, mathutils.Vector((uv0[0], uv0[1])), NeighborType.HorizontalBackward))
|
||||
|
||||
# insert vertical neighbor
|
||||
# insert top neighbor (forward)
|
||||
t_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind1, ind3)
|
||||
if t_face is not None:
|
||||
# mark it as processed
|
||||
t_face.tag = True
|
||||
# insert face with extra vertical offset.
|
||||
face_stack.append((t_face, mathutils.Vector((uv1[0], uv1[1]))))
|
||||
face_stack.append((t_face, mathutils.Vector((uv1[0], uv1[1])), NeighborType.Forward))
|
||||
# insert bottom neighbor (backward)
|
||||
# swap the index param of neighbor getter
|
||||
b_face: bmesh.types.BMFace | None = face_neighbor_getter(face, ind3, ind1)
|
||||
if b_face is not None:
|
||||
b_face.tag = True
|
||||
# pass origin pos, and order backward correction
|
||||
face_stack.append((b_face, mathutils.Vector((uv0[0], uv0[1])), NeighborType.VerticalBackward))
|
||||
|
||||
return failed
|
||||
|
||||
@ -420,10 +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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
81
bbp_ng/PROP_ballance_map_info.py
Normal 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)
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -345,6 +345,12 @@ def create_bme_struct(
|
||||
# get result
|
||||
prebuild_vec_data.append((cache_bv.x, cache_bv.y, cache_bv.z))
|
||||
|
||||
# Check whether given transform is mirror matrix
|
||||
# because mirror matrix will reverse triangle indice order.
|
||||
# If matrix is mirror matrix, we need reverse it again in following procession,
|
||||
# including getting uv, calculating normal and providing face data.
|
||||
mirror_matrix: bool = _is_mirror_matrix(transform)
|
||||
|
||||
# prepare mesh part data
|
||||
mesh_part: UTIL_blender_mesh.MeshWriterIngredient = UTIL_blender_mesh.MeshWriterIngredient()
|
||||
def vpos_iterator() -> typing.Iterator[UTIL_virtools_types.VxVector3]:
|
||||
@ -370,7 +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
|
||||
|
@ -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`,
|
||||
|
@ -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
|
||||
|
@ -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_'
|
||||
|
@ -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()
|
||||
|
@ -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)"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
4
docs/docs/en/ballance-properties.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Ballance Properties
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/bme-adder.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Add Floor
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/compile-distribute-plugin.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Compile and Distribute Plugin
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/component-adder.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Add Component
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/configure-plugin.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Configure Plugin
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/group-operations.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Group Operation
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/import-export-virtools.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Import and Export Virtools Document
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
@ -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.
|
||||
|
4
docs/docs/en/install-plugin.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Install Plugin
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/legacy-align.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Legacy Alignment
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/naming-convention.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Naming Convention
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/rail-adder.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Add Rail
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/report-bugs.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Report Issue
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/tech-infos.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Technical Information
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/uv-mapping.md
Normal file
@ -0,0 +1,4 @@
|
||||
# UV Mapping
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
4
docs/docs/en/virtools-properties.md
Normal file
@ -0,0 +1,4 @@
|
||||
# Virtools Properties
|
||||
|
||||
!!! info "Work in Progress"
|
||||
This part of manual still work in progress.
|
BIN
docs/docs/imgs/ballance-properties.png
Normal file
After Width: | Height: | Size: 33 KiB |
BIN
docs/docs/imgs/bme-adder-dialog.png
Normal file
After Width: | Height: | Size: 272 KiB |
BIN
docs/docs/imgs/bme-adder.png
Normal file
After Width: | Height: | Size: 133 KiB |
BIN
docs/docs/imgs/component-adder.png
Normal file
After Width: | Height: | Size: 212 KiB |
BIN
docs/docs/imgs/flatten-uv-flatten-mode.png
Normal file
After Width: | Height: | Size: 744 KiB |
BIN
docs/docs/imgs/flatten-uv-mechanism.png
Normal file
After Width: | Height: | Size: 573 KiB |
BIN
docs/docs/imgs/flatten-uv-scale-mode.png
Normal file
After Width: | Height: | Size: 436 KiB |
BIN
docs/docs/imgs/flatten-uv.png
Normal file
After Width: | Height: | Size: 28 KiB |
BIN
docs/docs/imgs/grouping.png
Normal file
After Width: | Height: | Size: 122 KiB |
BIN
docs/docs/imgs/legacy-align.png
Normal file
After Width: | Height: | Size: 86 KiB |
BIN
docs/docs/imgs/naming-convention.png
Normal file
After Width: | Height: | Size: 74 KiB |
BIN
docs/docs/imgs/rail-adder.png
Normal file
After Width: | Height: | Size: 199 KiB |
BIN
docs/docs/imgs/virtools-group.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
docs/docs/imgs/virtools-material.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
docs/docs/imgs/virtools-mesh.png
Normal file
After Width: | Height: | Size: 39 KiB |
BIN
docs/docs/imgs/virtools-texture.png
Normal file
After Width: | Height: | Size: 57 KiB |
29
docs/docs/zh-cn/ballance-properties.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Ballance属性
|
||||
|
||||
Ballance属性有别于Virtools属性,它是专门为Ballance制图服务的一系列属性。这些属性寄宿于场景,在同一场景中(制图不会涉及Blender中的场景切换),这些属性不会改变。在`Scene`属性面板可以找到Ballance属性相关的面板,如下图所示,分别是:
|
||||
|
||||
* `Ballance Elements`面板(红色箭头),对应Ballance机关
|
||||
* `BME Materials`面板(绿色箭头),对应BME材质
|
||||
* `Ballance Map`面板(蓝色箭头),对应Ballance地图信息
|
||||
|
||||
其中,只有Ballance地图信息是你需要重点关注的,其它属性在通常情况下不需要关注,除非地图中的某些材质或网格出现错误后,才需要关注这些属性。
|
||||
|
||||

|
||||
|
||||
## 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`即可将列表中的所有材质重置为正确状态。
|
30
docs/docs/zh-cn/bme-adder.md
Normal file
@ -0,0 +1,30 @@
|
||||
# 添加路面
|
||||
|
||||
## 开始生成
|
||||
|
||||
在3D视图中,点击`Add - Floors`可展开添加机关菜单。菜单如下图所示。
|
||||
|
||||

|
||||
|
||||
点击菜单后可以在弹出的子菜单中查看所有受支持的路面类型。其名称和图标提示了它所要创建路面的样式与形状。
|
||||
|
||||
!!! info "BME是可扩展的"
|
||||
BME的路面添加器是可扩展的,菜单中的每一个项实际上都由一组JSON数据描述。您可以阅读[技术信息](./tech-infos.md)章节来了解我们是如何编写这些JSON的,甚至您还可以根据你的需求自行扩展BME可创建的路面种类。
|
||||
|
||||
## 配置路面
|
||||
|
||||
点击其中一个路面类型,将打开路面创建对话框,这里我们展示的是Normal Platform(平面平台),如下图所示。在对话框里我们可以配置这个路面类型的各种属性,例如长宽高等距离属性,面的显示与否的属性等,来定制它生成的几何模型,以使其符合我们的要求。
|
||||
|
||||

|
||||
|
||||
在Normal Platform的对话框中,我们首先可以看到它要求我们提供路面的长度,宽度,这决定了我们平台的大小,下面还有对应的文字描述来帮助你理解这个属性具体是控制着什么。
|
||||
|
||||
然后它还需要我们提供这个平台的高度,高度默认为5,即Ballance中默认的路面高度大小,小于5将创建类似“魔虬”地图中的薄路面,大于5将创建类似“魔脓空间站”中非常高的路面墙体。
|
||||
|
||||
最后它指示我们需要配置这个路面哪些面需要显示。需要注意的是,Top和Bottom指的是沿高度方向(Z轴)的顶面和底面,而Front,Back,Left,Right则是以头顶朝向-X轴,眼睛朝向-Z轴俯视状态下的前后左右。您可能注意到这6个面按钮中间有一个透视的六面体,实际上这六个面的选项的位置与这个透视六面体的六个面的位置是一一对应的。
|
||||
|
||||
## 小贴士
|
||||
|
||||
每个路面类型,其配置的条目数是不同的,因此对于不同路面类型,需要根据配置的提示文本来了解对应配置具体是做什么的。一些路面类型所需要设置的条目可能很多,另一些则根本没有配置条目。
|
||||
|
||||
路面类型配置的默认值被设置为创建此路面时最常用的值。每一次切换路面类型或重新创建时,都会将值重置为默认值。
|
@ -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`的文件夹,其中就是可以离线浏览的帮助文档。
|
||||
|
59
docs/docs/zh-cn/component-adder.md
Normal file
@ -0,0 +1,59 @@
|
||||
# 添加机关
|
||||
|
||||
在3D视图中,点击`Add - Components`可展开添加机关菜单。菜单如下图左侧所示。
|
||||
|
||||

|
||||
|
||||
上图右侧则展示了一些机关添加的界面,会在后续依次介绍它们,右侧从上到下分别是:添加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小节的盘点对已经存在,不需要添加。
|
@ -7,7 +7,7 @@
|
||||
|
||||
## 打开配置面板
|
||||
|
||||
开启Blender,选择`编辑 - 偏好设置`,在打开的窗口中转到`插件`选项卡,在`社区`分类下找到BBP插件,名称为`Object: Ballance Blender Plugin`。请确保其左侧的勾已被选中,代表插件已被启用。点击勾左侧的三角箭头展开插件详细信息,如图所示,进入配置面板。
|
||||
开启Blender,选择`Edit - Preferences`,在打开的窗口中转到`Add-ons`选项卡,在`Community`分类下找到BBP插件,名称为`Object: Ballance Blender Plugin`。请确保其左侧的勾已被选中,代表插件已被启用。点击勾左侧的三角箭头展开插件详细信息,如图所示,进入配置面板。
|
||||
|
||||

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

|
||||
|
||||
### Group into...
|
||||
|
||||
把选择物体归入你选择的组。
|
||||
|
||||
### Ungroup from...
|
||||
|
||||
把选择物体从你选择的组中取消归组。
|
||||
|
||||
### Clear All Groups
|
||||
|
||||
清空选择物体的所有归组信息。执行前会让你确认以免误操作。
|
||||
|
55
docs/docs/zh-cn/import-export-virtools.md
Normal file
@ -0,0 +1,55 @@
|
||||
# 导入导出Virtools文档
|
||||
|
||||
!!! warning "这是实验性内容"
|
||||
原生导入导出Virtools文档是BBP插件的试验性内容,它可能存在许多问题,请参阅[报告问题](./report-bugs.md)章节来了解更多。当遇到问题时,请及时汇报。BBP插件的作者不对因BBP插件问题导致的任何后果负责。
|
||||
|
||||
## 导入Virtools文档
|
||||
|
||||
点击`File - Import - Virtools File`可以导入Virtools文档。导入支持CMO,VMO,NMO文件。点击后会弹出文件打开界面,并在侧边栏展示导入设置。首先你需要选择导入的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 Params(Virtools参数)章节的Encodings(编码)属性用于指定读取Virtools文档的编码。可以指定多个编码,多个编码之间用`;`(分号)分隔。下面列出一些常用的编码:
|
||||
|
||||
* 1252(Windows下):Ballance所用的西欧编码
|
||||
* 936(Windows下):中文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 Params(Virtools参数)章节与导入Virtools文档中的类似。Encodings(编码)属性决定了导出Virtools文档时所用的编码。
|
||||
|
||||
Global Texture Save Option(全局贴图保存选项)决定了那些设置了Use Global(使用全局设定)的贴图的真实保存方式。通常而言,设置为Raw Data(原始数据)则可以100%保证保存的Virtools文档可以包含正确贴图,但是其体积也可能会变大,设置为External(外部数据)则可以尽可能减少文件大小,但可能会出现导出的文档找不到贴图文件的问题。我们建议你在进行材质设置时就对每个材质单独指定应该如何保存,而不是依赖全局选项来设置。这个选项是给那些依赖全局贴图保存选项的旧地图的再编辑来使用的。还需要注意的是,尽管这个选项里有Use Global选项,但请 **不要** 选择,否则会导致错误,因为显然你不能让一个全局选项再去使用全局选项的设置。
|
||||
|
||||
Use Compress(使用压缩)属性指定保存的文档是否压缩存储。压缩可以显著减少文档体积,且在现代计算机平台上,压缩所造成的性能损失几乎可以忽略不计。当选择使用压缩后,一个额外的Compress Level(压缩等级)属性将会显示,用于指定压缩的级别,数值越高,压缩率越大,文件越小。
|
||||
|
||||
### Ballance参数
|
||||
|
||||
Ballance Params(Ballance参数)章节包含针对Ballance特有内容,对导出过程进行优化的参数。
|
||||
|
||||
Successive Sector(小节连续)是一个解决导出小节组时出现的Bug的选项。由于某些原因,如果一个小节中没有任何机关(实际上是某小节组中没有归入任何物体),导出插件会认为该小节组不存在,因而遗漏导出。且由于Ballance对最终小节,即飞船出现小节的判定是从1开始递增寻找最后一个存在的小节组,所以二者叠加,会导致Ballance错误地认定地图的小节数,从而在错误的小节显示飞船,这也就是导出Bug。当勾选此选项后,导出文档时会预先按照当前Blender文件中Ballance地图信息中指定的小节数预先创建所有小节组,然后再进行导出,这样就不会遗漏创建某些小节组,飞船也会在正确的小节显示。
|
||||
|
||||
这个选项通常在导出可游玩的地图时选中,如果你只是想导出一些模型,那么需要关闭此选项,否则会在最终文件中产生许多无用的小节组。
|
@ -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 "这些并不是全部"
|
||||
|
@ -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`,找到后勾选名称左侧的勾即可启用插件。插件安装成功后的偏好设置页面如下图所示。
|
||||
|
||||

|
||||
|
||||
|
29
docs/docs/zh-cn/legacy-align.md
Normal file
@ -0,0 +1,29 @@
|
||||
# 传统对齐
|
||||
|
||||
`Ballance - 3ds Max Align`提供了一种类似于3ds Max中对齐方式的对齐功能。
|
||||
|
||||
所谓传统对齐功能,是是将3ds Max中的对齐操作完美地在Blender中重新进行了实现。可以使得很多从3ds Max转来使用Blender的制图人可以更快地上手,并且提供了一些便捷的对齐操作。下图展示了正在运作的传统对齐。
|
||||
|
||||

|
||||
|
||||
## 使用方法
|
||||
|
||||
传统对齐支持将多个物体对一个物体的对齐,操作方法是先依次选中需要被对齐的物体,然后在最后选中对齐参考对象(也就是使其成为活动物体),然后点击`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中积攒的对齐要求。
|
||||
|
29
docs/docs/zh-cn/naming-convention.md
Normal file
@ -0,0 +1,29 @@
|
||||
# 命名规则
|
||||
|
||||
## 自动归组与重命名
|
||||
|
||||
在大纲视图中,对任意集合右键,可以得到自动归组与重命名菜单。
|
||||
|
||||

|
||||
|
||||
本插件目前支持两种命名标准。
|
||||
其一为技术信息章节已经阐述的制图链标准,在本插件中的名称为`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
|
||||
|
||||
根据给定的命名标准,为物体自动填充归组信息。
|
||||
需要注意的是,原有的归组信息会被覆盖。
|
||||
在制图过程中,如果你遵守了某些命名标准,则此功能可以为你自动完成归组功能。
|
81
docs/docs/zh-cn/rail-adder.md
Normal file
@ -0,0 +1,81 @@
|
||||
# 添加钢轨
|
||||
|
||||
在3D视图中,点击`Add - Components`可展开添加钢轨菜单。菜单如下图左侧所示。
|
||||
|
||||

|
||||
|
||||
上图右侧则展示了一些机关添加的界面,会在后续依次介绍它们,右侧从上到下分别是:添加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(步数)属性,含义均与螺旋轨一致。螺旋轨也有封盖属性。
|
29
docs/docs/zh-cn/report-bugs.md
Normal 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`文件以方便开发者定位错误。
|
@ -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版本发布时间的每日编译版本来安装它。
|
||||
|
57
docs/docs/zh-cn/uv-mapping.md
Normal file
@ -0,0 +1,57 @@
|
||||
# UV贴图
|
||||
|
||||
## 钢轨贴图
|
||||
|
||||
3D视图中的菜单`Ballance - Rail UV`提供了给钢轨贴图的功能。
|
||||
|
||||
您需要先选择需要被贴图的钢轨(可以多选),然后再点击此菜单,即可开始进行钢轨贴图。然后你只需要选择钢轨所用的Material(材质)属性即可。此功能将清空选择物体的所有材质,并将各个面所用的材质全部指定为你所指定的材质。然后按照Ballance中对钢轨进行后处理的方式为你选中的物体设置其UV。因此,通过本操作的执行,可以使得钢轨具有和游戏内相同的外貌。
|
||||
|
||||
!!! info "钢轨UV其实并不重要"
|
||||
事实上,所有归入`Phys_FloorRails`组的物体均会在Ballance加载关卡时进行一个后处理。后处理方法的具体操作就是按照某个特定的映射函数重设这些物体的UV(Virtools中调用`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(参考点)模式,我们稍后会说明这两个模式的区别。
|
||||
|
||||

|
||||
|
||||
沿边沿贴图顾名思义,其含义就是沿着某条边进行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仍然需要满足右手坐标系的要求。
|
||||
|
||||

|
||||
|
||||
建立完新坐标系并构建完过渡矩阵后并将每一个顶点都转换到新坐标系下后,我们便可丢弃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(参考点)为2,Reference Point UV(参考点UV)为1也可以贴上相同的贴图,我们来解释一下这里面的原理。首先Reference Point指定参考点,这个参考点是相对于Reference Edge指定的顶点,也就是参考系原点的偏移,因此在这里,Reference Edge为1,Reference 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。
|
||||
|
||||

|
||||
|
||||
最后,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轴上都考虑了面的连续性。
|
||||
|
||||

|
||||
|
||||
!!! info "Floor和Wood展开模式失败了"
|
||||
Floor和Wood相比Raw展开模式具有更多限制,它们只支持四边面,而且对建模的操作有很高要求,通常只有通过批量操作(例如细分,放样等)生成的几何结构才能被Floor和Wood正确识别。
|
||||
|
||||
如果Floor和Wood展开模式失败了,且展开得到的贴图完全不可接受,请尝试使用更加规范地方式建模,或转为手动贴图。
|
59
docs/docs/zh-cn/virtools-properties.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Virtools属性
|
||||
|
||||
## Virtools组
|
||||
|
||||
BBP插件为每一个Blender物体添加了新的属性,被称为Virtools Group。与Virtools中的组具有相同的功能。选择一个物体,在`Object`属性面板可以找到`Virtools Group`面板。
|
||||
|
||||

|
||||
|
||||
在`Virtools Group`面板中,可以点击添加为物体归组。在点击添加按钮后,可以选择预定义,然后从所有合法的Ballance组名中选择一个添加。或选择自定义,然后输入你想要的组名添加。也可以点击删除按钮,删除选中的Virtools组。最后,可以通过点击垃圾桶按钮一次性删除这个物体的所有组数据(删除前会让你确认)。
|
||||
|
||||
BBP还在Blender的其它菜单提供了对Virtools组的访问,具体内容请参阅[按组操作](./group-operations.md)。
|
||||
|
||||
## Virtools材质
|
||||
|
||||
插件为每一个Blender材质添加了新的属性,被称为Virtools Material。它在Virtools材质与Blender材质之间架起沟通的桥梁。转到`Material`属性面板,选择一个材质,即可以找到`Virtools Material`面板。
|
||||
|
||||

|
||||
|
||||
可以在`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贴图属性,如下图高亮部分所示。
|
||||
|
||||

|
||||
|
||||
其中,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`面板。
|
||||
|
||||

|
||||
|
||||
Virtools网格目前只是作为兼容来使用的。其只有Lit Mode一个属性可以设置。多数早期地图由于不知道如何正确设置材质,导致路面发黑,所以经常将Lit Mode设置为Prelit以让路面正常显示。此属性是为了兼容这种设计而存在的,用户通常无需设置此选项。
|
@ -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:
|
||||
|