11 Commits

Author SHA1 Message Date
e22b888bfc doc: fix bme adder doc 2025-09-01 13:16:02 +08:00
88ef1d3202 doc: fix legacy align doc 2025-09-01 13:05:51 +08:00
f2af90c876 doc: fix export target for virtools exporting in doc 2025-09-01 12:56:19 +08:00
4dba3c3a71 doc: add game camera doc 2025-09-01 11:00:34 +08:00
e31a677d83 doc: add version rules in doc 2025-09-01 10:12:44 +08:00
35fcbe54b5 fix: re-design the layout of game camera.
- use more friendly layout in game camera. reported by zzq.
2025-08-30 22:50:43 +08:00
9e83fe0a10 i18n: update i18n
- update i18n template and translation.
- fix lost translation context in code.
2025-08-26 21:54:32 +08:00
33fb1a65d3 chore: remove useless gitkeep 2025-08-26 20:47:06 +08:00
415cc98758 doc: update document
- add hint for gray virtools import export button
- add outcome of leaving blank ballance texture folder setting.
- update compile chapter according to the change of build scripts.
2025-08-26 20:42:11 +08:00
2d93ce1340 feat: allow export all object for virtools file. 2025-08-26 19:59:36 +08:00
1129872234 fix: fix UTIL_naming_convention rename in i18n files. 2025-08-25 14:22:42 +08:00
21 changed files with 2161 additions and 1666 deletions

2
.gitattributes vendored
View File

@ -3,4 +3,4 @@
# Element placeholder mesh should be save as binary
*.ph binary
# Raw json data should be binary, although i edit it manually
assets/jsons/*.json binary
assets/jsons/*.json5 binary

View File

@ -50,7 +50,8 @@ class BBP_OT_game_resolution(bpy.types.Operator):
name = "Resolution Kind",
description = "The type of preset resolution.",
items = _g_EnumHelper_ResolutionKind.generate_items(),
default = _g_EnumHelper_ResolutionKind.to_selection(ResolutionKind.Normal)
default = _g_EnumHelper_ResolutionKind.to_selection(ResolutionKind.Normal),
translation_context = 'BBP_OT_game_resolution/property'
) # type: ignore
def invoke(self, context, event):
@ -185,37 +186,96 @@ class BBP_OT_game_camera(bpy.types.Operator):
name = "Target Kind",
description = "",
items = _g_EnumHelper_TargetKind.generate_items(),
default = _g_EnumHelper_TargetKind.to_selection(TargetKind.Cursor)
default = _g_EnumHelper_TargetKind.to_selection(TargetKind.Cursor),
translation_context = 'BBP_OT_game_camera/property'
) # type: ignore
rotation_kind: bpy.props.EnumProperty(
name = "Rotation Angle Kind",
description = "",
items = _g_EnumHelper_RotationKind.generate_items(),
default = _g_EnumHelper_RotationKind.to_selection(RotationKind.Preset)
default = _g_EnumHelper_RotationKind.to_selection(RotationKind.Preset),
translation_context = 'BBP_OT_game_camera/property'
) # type: ignore
preset_rotation_angle: bpy.props.EnumProperty(
name = "Preset Rotation Angle",
description = "",
# I18N: Property not showen should not have name and desc.
# name = "Preset Rotation Angle",
# description = "",
options = {'HIDDEN'},
items = _g_EnumHelper_RotationAngle.generate_items(),
default = _g_EnumHelper_RotationAngle.to_selection(RotationAngle.Deg0)
default = _g_EnumHelper_RotationAngle.to_selection(RotationAngle.Deg0),
) # type: ignore
def preset_rotation_angle_deg_getter(self, probe) -> bool:
return _g_EnumHelper_RotationAngle.get_selection(self.preset_rotation_angle) == probe
def preset_rotation_angle_deg_setter(self, val) -> None:
self.preset_rotation_angle = _g_EnumHelper_RotationAngle.to_selection(val)
return None
preset_rotation_angle_deg0: bpy.props.BoolProperty(
name = "0 Degree",
translation_context = 'BBP_OT_game_camera/property',
get = lambda self: BBP_OT_game_camera.preset_rotation_angle_deg_getter(self, RotationAngle.Deg0),
set = lambda self, _: BBP_OT_game_camera.preset_rotation_angle_deg_setter(self, RotationAngle.Deg0)
) # type: ignore
preset_rotation_angle_deg45: bpy.props.BoolProperty(
name = "45 Degree",
translation_context = 'BBP_OT_game_camera/property',
get = lambda self: BBP_OT_game_camera.preset_rotation_angle_deg_getter(self, RotationAngle.Deg45),
set = lambda self, _: BBP_OT_game_camera.preset_rotation_angle_deg_setter(self, RotationAngle.Deg45)
) # type: ignore
preset_rotation_angle_deg90: bpy.props.BoolProperty(
name = "90 Degree",
translation_context = 'BBP_OT_game_camera/property',
get = lambda self: BBP_OT_game_camera.preset_rotation_angle_deg_getter(self, RotationAngle.Deg90),
set = lambda self, _: BBP_OT_game_camera.preset_rotation_angle_deg_setter(self, RotationAngle.Deg90)
) # type: ignore
preset_rotation_angle_deg135: bpy.props.BoolProperty(
name = "135 Degree",
translation_context = 'BBP_OT_game_camera/property',
get = lambda self: BBP_OT_game_camera.preset_rotation_angle_deg_getter(self, RotationAngle.Deg135),
set = lambda self, _: BBP_OT_game_camera.preset_rotation_angle_deg_setter(self, RotationAngle.Deg135)
) # type: ignore
preset_rotation_angle_deg180: bpy.props.BoolProperty(
name = "180 Degree",
translation_context = 'BBP_OT_game_camera/property',
get = lambda self: BBP_OT_game_camera.preset_rotation_angle_deg_getter(self, RotationAngle.Deg180),
set = lambda self, _: BBP_OT_game_camera.preset_rotation_angle_deg_setter(self, RotationAngle.Deg180)
) # type: ignore
preset_rotation_angle_deg225: bpy.props.BoolProperty(
name = "225 Degree",
translation_context = 'BBP_OT_game_camera/property',
get = lambda self: BBP_OT_game_camera.preset_rotation_angle_deg_getter(self, RotationAngle.Deg225),
set = lambda self, _: BBP_OT_game_camera.preset_rotation_angle_deg_setter(self, RotationAngle.Deg225)
) # type: ignore
preset_rotation_angle_deg270: bpy.props.BoolProperty(
name = "270 Degree",
translation_context = 'BBP_OT_game_camera/property',
get = lambda self: BBP_OT_game_camera.preset_rotation_angle_deg_getter(self, RotationAngle.Deg270),
set = lambda self, _: BBP_OT_game_camera.preset_rotation_angle_deg_setter(self, RotationAngle.Deg270)
) # type: ignore
preset_rotation_angle_deg315: bpy.props.BoolProperty(
name = "315 Degree",
translation_context = 'BBP_OT_game_camera/property',
get = lambda self: BBP_OT_game_camera.preset_rotation_angle_deg_getter(self, RotationAngle.Deg315),
set = lambda self, _: BBP_OT_game_camera.preset_rotation_angle_deg_setter(self, RotationAngle.Deg315)
) # type: ignore
custom_rotation_angle: bpy.props.FloatProperty(
name = "Custom Rotation Angle",
description = "The rotation angle of camera relative to 3D Cursor",
description = "The rotation angle of camera relative to 3D Cursor or Active Object",
subtype = 'ANGLE',
min = 0, max = math.radians(360),
step = 100,
# MARK: What the fuck of the precision?
# I set it to 2 but it doesn't work so I forcely set it to 100.
precision = 100,
translation_context = 'BBP_OT_game_camera/property'
) # type: ignore
perspective_kind: bpy.props.EnumProperty(
name = "Rotation Angle Kind",
description = "",
items = _g_EnumHelper_PerspectiveKind.generate_items(),
default = _g_EnumHelper_PerspectiveKind.to_selection(PerspectiveKind.Ordinary)
default = _g_EnumHelper_PerspectiveKind.to_selection(PerspectiveKind.Ordinary),
translation_context = 'BBP_OT_game_camera/property'
) # type: ignore
@classmethod
@ -249,7 +309,19 @@ class BBP_OT_game_camera(bpy.types.Operator):
rot_kind = _g_EnumHelper_RotationKind.get_selection(self.rotation_kind)
match rot_kind:
case RotationKind.Preset:
layout.prop(self, 'preset_rotation_angle', text='')
# for preset angles, we show a special layout (grid view)
subgrid = layout.grid_flow(row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
subgrid.prop(self, 'preset_rotation_angle_deg315', toggle = 1)
subgrid.prop(self, 'preset_rotation_angle_deg0', toggle = 1)
subgrid.prop(self, 'preset_rotation_angle_deg45', toggle = 1)
subgrid.prop(self, 'preset_rotation_angle_deg270', toggle = 1)
subicon = subgrid.row()
subicon.alignment = 'CENTER'
subicon.label(text='', icon='MESH_CIRCLE') # show a 3d circle as icon
subgrid.prop(self, 'preset_rotation_angle_deg90', toggle = 1)
subgrid.prop(self, 'preset_rotation_angle_deg225', toggle = 1)
subgrid.prop(self, 'preset_rotation_angle_deg180', toggle = 1)
subgrid.prop(self, 'preset_rotation_angle_deg135', toggle = 1)
case RotationKind.Custom:
layout.prop(self, 'custom_rotation_angle', text='')

View File

@ -137,7 +137,7 @@ class BBP_OT_legacy_align(bpy.types.Operator):
return None
apply_flag: bpy.props.BoolProperty(
# TR: Property not showen should not have name and desc.
# I18N: Property not showen should not have name and desc.
# name = "Apply Flag",
# description = "Internal flag.",
options = {'HIDDEN', 'SKIP_SAVE'},
@ -145,14 +145,14 @@ class BBP_OT_legacy_align(bpy.types.Operator):
update = apply_flag_updated
) # type: ignore
recursive_hinder: bpy.props.BoolProperty(
# TR: Property not showen should not have name and desc.
# I18N: Property not showen should not have name and desc.
# name = "Recursive Hinder",
# description = "An internal flag to prevent the loop calling to apply_flags's updator.",
options = {'HIDDEN', 'SKIP_SAVE'},
default = False
) # type: ignore
align_history : bpy.props.CollectionProperty(
# TR: Property not showen should not have name and desc.
# I18N: Property not showen should not have name and desc.
# name = "Historys",
# description = "Align history.",
type = BBP_PG_legacy_align_history

View File

@ -43,7 +43,7 @@ class BBP_PG_ptrprop_resolver(bpy.types.PropertyGroup):
translation_context = 'BBP_PG_ptrprop_resolver/property'
) # type: ignore
# TR: Properties not showen should not have name and desc.
# I18N: Properties not showen should not have name and desc.
ioport_encodings: bpy.props.CollectionProperty(type = BBP_PG_bmap_encoding) # type: ignore
active_ioport_encodings: bpy.props.IntProperty() # type: ignore

View File

@ -954,6 +954,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(),
translation_context = 'BBP_OT_preset_virtools_material/property'
) # type: ignore
@classmethod

View File

@ -245,10 +245,12 @@ class ExportMode(enum.IntEnum):
BldColl = enum.auto()
BldObj = enum.auto()
BldSelObjs = enum.auto()
BldAllObjs = enum.auto()
_g_ExportModeDesc: dict[ExportMode, tuple[str, str, str]] = {
ExportMode.BldColl: ('Collection', 'Export a collection', 'OUTLINER_COLLECTION'),
ExportMode.BldObj: ('Object', 'Export an object', 'OBJECT_DATA'),
ExportMode.BldSelObjs: ('Selected Objects', 'Export selected objects', 'SELECT_SET'),
ExportMode.BldAllObjs: ('All Objects', 'Export all objects stored in this file', 'FILE_BLEND'),
}
_g_EnumHelper_ExportMode = UTIL_functions.EnumPropHelper(
ExportMode,
@ -275,10 +277,8 @@ class ExportParams():
header.label(text='Export Parameters', text_ctxt='BBP/UTIL_ioport_shared.ExportParams/draw')
if body is None: return
# make prop expand horizontaly, not vertical.
horizon_body = body.row()
# draw switch
horizon_body.prop(self, "export_mode", expand=True)
body.prop(self, "export_mode", expand=True)
# draw picker
export_mode = _g_EnumHelper_ExportMode.get_selection(self.export_mode)
@ -290,6 +290,8 @@ class ExportParams():
ptrprops.draw_export_object(body)
case ExportMode.BldSelObjs:
pass # Draw nothing
case ExportMode.BldAllObjs:
pass # Draw nothing
def general_get_export_objects(self, context: bpy.types.Context) -> tuple[bpy.types.Object, ...] | None:
"""
@ -308,6 +310,8 @@ class ExportParams():
else: return (obj, )
case ExportMode.BldSelObjs:
return tuple(context.selected_objects)
case ExportMode.BldAllObjs:
return tuple(bpy.data.objects)
#endregion

View File

@ -48,7 +48,7 @@ import bpy
# - If we use `bpy.app.translations.pgettext` with other non-Blender functions, such as `print`.
# * Use it as a normal function.
#
# All translation annotation are started with `TR:`
# All translation annotation are started with `I18N:`
#
# The universal translation context prefix for BBP_NG plugin.

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 80 KiB

View File

@ -1,15 +1,25 @@
# 添加路面
!!! info "BME是可扩展的"
BME的路面添加器是可扩展的菜单中的每一个项实际上都由一组JSON数据描述。您可以阅读[技术信息](./tech-infos.md)章节来了解我们是如何编写这些JSON的甚至您还可以根据你的需求自行扩展BME可创建的路面种类。
## 开始生成
### 从添加菜单生成
在3D视图中点击`Add - Floors`可展开添加路面菜单。菜单如下图所示。
![](../imgs/bme-adder.png)
点击菜单后可以在弹出的子菜单中查看所有受支持的路面类型。其名称和图标提示了它所要创建路面的样式与形状。
点击菜单后可以在弹出的子菜单中按分类查看所有受支持的路面类型。其名称和图标提示了它所要创建路面的样式与形状。
!!! info "BME是可扩展的"
BME的路面添加器是可扩展的菜单中的每一个项实际上都由一组JSON数据描述。您可以阅读[技术信息](./tech-infos.md)章节来了解我们是如何编写这些JSON的甚至您还可以根据你的需求自行扩展BME可创建的路面种类。
### 从侧边栏生成
此外还可以通过点按N键打开3D视图的侧边栏在其中找到Ballance选项卡展开Floor面板也可找到如下图所示
![](../imgs/bme-adder-sidebar.png)
该面板相较于添加菜单,其好处在于可常驻在界面之中,避免了在持续添加路面的操作中,频繁打开菜单寻找路面的麻烦。此外,该选项卡中还有用于添加钢轨和机关的面板可供展开,在后续章节中不再赘述。
## 配置路面

View File

@ -12,11 +12,29 @@ BBP的Virtools文件原生导入导出功能依赖BMap以及其Python绑定PyBMa
然后我们需要将配置好的PyBMap拷贝到本项目的根目录下即可完成此步即存在`bbp_ng/PyBMap`文件夹为配置好的PyBMap
## 生成缩略图和压缩JSON
## 生成资源
BBP内置了一系列自定义图标以及其组件BME需要的用于描述结构的JSON文件。通过批量生成缩略图和压缩JSON的操作可以减小这些部分的大小使得其适合在Blender中加载也更方便分发
BBP的正常运行离不开一系列资源文件,而这些资源文件则需要一些处理才能够正常使用
转到`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`文件夹下
为了生成这些资源文件,首先需要转到`scripts`文件夹下,`uv sync`指令来还原脚本环境需提前安装Astral UV
### 生成缩略图
BBP内置了一系列自定义图标但这些图标都以其原始大小存储在库中。通过批量生成缩略图的操作可以减小这些部分的大小使得其适合在Blender中加载也更方便分发。
执行`uv run build_icons.py`来生成缩略图。其实际上是将`assets/icons`目录下的原始图片生成对应的缩略图并存储于`bbp_ng/icons`文件夹下。
### 生成JSON文件
BBP中的BME组件依赖一系列JSON文件来描述原型。这些描述文件以JSON5格式存储在库中方便编写者阅读。通过批量生成操作将这些JSON5文件转换为JSON文件并压缩其大小可以方便其在Blender中加载以及方便插件分发。
如果你是插件开发者或者是这些原型的编写者那么你在生成JSON文件前还需要额外地进行一项操作验证JSON文件的正确性。BBP插件在加载这些JSON文件时会默认这些文件都是正确的无错误的。如果将有错误的JSON文件放入例如缺少部分字段或者拼写错误等则会导致Blender在创建原型时抛出错误。所以验证JSON文件的正确性很有必要。执行`uv run validate_jsons.py`来验证所有原型文件。如果没有任何报错则验证无误。需要注意的是验证器并非完美的它只能尽可能地验证数据确保一些常见的错误例如字段名称拼写错误等不会发生并不能100%保证验证后的文件没有错误。
对于编译人员而言,只需要执行`uv run build_jsons.py`来生成JSON文件即可。其实际上是将`assets/jsons`目录下的原始JSON5文件读取压缩再以JSON格式写入到`bbp_ng/jsons`文件夹下。
### 生成机关网格
BBP中内置了Ballance所有机关占位符的网格信息。执行`uv run build_meshes.py`来部署这些内容,其简单地将`assets/meshes`下的网格文件复制到`bbp_ng/meshes`文件夹下。
## 翻译
@ -31,7 +49,7 @@ Blender对于插件的多语言支持不尽如人意且BBP的设计比较特
### 提取翻译模板
在翻译之前首先你需要意识到BBP需要翻译的文本由两部分组成一部分是BBP插件本身可以通过Blender自带的多语言插件来实现待翻译文本的提取。另一部分是BME组件中的用于描述结构的JSON文件其中各个展示用字段的名称需要进行翻译而这一部分Blender的多语言插件无能为力因为它是动态加载的。幸运的是我们已经写好了一个提取器可以提取BME的JSON文件中的相关待翻译文本当你在进行上一步压缩JSON的操作时实际上压缩器也一并运行了提取待翻译文本写入`bbp_ng/i18n/bme.pot`文件中。那么接下来的任务就只剩下提取插件部分的翻译了。
在翻译之前首先你需要意识到BBP需要翻译的文本由两部分组成一部分是BBP插件本身可以通过Blender自带的多语言插件来实现待翻译文本的提取。另一部分是BME组件中的用于描述结构的JSON文件其中各个展示用字段的名称需要进行翻译而这一部分Blender的多语言插件无能为力因为它是动态加载的。幸运的是我们已经写好了一个提取器可以提取BME的JSON文件中的相关待翻译文本。就是在上一步运行脚本的文件夹中,执行`uv run extract_jsons.py`,脚本就会提取待翻译文本写入`i18n/bme.pot`文件中。那么接下来的任务就只剩下提取插件部分的翻译了。
首先你需要启用Blender内置的多语言插件Manage UI translations。为了启用它你可能还需要下载对应Blender版本的源代码和翻译仓库具体操作方法可参考[Blender的官方文档](https://developer.blender.org/docs/handbook/translating/translator_guide/)。在启用插件并在偏好设置中配置了合适的相关路径后你就可以在Render面板下找到I18n Update Translation面板接下来就可以提取翻译了。按照以下步骤提取翻译
@ -41,11 +59,11 @@ Blender对于插件的多语言支持不尽如人意且BBP的设计比较特
* Simplified Chinese (简体中文)
1. 点击最下方一栏的Refresh I18n Data按钮然后在弹出的窗口中选择Ballance Blender Plugin等待一会后插件就会完成待翻译字符的提取。此时插件只是将他们按照Blender推荐的方式以Python源码的格式将翻译字段提取到了插件的源代码中。
1. 为了获得我们希望的可以用于编辑的POT文件还需要点击Export PO按钮在弹出的窗口中选择Ballance Blender Plugin保存的位置可以选择任意的文件夹例如桌面因为它会产生许多文件其中只有POT文件才是我们想要的取消勾选右侧的Update Existing选项并确保Export POT是勾选的最后进行保存。导出完成后可以在你选择的文件夹中找到一个名为`blender.pot`的翻译模板文件,以及众多以语言标识符为文件名的`.po`文件。
1. 你需要复制`blender.pot``bbp_ng/i18n`文件夹下,并将其重命名为`bbp_ng.pot`。至此我们提取了所有需要翻译的内容。
1. 你需要复制`blender.pot``i18n`文件夹下,并将其重命名为`bbp_ng.pot`。至此我们提取了所有需要翻译的内容。
### 合并翻译模板
现在`bbp_ng/i18n`文件夹下有两个POT文件分别代表了两部分提取的待翻译文本我们需要把他们合并起来。在`bbp_ng/i18n`文件夹执行`xgettext -o blender.pot bbp_ng.pot bme.pot`来进行合并。合并完成后的`bbp_ng/i18n/blender.pot`将用作翻译模板。
现在`i18n`文件夹下有两个POT文件分别代表了两部分提取的待翻译文本我们需要把他们合并起来。在`i18n`文件夹执行`xgettext -o blender.pot bbp_ng.pot bme.pot`来进行合并。合并完成后的`i18n/blender.pot`将用作翻译模板。
### 创建新语言翻译
@ -80,7 +98,7 @@ PO格式的翻译并不能被Blender识别因此在翻译完成后你还
1. 首先确保关闭了所有Blender进程否则插件会保持在加载状态对翻译元组变量的修改会不起作用。
1. 将插件中翻译元组变量`translations_tuple`的值改为空元组参见前文有关提交的注意事项。这一步操作的意图是让整个插件不存在翻译条目这样之后在使用Import PO功能的时候Blender的多语言插件就会认为PO文件中存储的所有字段都是要翻译的就不会出现只导入了一部分翻译的情况因为BME部分的翻译是后来合并入的
1. 打开Blender转到I18n Update Translation面板按照提取翻译模板时的操作方法在语言列表中仅选中需要翻译的语言。
1. 点击最下方一栏的Import PO按钮然后在弹出的窗口中选择Ballance Blender Plugin然后选择`bbp_ng/i18n`文件夹进行导入。这样我们就完成了将PO文件导入为Blender可识别的Python源码格式的操作。
1. 点击最下方一栏的Import PO按钮然后在弹出的窗口中选择Ballance Blender Plugin然后选择`i18n`文件夹进行导入。这样我们就完成了将PO文件导入为Blender可识别的Python源码格式的操作。
## 打包

View File

@ -19,7 +19,9 @@ BBP插件目前有2个设置需要配置。
请填写为Ballance的`Texture`目录插件将从此目录下调用外置贴图文件即Ballance原本带有的贴图文件。点击右侧的文件夹按钮可以浏览文件夹并选择。
这是关乎BBP是否正常运行的关键只有填写正确BBP才不会在运行中出错
该选项几乎是必填写的。如果不填写该项目则Virtools文件导入导出BME创建钢轨创建等各种核心功能将不可用按钮为灰色
该选项是BBP能否正常运行的关键只有填写正确BBP才不会在运行中出错。
### No Component Collection

View File

@ -39,7 +39,12 @@ Conflict Options冲突解决选项章节指示了当导入器遇到物体
### 导出目标
Export Target导出目标章节用于决定你需要将哪写物体导出到Virtools文档中。你可以选择导出一个集合或一个物体,并在下面选择对应的集合或物体。需要注意的是,选择集合的时候,会将内部集合中的物体也一起导出,即支持嵌套集合的导出。
Export Target导出目标章节用于决定你需要将哪写物体导出到Virtools文档中。你可以在四种模式中选择其一,来决定需要导出的内容:
* Object导出单个物体。选择后需要在下面选择需要导出的物体。
* Collection导出单个集合**这是最常用的选项**。选择后需要在下面选择需要导出的集合。值得注意的是,该选项支持嵌套集合导出,即选择集合的时候,会将集合中的集合的物体也一起导出。
* Selected Objects导出选择的物体。你需要在导出前选择好需要导出的物体。
* All Objects导出该文档中的所有物体。该选项慎用因为它是粗暴地遍历文档中的物体列表来进行导出很可能会导出许多你不需要的物体。
### Virtools参数
@ -56,3 +61,11 @@ Ballance ParamsBallance参数章节包含针对Ballance特有内容
Successive Sector小节连续是一个解决导出小节组时出现的Bug的选项。由于某些原因如果一个小节中没有任何机关实际上是某小节组中没有归入任何物体导出插件会认为该小节组不存在因而遗漏导出。且由于Ballance对最终小节即飞船出现小节的判定是从1开始递增寻找最后一个存在的小节组所以二者叠加会导致Ballance错误地认定地图的小节数从而在错误的小节显示飞船这也就是导出Bug。当勾选此选项后导出文档时会预先按照当前Blender文件中Ballance地图信息中指定的小节数预先创建所有小节组然后再进行导出这样就不会遗漏创建某些小节组飞船也会在正确的小节显示。
这个选项通常在导出可游玩的地图时选中,如果你只是想导出一些模型,那么需要关闭此选项,否则会在最终文件中产生许多无用的小节组。
## 无法导入导出
通常而言在你正确按照之前介绍的步骤安装和配置插件后你理论上就可以使用Virtools文档的导入导出功能了。但难免有意外发生。这里说的无法导入导出指的是导入和导出Virtools文档的按钮是灰色的不可点击的。你需要按照下述步骤检查。如果你所指的是在导入导出过程中出错了请参考本页页首的警告信息。
首先检查你是否已经在[配置插件](configure-plugin.md)章节正确配置了插件的`External Texture Folder`设置项。如果你没有配置则自然无法导入导出Virtools文件。因为导入导出Virtools文件需要依赖Ballance原版关卡数据。请按教程认真配置这一设置项。
如果你确定你已经设置了正确的贴图路径,你可以尝试点击菜单`Window - Toggle System Console`来打开控制台。在控制台中可能会有一些相关的错误信息输出在其中例如加载底层BMap库时失败等。加载底层BMap库失败通常只发生在一些罕见架构的机器上例如使用Snapdragon处理器的Windows系统等因为我们打包的插件中并未包含支持这些平台的Virtools文件底层读取库BMap。在面对这种情况时你有两个选择一是汇报给开发者等待开发者主动支持二是自行编译该库仅建议有丰富计算机知识的人这样做

View File

@ -14,9 +14,11 @@
在面板中,`Align Axis`指定了你要对齐的轴,此处可以多选以指定多个轴,不指定任何轴将无法进行对齐操作,因而也无法点击`Apply`按钮。
`Current Object`是对齐参考物体,也就是场景中的活动物体,通常也就是你选择的最后一个物体。在这个选项里指定你需要参考其什么数值进行对齐,分别有`Min`(轴上最小值)、`Center (Bounding Box)`(碰撞箱的中心)、`Center (Axis)`(物体的原点)、`Max`轴上的最大值可选。这些选项与3ds Max中的对齐选项是一致的
`Current Object`指示选择哪个实例作为对齐参考。你可以选择场景中的活动物体,通常也就是你选择的最后一个物体。或者是3D游标。需要注意的是如果你选择活动物体模式那么活动物体将排除在对齐操作之外不会被移动因为参考物体是不可动的。而如果你选择3D游标则活动物体会被纳入对齐操作的范围之中
`Target Objects`是正在被对齐的物体,可能有很多个,在这个选项里也是指定你需要参考其什么数值进行对齐。选项与`Current Object`含义一致。
`Current Object Align Mode`是对齐参考物体的对齐模式它只有在你选择活动物体作为对齐参考物体时才会出现。因为3D游标是一个单纯的点而物体占有一定体积我们需要按照某种模式后文叙述在空间中选择一个点作为后续对齐操作时使用的点。在这个选项里指定你需要参考其什么数值进行对齐,分别有`Min`(轴上最小值)、`Center (Bounding Box)`(碰撞箱的中心)、`Center (Axis)`(物体的原点)、`Max`轴上的最大值可选。这些选项与3ds Max中的对齐选项是一致
`Target Objects Align Mode`是正在被对齐的物体,可能有很多个,在这个选项里也是指定你需要参考其什么数值进行对齐。选项与`Current Object Align Mode`含义一致。
`Apply`按钮点击后将把当前页面的配置压入操作栈,并重置上面的设置,使得你可以开始新一轮对齐操作而无需再次执行传统对齐。操作栈中的操作个数在`Apply`按钮下方显示。

View File

@ -1,9 +1,13 @@
# 技术信息
## 标准与协议文档
* 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的类型提示库。
* Blender 3.6: `pip install fake-bpy-module-latest==20230627`
@ -11,3 +15,16 @@
* Blender 4.5: `pip install fake-bpy-module-latest==20250604`
这么做主要是因为`fake-bpy-module`没有很及时地发布适用于指定Blender版本的包因此我只能通过选择最接近Blender对应版本离开`main`主线时间的每日编译版本来安装它(因为每日编译版本只编译`main`主线)。
!!! question "为什么不采用Blender官方的bpy模块"
Blender在PyPI上提供了官方的名为`bpy`的包但我们不会采用它作为我们的开发辅助包。因为它基本上就是将Blender打包成了一个模块也就意味着你基本上又把Blender重新下载了一遍使得你可以通过Python来操纵Blender。这与我们使用一个仅提供类型提示的包来辅助插件开发的目的相悖。
## 版本号规则
BBP的版本号格式遵循[语义化版本](https://semver.org/lang/zh-CN/)。但略有区别:
* 主版本号只在重构整个插件时提升。
* 次版本号是常规更新使用。
* 修订号则是在不修改任何功能的情况下递增的版本号。例如4.2.1版本仅增加了对macOS Blender的更新不更改任何功能。
在BBP发布一个正式版前通常有3个阶段性版本分别是Alpha版本Beta版本和RC版本。Alpha版本专注于功能性更新用于检验新添加或修改的功能是否正常工作不包含文档和翻译。Beta版本则专注于插件文档而RC版本则关注于插件翻译。但这三个版本并非总是存在如果更新内容较少则可能会跳过其中一些版本或直接进行发布。

View File

@ -7,3 +7,31 @@
窥视归组并转换为网格其全称为窥视并复制曲线倒角物体的Virtools归组信息后再转换为网格。你可以选中一些物体后右键在物体上下文菜单中找到这一功能。
该功能正如其名,其将选中的物体转换为网格,如果选中的物体是曲线,且设置了倒角物体,则将倒角物体的归组信息赋予当前曲线(覆盖曲线当前归组设置)。如果选中的物体不是曲线,或者是曲线但没有倒角物体,那么该功能与执行转换为网格无异。该功能在放样建模时极为有用,因为只需要为截面物体进行正确的归组,然后再使用此功能将曲线转换为网格,就可以确保放样后的物体归组正确。
## 游戏内摄像机
许多制图者在制作地图时,往往对地图大小没有概念,很容易创建出过大或过小的地图。菜单项`Ballance - Game Camera`的游戏内摄像机功能则提供了一种在Blender内以游戏内摄像机视角预览地图的功能方便制图者可以把握地图的大小。
为了使用该功能,你的场景中必须首先有一个摄像机,且被设置为场景的活动摄像机(无摄像机时,通过添加菜单新添加的摄像机会被自动设置)。然后你还需要一个活动物体,且该活动物体不能是这个摄像机。
!!! question "为什么一定需要活动物体?"
由于Blender插件的限制活动物体是必须的因为游戏内摄像机功能支持以3D游标 **或活动物体** 为目标。
通常只有在一个空白的Blender文档中才会出现无活动物体可用的情况进而导致该功能不可用。对于一张自制地图最不缺少的就是可成为活动物体的物体只需要随便点击一个物体就可以得到当想使用3D游标作为目标时
点击该功能后,视图将自动转为活动摄像机的视角,方便你进行预览。你可以在左下角的面板中调整相关设置。
![](../imgs/game-camera.png)
首先是选择目标实际上就是选择玩家球位于哪里。如果选择3D游标则将3D游标视为玩家球。如果选择活动物体则将玩家球放置在活动物体的原点。
!!! info "玩家球的位置并非那么简单"
无论是选择3D游标还是选择活动物体如果不做特别精细的调整其预览的结果和游戏中会有细微出入。
当你使用3D游标模式并将其简单地吸附到地面上时它并非代表球的位置。因为玩家球是一个半径为2的球实际上你需要向+Z方向移动2个单位长度才能够得到和游戏中一模一样的视角。但这个操作过于繁琐不执行这个操作预览的效果也不会偏差太多。
当你使用活动物体模式时需要注意物体的原点是选择物体时显示的那个圆点的位置。对于大多数物体这可能并非你想要的因为他们可能位于物体内部或外部并非总位于物体表面或者某个面的中心。针对这种情况我建议你改用3D游标作为目标并通过进入编辑模式灵活运用吸附和`Shift + S`菜单,来将游标放置在正确的位置。
然后是选择摄像机的旋转角度。我们提供了8种游戏内预设角度分别对应90度和45度的各4种。此外如果这些预设角度不能满足你的需求你还可以设置自定义角度。
最后是选择摄像机视角分为Ordinary常规视角Lift按住空格键的视角和Easter Egg彩蛋视角三种。

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File