16 Commits

Author SHA1 Message Date
2e76ce7862 feat: bump up version 2026-03-20 14:55:38 +08:00
eb8984c341 refactor: use python built in dataclass to improve our raw data structs. 2026-03-20 14:50:05 +08:00
77e4dcdb69 fix: rename some types in module 2026-03-20 14:16:44 +08:00
44d3b1fc99 feat: finish virtools camera work
- add operator for applying camera aspect ratio to blender scene.
- add camera aspect ratio preset in virtools camera panel.
- update game camera operators. use virtools camera instead of directly modifying camera properties.
2026-03-20 13:49:00 +08:00
70fa3b5f07 feat: support virtools camera import and export 2026-03-13 15:47:24 +08:00
fbee0ccce5 doc: update README for notes 2026-03-06 16:14:47 +08:00
684567777f feat: add virtools camera support 2026-03-06 16:10:05 +08:00
5e94d7db85 fix: change the default length of flat and sink street from 2.5 to 5
- this feature is requested by Lee623
2026-03-06 14:40:50 +08:00
8a7e3306a7 feat: allow exporting object in virtools file which has geometry.
- allow exporting object without apply modifier.
- allow exporting any objects which can be mesh (has geometry).
- due to this change, add virtools mesh properties for metaball, font, curve, surface.
- due to this change, remove the virtools group warning for metaball, font, curve and surface.
2026-03-05 21:45:36 +08:00
68fbffad54 feat: update pybmap to the latest version 2026-03-05 12:45:25 +08:00
dd6e557f39 refactor: rename PyBMap to pybmap due to the change of libcmo21 2026-03-04 21:56:11 +08:00
e4a4eae88e fix: fix fatal error when exporting virtools file
- fix fatal error that user only can export virtools file into existing file, rather than any new file.
2025-09-23 21:45:56 +08:00
f920cdcaf5 i18n: fix translation 2025-09-04 10:01:41 +08:00
40cad381af i18n: update translation 2025-09-03 09:50:56 +08:00
0d20a1fe44 doc: update english doc 2025-09-02 13:51:51 +08:00
599e9a6ef0 doc: sync zh-cn doc to en doc 2025-09-01 13:59:31 +08:00
38 changed files with 1603 additions and 870 deletions

View File

@@ -5,3 +5,9 @@
BBP NG, abbr **B**allance **B**lender **P**lugin **N**ext **G**eneration.
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
## Notes
This project is now suspended and may be still suspended eternally
due to the lost interest and the lack of time of mine.
In the rest of life time of this project, only easy-to-be-resolved critical bugs will be fixed.

View File

@@ -5,3 +5,8 @@
BBP NG又名**B**allance **B**lender **P**lugin **N**ext **G**eneration下一代Ballance Blender插件
有关此插件的介绍安装编译汇报错误等请参阅本项目的GitHub Page页面https://yyc12345.github.io/BallanceBlenderHelper
## 注意事项
该项目的开发目前已暂停,并且由于我兴趣的丧失和时间的不足,可能会永远暂停。
在该项目剩余的生命周期中,只会修复易于解决的致命错误。

View File

@@ -1,324 +1,324 @@
[
{
"identifier": "floor_normal_straight",
"showcase": {
"title": "Normal Floor",
"category": "Floors",
"icon": "NormalFloor",
"type": "floor",
"cfgs": [
{
"field": "height_",
"type": "float",
"title": "Height",
"desc": "The height of block.",
"default": "5.0"
},
{
"field": "length_",
"type": "float",
"title": "Length",
"desc": "The length of block.",
"default": "2.5"
},
{
"field": "face_",
"type": "face",
"title": "Face",
"desc": "Whether has some faces.",
"default": "(True, False, False, False, True, True)"
}
]
},
"params": [
{
"field": "height",
"data": "height_"
},
{
"field": "length",
"data": "length_"
},
{
"field": "face",
"data": "face_"
}
],
"skip": "length == 0",
"vars": [
{
"field": "length_uv",
"data": "length / 5.0"
}
],
"vertices": [
{
"skip": "not face[0]",
"data": "(0, 0, 0)"
},
{
"skip": "not face[0]",
"data": "(length, 0, 0)"
},
{
"skip": "not face[0]",
"data": "(length, 5, 0)"
},
{
"skip": "not face[0]",
"data": "(0, 5, 0)"
}
],
"faces": [
{
"skip": "not face[0]",
"texture": "\"FloorTopFlat\"",
"indices": [0, 1, 2, 3],
"uvs": [
"(0, 0)",
"(0, length_uv)",
"(1, length_uv)",
"(1, 0)"
],
"normals": null
}
],
"instances": [
{
"identifier": "raw_floor_side",
"skip": "not face[2]",
"params": {
"height": "height",
"length": "5",
"is_left_sink": "False",
"is_right_sink": "False"
},
"transform": "rot(0, 0, 90) @ scale(1, -1, 1)"
},
{
"identifier": "raw_floor_side",
"skip": "not face[3]",
"params": {
"height": "height",
"length": "5",
"is_left_sink": "False",
"is_right_sink": "False"
},
"transform": "move(length, 0, 0) @ rot(0, 0, 90)"
},
{
"identifier": "raw_floor_side",
"skip": "not face[4]",
"params": {
"height": "height",
"length": "length",
"is_left_sink": "False",
"is_right_sink": "False"
},
"transform": "ident()"
},
{
"identifier": "raw_floor_side",
"skip": "not face[5]",
"params": {
"height": "height",
"length": "length",
"is_left_sink": "False",
"is_right_sink": "False"
},
"transform": "move(0, 5, 0) @ scale(1, -1, 1)"
},
{
"identifier": "floor_rectangle_bottom",
"skip": "not face[1]",
"params": {
"length": "length",
"width": "5"
},
"transform": "move(0, 0, -height)"
}
]
},
{
"identifier": "floor_sink_straight",
"showcase": {
"title": "Sink Floor",
"category": "Floors",
"icon": "SinkFloor",
"type": "floor",
"cfgs": [
{
"field": "height_",
"type": "float",
"title": "Height",
"desc": "The height of block.",
"default": "5.0"
},
{
"field": "length_",
"type": "float",
"title": "Length",
"desc": "The length of block.",
"default": "2.5"
},
{
"field": "face_",
"type": "face",
"title": "Face",
"desc": "Whether has some faces.",
"default": "(True, False, False, False, True, True)"
}
]
},
"params": [
{
"field": "height",
"data": "height_"
},
{
"field": "length",
"data": "length_"
},
{
"field": "face",
"data": "face_"
}
],
"skip": "length == 0",
"vars": [
{
"field": "length_uv",
"data": "length / 5.0"
}
],
"vertices": [
{
"skip": "not face[0]",
"data": "(0, 0, 0)"
},
{
"skip": "not face[0]",
"data": "(length, 0, 0)"
},
{
"skip": "not face[0]",
"data": "(length, 2.5, -0.7)"
},
{
"skip": "not face[0]",
"data": "(0, 2.5, -0.7)"
},
{
"skip": "not face[0]",
"data": "(length, 5, 0)"
},
{
"skip": "not face[0]",
"data": "(0, 5, 0)"
}
],
"faces": [
{
"skip": "not face[0]",
"texture": "\"FloorTopProfil\"",
"indices": [0, 1, 2, 3],
"uvs": [
"(0, 0)",
"(0, length_uv)",
"(0.5, length_uv)",
"(0.5, 0)"
],
"normals": null
},
{
"skip": "not face[0]",
"texture": "\"FloorTopProfil\"",
"indices": [3, 2, 4, 5],
"uvs": [
"(0.5, 0)",
"(0.5, length_uv)",
"(1, length_uv)",
"(1, 0)"
],
"normals": null
}
],
"instances": [
{
"identifier": "raw_floor_side",
"skip": "not face[2]",
"params": {
"height": "height",
"length": "2.5",
"is_left_sink": "False",
"is_right_sink": "True"
},
"transform": "rot(0, 0, 90) @ scale(1, -1, 1)"
},
{
"identifier": "raw_floor_side",
"skip": "not face[2]",
"params": {
"height": "height",
"length": "2.5",
"is_left_sink": "True",
"is_right_sink": "False"
},
"transform": "move(0, 2.5, 0) @ rot(0, 0, 90) @ scale(1, -1, 1)"
},
{
"identifier": "raw_floor_side",
"skip": "not face[3]",
"params": {
"height": "height",
"length": "2.5",
"is_left_sink": "False",
"is_right_sink": "True"
},
"transform": "move(length, 0, 0) @ rot(0, 0, 90)"
},
{
"identifier": "raw_floor_side",
"skip": "not face[3]",
"params": {
"height": "height",
"length": "2.5",
"is_left_sink": "True",
"is_right_sink": "False"
},
"transform": "move(length, 2.5, 0) @ rot(0, 0, 90)"
},
{
"identifier": "raw_floor_side",
"skip": "not face[4]",
"params": {
"height": "height",
"length": "length",
"is_left_sink": "False",
"is_right_sink": "False"
},
"transform": "ident()"
},
{
"identifier": "raw_floor_side",
"skip": "not face[5]",
"params": {
"height": "height",
"length": "length",
"is_left_sink": "False",
"is_right_sink": "False"
},
"transform": "move(0, 5, 0) @ scale(1, -1, 1)"
},
{
"identifier": "floor_rectangle_bottom",
"skip": "not face[1]",
"params": {
"length": "length",
"width": "5"
},
"transform": "move(0, 0, -height)"
}
]
}
[
{
"identifier": "floor_normal_straight",
"showcase": {
"title": "Normal Floor",
"category": "Floors",
"icon": "NormalFloor",
"type": "floor",
"cfgs": [
{
"field": "height_",
"type": "float",
"title": "Height",
"desc": "The height of block.",
"default": "5.0"
},
{
"field": "length_",
"type": "float",
"title": "Length",
"desc": "The length of block.",
"default": "5.0"
},
{
"field": "face_",
"type": "face",
"title": "Face",
"desc": "Whether has some faces.",
"default": "(True, False, False, False, True, True)"
}
]
},
"params": [
{
"field": "height",
"data": "height_"
},
{
"field": "length",
"data": "length_"
},
{
"field": "face",
"data": "face_"
}
],
"skip": "length == 0",
"vars": [
{
"field": "length_uv",
"data": "length / 5.0"
}
],
"vertices": [
{
"skip": "not face[0]",
"data": "(0, 0, 0)"
},
{
"skip": "not face[0]",
"data": "(length, 0, 0)"
},
{
"skip": "not face[0]",
"data": "(length, 5, 0)"
},
{
"skip": "not face[0]",
"data": "(0, 5, 0)"
}
],
"faces": [
{
"skip": "not face[0]",
"texture": "\"FloorTopFlat\"",
"indices": [0, 1, 2, 3],
"uvs": [
"(0, 0)",
"(0, length_uv)",
"(1, length_uv)",
"(1, 0)"
],
"normals": null
}
],
"instances": [
{
"identifier": "raw_floor_side",
"skip": "not face[2]",
"params": {
"height": "height",
"length": "5",
"is_left_sink": "False",
"is_right_sink": "False"
},
"transform": "rot(0, 0, 90) @ scale(1, -1, 1)"
},
{
"identifier": "raw_floor_side",
"skip": "not face[3]",
"params": {
"height": "height",
"length": "5",
"is_left_sink": "False",
"is_right_sink": "False"
},
"transform": "move(length, 0, 0) @ rot(0, 0, 90)"
},
{
"identifier": "raw_floor_side",
"skip": "not face[4]",
"params": {
"height": "height",
"length": "length",
"is_left_sink": "False",
"is_right_sink": "False"
},
"transform": "ident()"
},
{
"identifier": "raw_floor_side",
"skip": "not face[5]",
"params": {
"height": "height",
"length": "length",
"is_left_sink": "False",
"is_right_sink": "False"
},
"transform": "move(0, 5, 0) @ scale(1, -1, 1)"
},
{
"identifier": "floor_rectangle_bottom",
"skip": "not face[1]",
"params": {
"length": "length",
"width": "5"
},
"transform": "move(0, 0, -height)"
}
]
},
{
"identifier": "floor_sink_straight",
"showcase": {
"title": "Sink Floor",
"category": "Floors",
"icon": "SinkFloor",
"type": "floor",
"cfgs": [
{
"field": "height_",
"type": "float",
"title": "Height",
"desc": "The height of block.",
"default": "5.0"
},
{
"field": "length_",
"type": "float",
"title": "Length",
"desc": "The length of block.",
"default": "5.0"
},
{
"field": "face_",
"type": "face",
"title": "Face",
"desc": "Whether has some faces.",
"default": "(True, False, False, False, True, True)"
}
]
},
"params": [
{
"field": "height",
"data": "height_"
},
{
"field": "length",
"data": "length_"
},
{
"field": "face",
"data": "face_"
}
],
"skip": "length == 0",
"vars": [
{
"field": "length_uv",
"data": "length / 5.0"
}
],
"vertices": [
{
"skip": "not face[0]",
"data": "(0, 0, 0)"
},
{
"skip": "not face[0]",
"data": "(length, 0, 0)"
},
{
"skip": "not face[0]",
"data": "(length, 2.5, -0.7)"
},
{
"skip": "not face[0]",
"data": "(0, 2.5, -0.7)"
},
{
"skip": "not face[0]",
"data": "(length, 5, 0)"
},
{
"skip": "not face[0]",
"data": "(0, 5, 0)"
}
],
"faces": [
{
"skip": "not face[0]",
"texture": "\"FloorTopProfil\"",
"indices": [0, 1, 2, 3],
"uvs": [
"(0, 0)",
"(0, length_uv)",
"(0.5, length_uv)",
"(0.5, 0)"
],
"normals": null
},
{
"skip": "not face[0]",
"texture": "\"FloorTopProfil\"",
"indices": [3, 2, 4, 5],
"uvs": [
"(0.5, 0)",
"(0.5, length_uv)",
"(1, length_uv)",
"(1, 0)"
],
"normals": null
}
],
"instances": [
{
"identifier": "raw_floor_side",
"skip": "not face[2]",
"params": {
"height": "height",
"length": "2.5",
"is_left_sink": "False",
"is_right_sink": "True"
},
"transform": "rot(0, 0, 90) @ scale(1, -1, 1)"
},
{
"identifier": "raw_floor_side",
"skip": "not face[2]",
"params": {
"height": "height",
"length": "2.5",
"is_left_sink": "True",
"is_right_sink": "False"
},
"transform": "move(0, 2.5, 0) @ rot(0, 0, 90) @ scale(1, -1, 1)"
},
{
"identifier": "raw_floor_side",
"skip": "not face[3]",
"params": {
"height": "height",
"length": "2.5",
"is_left_sink": "False",
"is_right_sink": "True"
},
"transform": "move(length, 0, 0) @ rot(0, 0, 90)"
},
{
"identifier": "raw_floor_side",
"skip": "not face[3]",
"params": {
"height": "height",
"length": "2.5",
"is_left_sink": "True",
"is_right_sink": "False"
},
"transform": "move(length, 2.5, 0) @ rot(0, 0, 90)"
},
{
"identifier": "raw_floor_side",
"skip": "not face[4]",
"params": {
"height": "height",
"length": "length",
"is_left_sink": "False",
"is_right_sink": "False"
},
"transform": "ident()"
},
{
"identifier": "raw_floor_side",
"skip": "not face[5]",
"params": {
"height": "height",
"length": "length",
"is_left_sink": "False",
"is_right_sink": "False"
},
"transform": "move(0, 5, 0) @ scale(1, -1, 1)"
},
{
"identifier": "floor_rectangle_bottom",
"skip": "not face[1]",
"params": {
"length": "length",
"width": "5"
},
"transform": "move(0, 0, -height)"
}
]
}
]

4
bbp_ng/.gitignore vendored
View File

@@ -1,7 +1,7 @@
## ===== Personal =====
# Do not include PyBMap in this repository.
# Do not include pybmap in this repository.
# Order user build and fetch it manually.
PyBMap/
pybmap/
# Disable generated assets but keep the directory hierarchy.
icons/*

View File

@@ -1,10 +1,11 @@
import bpy, mathutils
from bpy_extras.wm_utils.progress_report import ProgressReport
import tempfile, os, typing
from dataclasses import dataclass
from . import PROP_preferences, UTIL_ioport_shared, UTIL_naming_convention
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, PROP_virtools_light
from .PyBMap import bmap_wrapper as bmap
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_virtools_light, PROP_virtools_camera
from .pybmap import bmap_wrapper as bmap
class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtoolsFile, UTIL_ioport_shared.ExportParams, UTIL_ioport_shared.VirtoolsParams, UTIL_ioport_shared.BallanceParams):
"""Export Virtools File"""
@@ -44,9 +45,18 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
self.report({'ERROR'}, 'You must specify at least one encoding for file saving (e.g. cp1252, gbk)!')
return {'CANCELLED'}
# check file name
# check file name, but has slightly difference with importer.
# when exporting, file can be inexisting in file system, so we can't check it directly.
filename = self.general_get_filename()
if not os.path.isfile(filename):
bad_filename: bool = False
# for filename, at first, it should not be empty surely.
if len(filename) == 0:
bad_filename = True
# then, if it is existing, it should be a file, otherwise everything is okey.
if os.path.exists(filename) and (not os.path.isfile(filename)):
bad_filename = True
# now, show message and exit if we have bad file name
if bad_filename:
self.report({'ERROR'}, 'No file was selected!')
return {'CANCELLED'}
@@ -72,11 +82,18 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
self.draw_virtools_params(context, layout, False)
self.draw_ballance_params(layout, False)
_TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject]
_TLightPair = tuple[bpy.types.Object, bpy.types.Light, bmap.BMTargetLight]
_TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh]
_TMaterialPair = tuple[bpy.types.Material, bmap.BMMaterial]
_TTexturePair = tuple[bpy.types.Image, bmap.BMTexture]
_Object3dPair = tuple[bpy.types.Object, bmap.BM3dObject]
_LightPair = tuple[bpy.types.Object, bpy.types.Light, bmap.BMTargetLight]
_CameraPair = tuple[bpy.types.Object, bpy.types.Camera, bmap.BMTargetCamera]
_MeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh]
_MaterialPair = tuple[bpy.types.Material, bmap.BMMaterial]
_TexturePair = tuple[bpy.types.Image, bmap.BMTexture]
@dataclass
class _PreparedCrets:
obj3d_crets: tuple[_Object3dPair, ...]
light_crets: tuple[_LightPair, ...]
camera_crets: tuple[_CameraPair, ...]
def _export_virtools(
file_name_: str,
@@ -103,22 +120,21 @@ def _export_virtools(
# prepare progress reporter
with ProgressReport(wm = bpy.context.window_manager) as progress:
# prepare 3dobject and light
obj3d_crets: tuple[_TObj3dPair, ...]
light_crets: tuple[_TLightPair, ...]
(obj3d_crets, light_crets) = _prepare_virtools_3dobjects(writer, progress, export_objects)
# prepare 3dobject, light and camera
prep_crets = _prepare_virtools_3dobjects(writer, progress, export_objects)
# export group according to prepared 3dobject
_export_virtools_groups(writer, progress, successive_sector_, successive_sector_count_, obj3d_crets)
# export prepared light
_export_virtools_light(writer, progress, light_crets)
_export_virtools_groups(writer, progress, successive_sector_, successive_sector_count_, prep_crets.obj3d_crets)
# export prepared light and camera
_export_virtools_light(writer, progress, prep_crets.light_crets)
_export_virtools_camera(writer, progress, prep_crets.camera_crets)
# export prepared 3dobject
mesh_crets: tuple[_TMeshPair, ...] = _export_virtools_3dobjects(
writer, progress, obj3d_crets)
mesh_crets: tuple[_MeshPair, ...] = _export_virtools_3dobjects(
writer, progress, prep_crets.obj3d_crets)
# export mesh
material_crets: tuple[_TMaterialPair, ...] = _export_virtools_meshes(
material_crets: tuple[_MaterialPair, ...] = _export_virtools_meshes(
writer, progress, mesh_crets)
# export material
texture_crets: tuple[_TTexturePair, ...] = _export_virtools_materials(
texture_crets: tuple[_TexturePair, ...] = _export_virtools_materials(
writer, progress, material_crets)
# export texture
_export_virtools_textures(writer, progress, vt_temp_folder, texture_crets)
@@ -131,7 +147,7 @@ def _prepare_virtools_3dobjects(
writer: bmap.BMFileWriter,
progress: ProgressReport,
export_objects: tuple[bpy.types.Object, ...]
) -> tuple[tuple[_TObj3dPair, ...], tuple[_TLightPair, ...]]:
) -> _PreparedCrets:
# this function only create equvalent entries in virtools engine and do not export anything
# because _export_virtools_3dobjects() and _export_virtools_groups() are need use the return value of this function
#
@@ -139,52 +155,65 @@ def _prepare_virtools_3dobjects(
# we also need extract exported lights and create equvalent entries in virtools for them.
# create 3dobject hashset and result
obj3d_crets: list[_TObj3dPair] = []
obj3d_crets: list[_Object3dPair] = []
obj3d_cret_set: set[bpy.types.Object] = set()
# create light hashset and result
light_crets: list[_TLightPair] = []
light_crets: list[_LightPair] = []
light_cret_set: set[bpy.types.Object] = set()
# create camera hashset and result
camera_crets: list[_CameraPair] = []
camera_cret_set: set[bpy.types.Object] = set()
# start saving
tr_text: str = bpy.app.translations.pgettext_rpt('Creating 3dObjects and Lights', 'BBP_OT_export_virtools/execute')
progress.enter_substeps(len(export_objects), tr_text)
# iterate exported object list
for obj3d in export_objects:
# only accept mesh object and light object
# only accept mesh-like object, camera and light object
# all of other objects will be discard.
match(obj3d.type):
case 'MESH':
# mesh object
if obj3d not in obj3d_cret_set:
# add into set
obj3d_cret_set.add(obj3d)
# create virtools instance
vtobj3d: bmap.BM3dObject = writer.create_3dobject()
# add into result list
obj3d_crets.append((obj3d, vtobj3d))
case 'LIGHT':
# light object
if obj3d not in light_cret_set:
# add into set
light_cret_set.add(obj3d)
# create virtools instance
vtlight: bmap.BMTargetLight = writer.create_target_light()
# add into result list
light_crets.append((obj3d, typing.cast(bpy.types.Light, obj3d.data), vtlight))
if UTIL_blender_mesh.TemporaryMesh.has_geometry(obj3d):
# mesh-like object
if obj3d not in obj3d_cret_set:
# add into set
obj3d_cret_set.add(obj3d)
# create virtools instance
vtobj3d: bmap.BM3dObject = writer.create_3dobject()
# add into result list
obj3d_crets.append((obj3d, vtobj3d))
else:
match(obj3d.type):
case 'CAMERA':
# camera object
if obj3d not in camera_cret_set:
# add into set
camera_cret_set.add(obj3d)
# create virtools instance
vtcamera: bmap.BMTargetCamera = writer.create_target_camera()
# add into result list
camera_crets.append((obj3d, typing.cast(bpy.types.Camera, obj3d.data), vtcamera))
case 'LIGHT':
# light object
if obj3d not in light_cret_set:
# add into set
light_cret_set.add(obj3d)
# create virtools instance
vtlight: bmap.BMTargetLight = writer.create_target_light()
# add into result list
light_crets.append((obj3d, typing.cast(bpy.types.Light, obj3d.data), vtlight))
# step progress no matter whether create new one
progress.step()
# leave progress and return
progress.leave_substeps()
return (tuple(obj3d_crets), tuple(light_crets))
return _PreparedCrets(tuple(obj3d_crets), tuple(light_crets), tuple(camera_crets))
def _export_virtools_groups(
writer: bmap.BMFileWriter,
progress: ProgressReport,
successive_sector: bool,
successive_sector_count: int,
obj3d_crets: tuple[_TObj3dPair, ...]
obj3d_crets: tuple[_Object3dPair, ...]
) -> None:
# create virtools group
group_cret_map: dict[str, bmap.BMGroup] = {}
@@ -232,7 +261,7 @@ def _export_virtools_groups(
def _export_virtools_light(
writer: bmap.BMFileWriter,
progress: ProgressReport,
light_crets: tuple[_TLightPair, ...]
light_crets: tuple[_LightPair, ...]
) -> None:
# start saving
tr_text: str = bpy.app.translations.pgettext_rpt('Saving Lights', 'BBP_OT_export_virtools/execute')
@@ -274,13 +303,56 @@ def _export_virtools_light(
# leave progress and return
progress.leave_substeps()
def _export_virtools_camera(
writer: bmap.BMFileWriter,
progress: ProgressReport,
camera_crets: tuple[_CameraPair, ...]
) -> None:
# start saving
tr_text: str = bpy.app.translations.pgettext_rpt('Saving Cameras', 'BBP_OT_export_virtools/execute')
progress.enter_substeps(len(camera_crets), tr_text)
for obj3d, camera, vtcamera in camera_crets:
# set name
vtcamera.set_name(obj3d.name)
# setup 3d entity parts
# set world matrix
vtmat: UTIL_virtools_types.VxMatrix = UTIL_virtools_types.VxMatrix()
bldmat: mathutils.Matrix = UTIL_virtools_types.bldmatrix_restore_camera_obj(obj3d.matrix_world)
UTIL_virtools_types.vxmatrix_from_blender(vtmat, bldmat)
UTIL_virtools_types.vxmatrix_conv_co(vtmat)
vtcamera.set_world_matrix(vtmat)
# set visibility
vtcamera.set_visibility(not obj3d.hide_get())
# setup camera data
rawcamera: PROP_virtools_camera.RawVirtoolsCamera = PROP_virtools_camera.get_raw_virtools_camera(camera)
vtcamera.set_projection_type(rawcamera.mProjectionType)
vtcamera.set_orthographic_zoom(rawcamera.mOrthographicZoom)
vtcamera.set_front_plane(rawcamera.mFrontPlane)
vtcamera.set_back_plane(rawcamera.mBackPlane)
vtcamera.set_fov(rawcamera.mFov)
(w, h) = rawcamera.mAspectRatio
vtcamera.set_aspect_ratio(w, h)
# step
progress.step()
# leave progress and return
progress.leave_substeps()
def _export_virtools_3dobjects(
writer: bmap.BMFileWriter,
progress: ProgressReport,
obj3d_crets: tuple[_TObj3dPair, ...]
) -> tuple[_TMeshPair, ...]:
obj3d_crets: tuple[_Object3dPair, ...]
) -> tuple[_MeshPair, ...]:
# create virtools mesh
mesh_crets: list[_TMeshPair] = []
mesh_crets: list[_MeshPair] = []
mesh_cret_map: dict[bpy.types.Mesh, bmap.BMMesh] = {}
# start saving
tr_text: str = bpy.app.translations.pgettext_rpt('Saving 3dObjects', 'BBP_OT_export_virtools/execute')
@@ -324,10 +396,10 @@ def _export_virtools_3dobjects(
def _export_virtools_meshes(
writer: bmap.BMFileWriter,
progress: ProgressReport,
mesh_crets: tuple[_TMeshPair, ...]
) -> tuple[_TMaterialPair, ...]:
mesh_crets: tuple[_MeshPair, ...]
) -> tuple[_MaterialPair, ...]:
# create virtools mesh
material_crets: list[_TMaterialPair] = []
material_crets: list[_MaterialPair] = []
material_cret_map: dict[bpy.types.Material, bmap.BMMaterial] = {}
# start saving
tr_text: str = bpy.app.translations.pgettext_rpt('Saving Meshes', 'BBP_OT_export_virtools/execute')
@@ -439,10 +511,10 @@ def _export_virtools_meshes(
def _export_virtools_materials(
writer: bmap.BMFileWriter,
progress: ProgressReport,
material_crets: tuple[_TMaterialPair, ...]
) -> tuple[_TTexturePair, ...]:
material_crets: tuple[_MaterialPair, ...]
) -> tuple[_TexturePair, ...]:
# create virtools mesh
texture_crets: list[_TTexturePair] = []
texture_crets: list[_TexturePair] = []
texture_cret_map: dict[bpy.types.Image, bmap.BMTexture] = {}
# start saving
tr_text: str = bpy.app.translations.pgettext_rpt('Saving Materials', 'BBP_OT_export_virtools/execute')
@@ -508,7 +580,7 @@ def _export_virtools_textures(
writer: bmap.BMFileWriter,
progress: ProgressReport,
vt_temp_folder: str,
texture_crets: tuple[_TTexturePair, ...]
texture_crets: tuple[_TexturePair, ...]
) -> None:
# start saving
tr_text: str = bpy.app.translations.pgettext_rpt('Saving Textures', 'BBP_OT_export_virtools/execute')

View File

@@ -3,8 +3,8 @@ from bpy_extras.wm_utils.progress_report import ProgressReport
import tempfile, os, typing
from . import PROP_preferences, UTIL_ioport_shared, UTIL_naming_convention
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, PROP_virtools_light, PROP_ballance_map_info
from .PyBMap import bmap_wrapper as bmap
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_virtools_light, PROP_virtools_camera, 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, UTIL_ioport_shared.BallanceParams):
"""Import Virtools File"""
@@ -80,8 +80,9 @@ def _import_virtools(file_name_: str, encodings_: tuple[str, ...], resolver: UTI
# import 3dobjects
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object] = _import_virtools_3dobjects(
reader, progress, resolver, mesh_cret_map)
# import light
# import light and camera
_import_virtools_lights(reader, progress, resolver)
_import_virtools_cameras(reader, progress, resolver)
# import groups
_import_virtools_groups(reader, progress, obj3d_cret_map)
@@ -423,6 +424,53 @@ def _import_virtools_lights(
# leave progress
progress.leave_substeps()
def _import_virtools_cameras(
reader: bmap.BMFileReader,
progress: ProgressReport,
resolver: UTIL_ioport_shared.ConflictResolver
) -> None:
# prepare progress
tr_text: str = bpy.app.translations.pgettext_rpt('Loading Cameras', 'BBP_OT_import_virtools/execute')
progress.enter_substeps(reader.get_target_camera_count(), tr_text)
# same creation notes like light
for vtcamera in reader.get_target_cameras():
# create camera data block and 3d object together
(camera_3dobj, camera, init_camera) = resolver.create_camera(
UTIL_virtools_types.virtools_name_regulator(vtcamera.get_name())
)
if init_camera:
# setup camera data block
rawcamera: PROP_virtools_camera.RawVirtoolsCamera = PROP_virtools_camera.RawVirtoolsCamera()
rawcamera.mProjectionType = vtcamera.get_projection_type()
rawcamera.mOrthographicZoom = vtcamera.get_orthographic_zoom()
rawcamera.mFrontPlane = vtcamera.get_front_plane()
rawcamera.mBackPlane = vtcamera.get_back_plane()
rawcamera.mFov = vtcamera.get_fov()
rawcamera.mAspectRatio = vtcamera.get_aspect_ratio()
PROP_virtools_camera.set_raw_virtools_camera(camera, rawcamera)
PROP_virtools_camera.apply_to_blender_camera(camera)
# setup camera associated 3d object
# add into scene
UTIL_functions.add_into_scene(camera_3dobj)
# set world matrix
vtmat: UTIL_virtools_types.VxMatrix = vtcamera.get_world_matrix()
UTIL_virtools_types.vxmatrix_conv_co(vtmat)
bldmat: mathutils.Matrix = UTIL_virtools_types.vxmatrix_to_blender(vtmat)
camera_3dobj.matrix_world = UTIL_virtools_types.bldmatrix_patch_camera_obj(bldmat)
# set visibility
camera_3dobj.hide_set(not vtcamera.get_visibility())
# leave progress
progress.leave_substeps()
def _import_virtools_groups(
reader: bmap.BMFileReader,
progress: ProgressReport,

View File

@@ -1,34 +1,15 @@
import bpy, mathutils
import typing, enum, math
from . import UTIL_functions
from . import UTIL_functions, PROP_virtools_camera
# TODO:
# This file should have fully refactor after we finish Virtools Camera import and export,
# because this module is highly rely on it. Current implementation is a compromise.
# There is a list of things to be done:
# - Remove BBP_OT_game_resolution operator, because Virtools Camera will have similar function in panel.
# - Update BBP_OT_game_cameraoperator with Virtools Camera.
#region Game Resolution
#region Enum Defines
class ResolutionKind(enum.IntEnum):
Normal = enum.auto()
Extended = enum.auto()
Widescreen = enum.auto()
Panoramic = enum.auto()
def to_resolution(self) -> tuple[int, int]:
match self:
case ResolutionKind.Normal: return (1024, 768)
case ResolutionKind.Extended: return (1280, 720)
case ResolutionKind.Widescreen: return (1400, 600)
case ResolutionKind.Panoramic: return (2000, 700)
WideScreen = enum.auto()
_g_ResolutionKindDesc: dict[ResolutionKind, tuple[str, str]] = {
ResolutionKind.Normal: ("Normal", "Aspect ratio: 4:3."),
ResolutionKind.Extended: ("Extended", "Aspect ratio: 16:9."),
ResolutionKind.Widescreen: ("Widescreen", "Aspect ratio: 7:3."),
ResolutionKind.Panoramic: ("Panoramic", "Aspect ratio: 20:7."),
ResolutionKind.Normal: ("Normal", "Vanilla Ballance Resolution"),
ResolutionKind.WideScreen: ("Wide Screen", "Ballance Resolution with Wide Screen Fix"),
}
_g_EnumHelper_ResolutionKind = UTIL_functions.EnumPropHelper(
ResolutionKind,
@@ -39,45 +20,6 @@ _g_EnumHelper_ResolutionKind = UTIL_functions.EnumPropHelper(
lambda _: ""
)
class BBP_OT_game_resolution(bpy.types.Operator):
"""Set Blender render resolution to Ballance game"""
bl_idname = "bbp.game_resolution"
bl_label = "Game Resolution"
bl_options = {'REGISTER', 'UNDO'}
bl_translation_context = 'BBP_OT_game_resolution'
resolution_kind: bpy.props.EnumProperty(
name = "Resolution Kind",
description = "The type of preset resolution.",
items = _g_EnumHelper_ResolutionKind.generate_items(),
default = _g_EnumHelper_ResolutionKind.to_selection(ResolutionKind.Normal),
translation_context = 'BBP_OT_game_resolution/property'
) # type: ignore
def invoke(self, context, event):
return self.execute(context)
def draw(self, context):
layout = self.layout
layout.use_property_split = True
layout.prop(self, 'resolution_kind')
def execute(self, context):
# fetch resolution
resolution_kind = _g_EnumHelper_ResolutionKind.get_selection(self.resolution_kind)
resolution = resolution_kind.to_resolution()
# setup resolution
render_settings = bpy.context.scene.render
render_settings.resolution_x = resolution[0]
render_settings.resolution_y = resolution[1]
return {'FINISHED'}
#endregion
#region Game Camera
#region Enum Defines
class TargetKind(enum.IntEnum):
Cursor = enum.auto()
ActiveObject = enum.auto()
@@ -191,6 +133,9 @@ class BBP_OT_game_camera(bpy.types.Operator):
) # type: ignore
rotation_kind: bpy.props.EnumProperty(
# YYC MAKR:
# This property is not shown on UI layout,
# but it should be translated because it is not PURE assistant property.
name = "Rotation Angle Kind",
description = "",
items = _g_EnumHelper_RotationKind.generate_items(),
@@ -198,9 +143,9 @@ class BBP_OT_game_camera(bpy.types.Operator):
translation_context = 'BBP_OT_game_camera/property'
) # type: ignore
preset_rotation_angle: bpy.props.EnumProperty(
# I18N: Property not showen should not have name and desc.
# name = "Preset Rotation Angle",
# description = "",
name = "Preset Rotation Angle",
description = "",
translation_context = 'BBP_OT_game_camera/property',
options = {'HIDDEN'},
items = _g_EnumHelper_RotationAngle.generate_items(),
default = _g_EnumHelper_RotationAngle.to_selection(RotationAngle.Deg0),
@@ -278,6 +223,21 @@ class BBP_OT_game_camera(bpy.types.Operator):
translation_context = 'BBP_OT_game_camera/property'
) # type: ignore
modify_resolution: bpy.props.BoolProperty(
name = 'Modify Resolution',
description = 'Whether modify the resolution of camera.',
default = False,
translation_context = 'BBP_OT_game_camera/property'
) # type: ignore
resolution_kind: bpy.props.EnumProperty(
name = "Resolution Kind",
description = "The type of preset resolution.",
items = _g_EnumHelper_ResolutionKind.generate_items(),
default = _g_EnumHelper_ResolutionKind.to_selection(ResolutionKind.Normal),
translation_context = 'BBP_OT_game_camera/property'
) # type: ignore
@classmethod
def poll(cls, context):
# find camera object
@@ -330,6 +290,12 @@ class BBP_OT_game_camera(bpy.types.Operator):
layout.label(text='Perspective', text_ctxt='BBP_OT_game_camera/draw')
layout.row().prop(self, 'perspective_kind', expand=True)
# Show resolution kind
layout.separator()
layout.prop(self, 'modify_resolution', text='Resolution', text_ctxt='BBP_OT_game_camera/draw')
if self.modify_resolution:
layout.row().prop(self, 'resolution_kind', expand=True)
def execute(self, context):
# fetch angle
angle: float
@@ -344,11 +310,12 @@ class BBP_OT_game_camera(bpy.types.Operator):
camera_obj = typing.cast(bpy.types.Object, _find_camera_obj())
target_kind = _g_EnumHelper_TargetKind.get_selection(self.target_kind)
perspective_kind = _g_EnumHelper_PerspectiveKind.get_selection(self.perspective_kind)
resolution_kind = _g_EnumHelper_ResolutionKind.get_selection(self.resolution_kind)
# setup its transform and properties
glob_trans = _fetch_glob_translation(camera_obj, target_kind)
_setup_camera_transform(camera_obj, angle, perspective_kind, glob_trans)
_setup_camera_properties(camera_obj)
_setup_camera_properties(camera_obj, resolution_kind)
# return
return {'FINISHED'}
@@ -448,24 +415,39 @@ def _setup_camera_transform(camobj: bpy.types.Object, angle: float, perspective:
glob_trans_mat = mathutils.Matrix.Translation(glob_trans)
camobj.matrix_world = glob_trans_mat @ trans_mat @ rot_mat
def _setup_camera_properties(camobj: bpy.types.Object) -> None:
# fetch camera
def _setup_camera_properties(camobj: bpy.types.Object, resolution_kind: ResolutionKind | None) -> None:
# fetch camera and its raw data
camera = typing.cast(bpy.types.Camera, camobj.data)
rawdata = PROP_virtools_camera.get_raw_virtools_camera(camera)
# set clipping
camera.clip_start = 4
camera.clip_end = 1200
# set FOV
camera.lens_unit = 'FOV'
camera.angle = math.radians(58)
#endregion
rawdata.mFrontPlane = 4
rawdata.mBackPlane = 1200
# set FOV and aspect ratio according to presented resolution kind
if resolution_kind is not None:
match resolution_kind:
case ResolutionKind.Normal:
rawdata.mFov = math.radians(58)
rawdata.mAspectRatio = (4, 3)
case ResolutionKind.WideScreen:
# prepare input arguments
aspect_ratio = (16, 9)
fov = math.radians(58)
# FOV correction reference:
# https://github.com/doyaGu/BallanceModLoaderPlus/blob/c4ab4386fd834af69a960c156fca97237b2fd4c5/src/RenderHook.cpp#L46
aspect = aspect_ratio[0] / aspect_ratio[1]
rawdata.mFov = math.atan2(math.tan(fov * 0.5) * 0.75 * aspect, 1.0) * 2.0
rawdata.mAspectRatio = aspect_ratio
# rewrite it back
PROP_virtools_camera.set_raw_virtools_camera(camera, rawdata)
# and apply it into camera and blender scene
PROP_virtools_camera.apply_to_blender_camera(camera)
PROP_virtools_camera.apply_to_blender_scene_resolution(camera)
def register() -> None:
bpy.utils.register_class(BBP_OT_game_resolution)
bpy.utils.register_class(BBP_OT_game_camera)
def unregister() -> None:
bpy.utils.unregister_class(BBP_OT_game_camera)
bpy.utils.unregister_class(BBP_OT_game_resolution)

View File

@@ -1,14 +1,12 @@
import bpy
import typing
from dataclasses import dataclass
from dataclasses import field as datafield
from . import UTIL_functions
@dataclass
class RawBallanceMapInfo():
cSectorCount: typing.ClassVar[int] = 1
mSectorCount: int
def __init__(self, **kwargs):
self.mSectorCount = kwargs.get("mSectorCount", RawBallanceMapInfo.cSectorCount)
mSectorCount: int = datafield(default=1)
def regulate(self):
self.mSectorCount = UTIL_functions.clamp_int(self.mSectorCount, 1, 999)

View File

@@ -1,21 +1,19 @@
import bpy
import os, typing
from dataclasses import dataclass
from dataclasses import field as datafield
from . import UTIL_naming_convention
@dataclass
class RawPreferences():
cBallanceTextureFolder: typing.ClassVar[str] = ""
cNoComponentCollection: typing.ClassVar[str] = ""
mBallanceTextureFolder: str
mNoComponentCollection: str
def __init__(self, **kwargs):
self.mBallanceTextureFolder = kwargs.get("mBallanceTextureFolder", "")
self.mNoComponentCollection = kwargs.get("mNoComponentCollection", "")
mBallanceTextureFolder: str = datafield(default="")
mNoComponentCollection: str = datafield(default="")
def has_valid_blc_tex_folder(self) -> bool:
return os.path.isdir(self.mBallanceTextureFolder)
DEFAULT_RAW_PREFERENCES = RawPreferences()
class BBPPreferences(bpy.types.AddonPreferences):
bl_idname = __package__
@@ -23,14 +21,14 @@ class BBPPreferences(bpy.types.AddonPreferences):
name = "Ballance Texture Folder",
description = "The path to folder which will be used by this plugin to get external Ballance texture.",
subtype = 'DIR_PATH',
default = RawPreferences.cBallanceTextureFolder,
default = DEFAULT_RAW_PREFERENCES.mBallanceTextureFolder,
translation_context = 'BBPPreferences/property'
) # type: ignore
no_component_collection: bpy.props.StringProperty(
name = "No Component Collection",
description = "When importing, it is the name of collection where objects store will not be saved as component. When exporting, all forced no component objects will be stored in this name represented collection",
default = RawPreferences.cNoComponentCollection,
default = DEFAULT_RAW_PREFERENCES.mNoComponentCollection,
translation_context = 'BBPPreferences/property'
) # type: ignore

View File

@@ -202,7 +202,7 @@ class PropsVisitor():
"""
encodings = get_ioport_encodings(self.__mAssocScene)
encodings.clear()
for default_enc in UTIL_virtools_types.g_PyBMapDefaultEncodings:
for default_enc in UTIL_virtools_types.g_PybmapDefaultEncodings:
item = encodings.add()
item.encoding = default_enc
def draw_ioport_encodings(self, layout: bpy.types.UILayout) -> None:

View File

@@ -0,0 +1,404 @@
import bpy
import typing, math, enum
from dataclasses import dataclass
from dataclasses import field as datafield
from . import UTIL_functions, UTIL_virtools_types
@dataclass
class RawVirtoolsCamera():
mProjectionType: UTIL_virtools_types.CK_CAMERA_PROJECTION = datafield(default=UTIL_virtools_types.CK_CAMERA_PROJECTION.CK_PERSPECTIVEPROJECTION)
mOrthographicZoom: float = datafield(default=1.0)
mFrontPlane: float = datafield(default=1.0)
mBackPlane: float = datafield(default=4000.0)
mFov: float = datafield(default=0.5)
mAspectRatio: tuple[int, int] = datafield(default_factory=lambda: (4, 3))
def regulate(self) -> None:
# everything should be positive
self.mOrthographicZoom = max(0.0, self.mOrthographicZoom)
self.mFrontPlane = max(0.0, self.mFrontPlane)
self.mBackPlane = max(0.0, self.mBackPlane)
self.mFov = max(0.0, self.mFov)
# aspect ratio should be positive and at least 1
(w, h) = self.mAspectRatio
w = max(1, w)
h = max(1, h)
self.mAspectRatio = (w, h)
DEFAULT_RAW_VIRTOOLS_CAMERA = RawVirtoolsCamera()
#region Blender Enum Prop Helper
_g_Helper_CK_CAMERA_PROJECTION = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_CAMERA_PROJECTION)
#endregion
class BBP_PG_virtools_camera(bpy.types.PropertyGroup):
projection_type: bpy.props.EnumProperty(
name = "Type",
description = "The type of this camera.",
items = _g_Helper_CK_CAMERA_PROJECTION.generate_items(),
default = _g_Helper_CK_CAMERA_PROJECTION.to_selection(DEFAULT_RAW_VIRTOOLS_CAMERA.mProjectionType),
translation_context = 'BBP_PG_virtools_camera/property'
) # type: ignore
orthographic_zoom: bpy.props.FloatProperty(
name = "Orthographic Zoom",
description = "Defines the orthographic zoom.",
min = 0.0,
soft_min = 0.0,
soft_max = 0.5,
step = 5,
default = DEFAULT_RAW_VIRTOOLS_CAMERA.mOrthographicZoom,
translation_context = 'BBP_PG_virtools_camera/property'
) # type: ignore
front_plane: bpy.props.FloatProperty(
name = "Front Plane",
description = "Defines the front plane.",
min = 0.0,
soft_min = 0.0,
soft_max = 5000.0,
step = 100,
default = DEFAULT_RAW_VIRTOOLS_CAMERA.mFrontPlane,
translation_context = 'BBP_PG_virtools_camera/property'
) # type: ignore
back_plane: bpy.props.FloatProperty(
name = "Back Plane",
description = "Defines the back plane.",
min = 0.0,
soft_min = 0.0,
soft_max = 5000.0,
step = 100,
default = DEFAULT_RAW_VIRTOOLS_CAMERA.mBackPlane,
translation_context = 'BBP_PG_virtools_camera/property'
) # type: ignore
fov: bpy.props.FloatProperty(
name = "Field of View",
description = "Defines the field of view.",
subtype = 'ANGLE',
min = 0.0,
max = math.radians(180.0),
step = 100,
precision = 100,
default = DEFAULT_RAW_VIRTOOLS_CAMERA.mFov,
translation_context = 'BBP_PG_virtools_camera/property'
) # type: ignore
aspect_ratio_w: bpy.props.IntProperty(
name = "Aspect Ratio Width",
description = "Defines the width of aspect ratio.",
min = 1,
soft_min = 1,
soft_max = 40,
step = 1,
default = DEFAULT_RAW_VIRTOOLS_CAMERA.mAspectRatio[0],
translation_context = 'BBP_PG_virtools_camera/property'
) # type: ignore
aspect_ratio_h: bpy.props.IntProperty(
name = "Aspect Ratio Height",
description = "Defines the height of aspect ratio.",
min = 1,
soft_min = 1,
soft_max = 40,
step = 1,
default = DEFAULT_RAW_VIRTOOLS_CAMERA.mAspectRatio[1],
translation_context = 'BBP_PG_virtools_camera/property'
) # type: ignore
#region Getter Setter and Applyer
def get_virtools_camera(cam: bpy.types.Camera) -> BBP_PG_virtools_camera:
return cam.virtools_camera
def get_raw_virtools_camera(cam: bpy.types.Camera) -> RawVirtoolsCamera:
props: BBP_PG_virtools_camera = get_virtools_camera(cam)
rawdata: RawVirtoolsCamera = RawVirtoolsCamera()
rawdata.mProjectionType = _g_Helper_CK_CAMERA_PROJECTION.get_selection(props.projection_type)
rawdata.mOrthographicZoom = props.orthographic_zoom
rawdata.mFrontPlane = props.front_plane
rawdata.mBackPlane = props.back_plane
rawdata.mFov = props.fov
rawdata.mAspectRatio = (props.aspect_ratio_w, props.aspect_ratio_h)
rawdata.regulate()
return rawdata
def set_raw_virtools_camera(cam: bpy.types.Camera, rawdata: RawVirtoolsCamera) -> None:
props: BBP_PG_virtools_camera = get_virtools_camera(cam)
props.projection_type = _g_Helper_CK_CAMERA_PROJECTION.to_selection(rawdata.mProjectionType)
props.orthographic_zoom = rawdata.mOrthographicZoom
props.front_plane = rawdata.mFrontPlane
props.back_plane = rawdata.mBackPlane
props.fov = rawdata.mFov
(props.aspect_ratio_w, props.aspect_ratio_h) = rawdata.mAspectRatio
def apply_to_blender_camera(cam: bpy.types.Camera) -> None:
# get raw data first
rawdata: RawVirtoolsCamera = get_raw_virtools_camera(cam)
# set camera type
match(rawdata.mProjectionType):
case UTIL_virtools_types.CK_CAMERA_PROJECTION.CK_PERSPECTIVEPROJECTION:
cam.type = 'PERSP'
case UTIL_virtools_types.CK_CAMERA_PROJECTION.CK_ORTHOGRAPHICPROJECTION:
cam.type = 'ORTHO'
# set orthographic zoom
cam.ortho_scale = rawdata.mOrthographicZoom
# front and back plane
cam.clip_start = rawdata.mFrontPlane
cam.clip_end = rawdata.mBackPlane
# fov
cam.lens_unit = 'FOV'
cam.angle = rawdata.mFov
def apply_to_blender_scene_resolution(cam: bpy.types.Camera) -> None:
# get raw data first
rawdata: RawVirtoolsCamera = get_raw_virtools_camera(cam)
# fetch width and height
(w, h) = rawdata.mAspectRatio
# compute a proper resolution from this aspect ratio
# calculate their lcm first
hw_lcm = math.lcm(w, h)
# get the first number which is greater than 1000 (1000 is a proper resolution size)
# and can be integrally divided by this lcm.
HW_MIN: int = 1000
min_edge = ((HW_MIN // hw_lcm) + 1) * hw_lcm
# calculate the final resolution
if w < h:
# width is shorter than height, set width as min edge
width = min_edge
height = width // w * h
else:
# opposite case
height = min_edge
width = height // h * w
# setup resolution
render_settings = bpy.context.scene.render
render_settings.resolution_x = width
render_settings.resolution_y = height
#endregion
#region Aspect Ratio Preset
class AspectRatioPresetType(enum.IntEnum):
Normal = enum.auto()
Extended = enum.auto()
Widescreen = enum.auto()
Panoramic = enum.auto()
def to_aspect_ratio(self) -> tuple[int, int]:
match self:
case AspectRatioPresetType.Normal: return (4, 3)
case AspectRatioPresetType.Extended: return (16, 9)
case AspectRatioPresetType.Widescreen: return (7, 3)
case AspectRatioPresetType.Panoramic: return (20, 7)
_g_AspectRatioPresetTypeDesc: dict[AspectRatioPresetType, tuple[str, str]] = {
AspectRatioPresetType.Normal: ("Normal", "Aspect ratio: 4:3."),
AspectRatioPresetType.Extended: ("Extended", "Aspect ratio: 16:9."),
AspectRatioPresetType.Widescreen: ("Widescreen", "Aspect ratio: 7:3."),
AspectRatioPresetType.Panoramic: ("Panoramic", "Aspect ratio: 20:7."),
}
_g_Helper_AspectRatioPresetType = UTIL_functions.EnumPropHelper(
AspectRatioPresetType,
lambda x: str(x.value),
lambda x: AspectRatioPresetType(int(x)),
lambda x: _g_AspectRatioPresetTypeDesc[x][0],
lambda x: _g_AspectRatioPresetTypeDesc[x][1],
lambda _: ""
)
def preset_virtools_camera_aspect_ratio(cam: bpy.types.Camera, preset_type: AspectRatioPresetType) -> None:
# get raw data from it
rawdata = get_raw_virtools_camera(cam)
# modify its aspect ratio
rawdata.mAspectRatio = preset_type.to_aspect_ratio()
# rewrite it.
set_raw_virtools_camera(cam, rawdata)
#endregion
#region Operators
class BBP_OT_apply_virtools_camera(bpy.types.Operator):
"""Apply Virtools Camera to Blender Camera except Resolution."""
bl_idname = "bbp.apply_virtools_camera"
bl_label = "Apply to Blender Camera"
bl_options = {'UNDO'}
bl_translation_context = 'BBP_OT_apply_virtools_camera'
@classmethod
def poll(cls, context):
return context.camera is not None
def execute(self, context):
cam: bpy.types.Camera = context.camera
apply_to_blender_camera(cam)
return {'FINISHED'}
class BBP_OT_apply_virtools_camera_resolution(bpy.types.Operator):
"""Apply Virtools Camera Resolution to Blender Scene."""
bl_idname = "bbp.apply_virtools_camera_resolution"
bl_label = "Apply to Blender Scene Resolution"
bl_options = {'UNDO'}
bl_translation_context = 'BBP_OT_apply_virtools_camera_resolution'
@classmethod
def poll(cls, context):
return context.camera is not None
def execute(self, context):
cam: bpy.types.Camera = context.camera
apply_to_blender_scene_resolution(cam)
return {'FINISHED'}
class BBP_OT_preset_virtools_camera_aspect_ratio(bpy.types.Operator):
"""Preset Virtools Camera Aspect Ratio with Virtools Presets."""
bl_idname = "bbp.preset_virtools_camera_aspect_ratio"
bl_label = "Preset Virtools Camera Aspect Ratio"
bl_options = {'UNDO'}
bl_translation_context = 'BBP_OT_preset_virtools_camera_aspect_ratio'
preset_type: bpy.props.EnumProperty(
name = "Preset",
description = "The preset which you want to apply.",
items = _g_Helper_AspectRatioPresetType.generate_items(),
translation_context = 'BBP_OT_preset_virtools_camera_aspect_ratio/property'
) # type: ignore
@classmethod
def poll(cls, context):
return context.camera is not None
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self)
def draw(self, context):
self.layout.prop(self, "preset_type")
def execute(self, context):
# get essential value
cam: bpy.types.Camera = context.camera
expected_preset: AspectRatioPresetType = _g_Helper_AspectRatioPresetType.get_selection(self.preset_type)
# apply preset to material
preset_virtools_camera_aspect_ratio(cam, expected_preset)
return {'FINISHED'}
#endregion
class BBP_PT_virtools_camera(bpy.types.Panel):
"""Show Virtools Camera Properties"""
bl_label = "Virtools Camera"
bl_idname = "BBP_PT_virtools_camera"
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_context = "data" # idk why blender use `data` as the camera tab same as mesh.
bl_translation_context = 'BBP_PT_virtools_camera'
@classmethod
def poll(cls, context):
return context.camera is not None
def draw(self, context):
# get layout and target
layout = self.layout
cam: bpy.types.Camera = context.camera
props: BBP_PG_virtools_camera = get_virtools_camera(cam)
rawdata: RawVirtoolsCamera = get_raw_virtools_camera(cam)
# draw operator
row = layout.row()
row.operator(
BBP_OT_apply_virtools_camera.bl_idname, text='Apply', icon='NODETREE',
text_ctxt='BBP_PT_virtools_camera/draw')
row.operator(
BBP_OT_apply_virtools_camera_resolution.bl_idname, text='Apply Resolution', icon='SCENE',
text_ctxt='BBP_PT_virtools_camera/draw')
# draw data
layout.separator()
# show camera type first
layout.prop(props, 'projection_type')
# all camera has front and back plane
layout.label(text='Clipping', text_ctxt='BBP_PT_virtools_camera/draw')
sublayout = layout.column()
sublayout.use_property_split = True
sublayout.prop(props, 'front_plane')
sublayout.prop(props, 'back_plane')
# only perspective camera has fov setting
if rawdata.mProjectionType == UTIL_virtools_types.CK_CAMERA_PROJECTION.CK_PERSPECTIVEPROJECTION:
layout.separator()
layout.label(text='Perspective Parameters', text_ctxt='BBP_PT_virtools_camera/draw')
sublayout = layout.column()
sublayout.use_property_split = True
sublayout.prop(props, 'fov')
# only orthographic camera has orthographic zoom setting
if rawdata.mProjectionType == UTIL_virtools_types.CK_CAMERA_PROJECTION.CK_ORTHOGRAPHICPROJECTION:
layout.separator()
layout.label(text='Orthographic Parameters', text_ctxt='BBP_PT_virtools_camera/draw')
sublayout = layout.column()
sublayout.use_property_split = True
sublayout.prop(props, 'orthographic_zoom')
# aspect ratio
layout.separator()
row = layout.row()
row.label(text='Aspect Ratio', text_ctxt='BBP_PT_virtools_camera/draw')
row.operator(BBP_OT_preset_virtools_camera_aspect_ratio.bl_idname, text='', icon = "PRESET")
sublayout = layout.row()
sublayout.use_property_split = False
sublayout.prop(props, 'aspect_ratio_w', text = '', expand = True)
sublayout.prop(props, 'aspect_ratio_h', text = '', expand = True)
# Register
def register() -> None:
bpy.utils.register_class(BBP_PG_virtools_camera)
bpy.utils.register_class(BBP_OT_apply_virtools_camera)
bpy.utils.register_class(BBP_OT_apply_virtools_camera_resolution)
bpy.utils.register_class(BBP_OT_preset_virtools_camera_aspect_ratio)
bpy.utils.register_class(BBP_PT_virtools_camera)
# add into camera metadata
bpy.types.Camera.virtools_camera = bpy.props.PointerProperty(type = BBP_PG_virtools_camera)
def unregister() -> None:
# remove from metadata
del bpy.types.Camera.virtools_camera
bpy.utils.unregister_class(BBP_PT_virtools_camera)
bpy.utils.unregister_class(BBP_OT_preset_virtools_camera_aspect_ratio)
bpy.utils.unregister_class(BBP_OT_apply_virtools_camera_resolution)
bpy.utils.unregister_class(BBP_OT_apply_virtools_camera)
bpy.utils.unregister_class(BBP_PG_virtools_camera)

View File

@@ -1,6 +1,6 @@
import bpy
import typing, enum
from . import UTIL_functions, UTIL_icons_manager
from . import UTIL_functions, UTIL_icons_manager, UTIL_blender_mesh
#region Virtools Groups Define & Help Class
@@ -387,9 +387,9 @@ class BBP_PT_virtools_groups(bpy.types.Panel):
target = typing.cast(bpy.types.Object, context.active_object)
# notify on non-mesh object
if target.type != 'MESH':
if not UTIL_blender_mesh.TemporaryMesh.has_geometry(target):
layout.label(
text='Virtools Group is invalid on non-mesh object!', icon='ERROR',
text='Virtools Group is invalid on non-mesh-like object!', icon='ERROR',
text_ctxt='BBP_PT_virtools_groups/draw')
# draw main body

View File

@@ -1,55 +1,26 @@
import bpy, mathutils
from bpy.types import Context
import bpy
import typing, math
from dataclasses import dataclass
from dataclasses import field as datafield
from . import UTIL_functions, UTIL_virtools_types
# Raw Data
@dataclass
class RawVirtoolsLight():
# Class member
mType: UTIL_virtools_types.VXLIGHT_TYPE = datafield(default=UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTPOINT)
mColor: UTIL_virtools_types.VxColor = datafield(default_factory=lambda: UTIL_virtools_types.VxColor(1.0, 1.0, 1.0, 1.0))
mType: UTIL_virtools_types.VXLIGHT_TYPE
mColor: UTIL_virtools_types.VxColor
mConstantAttenuation: float = datafield(default=1.0)
mLinearAttenuation: float = datafield(default=0.0)
mQuadraticAttenuation: float = datafield(default=0.0)
mConstantAttenuation: float
mLinearAttenuation: float
mQuadraticAttenuation: float
mRange: float = datafield(default=100.0)
mRange: float
mHotSpot: float
mFalloff: float
mFalloffShape: float
# Class member default value
cDefaultType: typing.ClassVar[UTIL_virtools_types.VXLIGHT_TYPE] = UTIL_virtools_types.VXLIGHT_TYPE.VX_LIGHTPOINT
cDefaultColor: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(1.0, 1.0, 1.0, 1.0)
cDefaultConstantAttenuation: typing.ClassVar[float] = 1.0
cDefaultLinearAttenuation: typing.ClassVar[float] = 0.0
cDefaultQuadraticAttenuation: typing.ClassVar[float] = 0.0
cDefaultRange: typing.ClassVar[float] = 100.0
cDefaultHotSpot: typing.ClassVar[float] = math.radians(40)
cDefaultFalloff: typing.ClassVar[float] = math.radians(45)
cDefaultFalloffShape: typing.ClassVar[float] = 1.0
def __init__(self, **kwargs):
# assign default value for each component
self.mType = kwargs.get('mType', RawVirtoolsLight.cDefaultType)
self.mColor = kwargs.get('mColor', RawVirtoolsLight.cDefaultColor).clone()
self.mConstantAttenuation = kwargs.get('mConstantAttenuation', RawVirtoolsLight.cDefaultConstantAttenuation)
self.mLinearAttenuation = kwargs.get('mLinearAttenuation', RawVirtoolsLight.cDefaultLinearAttenuation)
self.mQuadraticAttenuation = kwargs.get('mQuadraticAttenuation', RawVirtoolsLight.cDefaultQuadraticAttenuation)
self.mRange = kwargs.get('mRange', RawVirtoolsLight.cDefaultRange)
self.mHotSpot = kwargs.get('mHotSpot', RawVirtoolsLight.cDefaultHotSpot)
self.mFalloff = kwargs.get('mFalloff', RawVirtoolsLight.cDefaultFalloff)
self.mFalloffShape = kwargs.get('mFalloffShape', RawVirtoolsLight.cDefaultFalloffShape)
mHotSpot: float = datafield(default=math.radians(40))
mFalloff: float = datafield(default=math.radians(45))
mFalloffShape: float = datafield(default=1.0)
def regulate(self) -> None:
# regulate color and reset its alpha value
@@ -71,6 +42,8 @@ class RawVirtoolsLight():
if self.mFalloff < self.mHotSpot:
self.mFalloff = self.mHotSpot
DEFAULT_RAW_VIRTOOLS_LIGHT = RawVirtoolsLight()
# Blender Property Group
_g_Helper_VXLIGHT_TYPE = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXLIGHT_TYPE)
@@ -80,7 +53,7 @@ class BBP_PG_virtools_light(bpy.types.PropertyGroup):
name = "Type",
description = "The type of this light",
items = _g_Helper_VXLIGHT_TYPE.generate_items(),
default = _g_Helper_VXLIGHT_TYPE.to_selection(RawVirtoolsLight.cDefaultType),
default = _g_Helper_VXLIGHT_TYPE.to_selection(DEFAULT_RAW_VIRTOOLS_LIGHT.mType),
translation_context = 'BBP_PG_virtools_light/property'
) # type: ignore
@@ -91,7 +64,7 @@ class BBP_PG_virtools_light(bpy.types.PropertyGroup):
min = 0.0,
max = 1.0,
size = 3,
default = RawVirtoolsLight.cDefaultColor.to_const_rgb(),
default = DEFAULT_RAW_VIRTOOLS_LIGHT.mColor.to_const_rgb(),
translation_context = 'BBP_PG_virtools_light/property'
) # type: ignore
@@ -101,7 +74,7 @@ class BBP_PG_virtools_light(bpy.types.PropertyGroup):
min = 0.0,
max = 10.0,
step = 10,
default = RawVirtoolsLight.cDefaultConstantAttenuation,
default = DEFAULT_RAW_VIRTOOLS_LIGHT.mConstantAttenuation,
translation_context = 'BBP_PG_virtools_light/property'
) # type: ignore
@@ -111,7 +84,7 @@ class BBP_PG_virtools_light(bpy.types.PropertyGroup):
min = 0.0,
max = 10.0,
step = 10,
default = RawVirtoolsLight.cDefaultLinearAttenuation,
default = DEFAULT_RAW_VIRTOOLS_LIGHT.mLinearAttenuation,
translation_context = 'BBP_PG_virtools_light/property'
) # type: ignore
@@ -121,7 +94,7 @@ class BBP_PG_virtools_light(bpy.types.PropertyGroup):
min = 0.0,
max = 10.0,
step = 10,
default = RawVirtoolsLight.cDefaultQuadraticAttenuation,
default = DEFAULT_RAW_VIRTOOLS_LIGHT.mQuadraticAttenuation,
translation_context = 'BBP_PG_virtools_light/property'
) # type: ignore
@@ -131,7 +104,7 @@ class BBP_PG_virtools_light(bpy.types.PropertyGroup):
min = 0.0,
max = 200.0,
step = 100,
default = RawVirtoolsLight.cDefaultRange,
default = DEFAULT_RAW_VIRTOOLS_LIGHT.mRange,
translation_context = 'BBP_PG_virtools_light/property'
) # type: ignore
@@ -141,7 +114,7 @@ class BBP_PG_virtools_light(bpy.types.PropertyGroup):
min = 0.0,
max = math.radians(180),
subtype = 'ANGLE',
default = RawVirtoolsLight.cDefaultHotSpot,
default = DEFAULT_RAW_VIRTOOLS_LIGHT.mHotSpot,
translation_context = 'BBP_PG_virtools_light/property'
) # type: ignore
@@ -151,7 +124,7 @@ class BBP_PG_virtools_light(bpy.types.PropertyGroup):
min = 0.0,
max = math.radians(180),
subtype = 'ANGLE',
default = RawVirtoolsLight.cDefaultFalloff,
default = DEFAULT_RAW_VIRTOOLS_LIGHT.mFalloff,
translation_context = 'BBP_PG_virtools_light/property'
) # type: ignore
@@ -161,7 +134,7 @@ class BBP_PG_virtools_light(bpy.types.PropertyGroup):
min = 0.0,
max = 10.0,
step = 10,
default = RawVirtoolsLight.cDefaultFalloffShape,
default = DEFAULT_RAW_VIRTOOLS_LIGHT.mFalloffShape,
translation_context = 'BBP_PG_virtools_light/property'
) # type: ignore

View File

@@ -1,98 +1,41 @@
import bpy
import typing, enum, copy, os
from dataclasses import dataclass
from dataclasses import field as datafield
from . import UTIL_virtools_types, UTIL_functions, UTIL_ballance_texture, UTIL_file_browser
from . import PROP_virtools_texture, PROP_preferences
@dataclass
class RawVirtoolsMaterial():
# Instance Member Declarations
mDiffuse: UTIL_virtools_types.VxColor = datafield(default_factory=lambda: UTIL_virtools_types.VxColor(0.7, 0.7, 0.7, 1.0))
mAmbient: UTIL_virtools_types.VxColor = datafield(default_factory=lambda: UTIL_virtools_types.VxColor(0.3, 0.3, 0.3, 1.0))
mSpecular: UTIL_virtools_types.VxColor = datafield(default_factory=lambda: UTIL_virtools_types.VxColor(0.5, 0.5, 0.5, 1.0))
mEmissive: UTIL_virtools_types.VxColor = datafield(default_factory=lambda: UTIL_virtools_types.VxColor(0.0, 0.0, 0.0, 1.0))
mSpecularPower: float = datafield(default=0.0)
mDiffuse: UTIL_virtools_types.VxColor
mAmbient: UTIL_virtools_types.VxColor
mSpecular: UTIL_virtools_types.VxColor
mEmissive: UTIL_virtools_types.VxColor
mSpecularPower: float
mTexture: bpy.types.Image | None = datafield(default=None)
mTextureBorderColor: UTIL_virtools_types.VxColor = datafield(default_factory=lambda: UTIL_virtools_types.VxColor(0.0, 0.0, 0.0, 0.0))
mTexture: bpy.types.Image | None
mTextureBorderColor: UTIL_virtools_types.VxColor
mTextureBlendMode: UTIL_virtools_types.VXTEXTURE_BLENDMODE = datafield(default=UTIL_virtools_types.VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MODULATEALPHA)
mTextureMinMode: UTIL_virtools_types.VXTEXTURE_FILTERMODE = datafield(default=UTIL_virtools_types.VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEAR)
mTextureMagMode: UTIL_virtools_types.VXTEXTURE_FILTERMODE = datafield(default=UTIL_virtools_types.VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEAR)
mTextureAddressMode: UTIL_virtools_types.VXTEXTURE_ADDRESSMODE = datafield(default=UTIL_virtools_types.VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSWRAP)
mTextureBlendMode: UTIL_virtools_types.VXTEXTURE_BLENDMODE
mTextureMinMode: UTIL_virtools_types.VXTEXTURE_FILTERMODE
mTextureMagMode: UTIL_virtools_types.VXTEXTURE_FILTERMODE
mTextureAddressMode: UTIL_virtools_types.VXTEXTURE_ADDRESSMODE
mSourceBlend: UTIL_virtools_types.VXBLEND_MODE = datafield(default=UTIL_virtools_types.VXBLEND_MODE.VXBLEND_ONE)
mDestBlend: UTIL_virtools_types.VXBLEND_MODE = datafield(default=UTIL_virtools_types.VXBLEND_MODE.VXBLEND_ZERO)
mFillMode: UTIL_virtools_types.VXFILL_MODE = datafield(default=UTIL_virtools_types.VXFILL_MODE.VXFILL_SOLID)
mShadeMode: UTIL_virtools_types.VXSHADE_MODE = datafield(default=UTIL_virtools_types.VXSHADE_MODE.VXSHADE_GOURAUD)
mSourceBlend: UTIL_virtools_types.VXBLEND_MODE
mDestBlend: UTIL_virtools_types.VXBLEND_MODE
mFillMode: UTIL_virtools_types.VXFILL_MODE
mShadeMode: UTIL_virtools_types.VXSHADE_MODE
mEnableAlphaTest: bool = datafield(default=False)
mEnableAlphaBlend: bool = datafield(default=False)
mEnablePerspectiveCorrection: bool = datafield(default=True)
mEnableZWrite: bool = datafield(default=True)
mEnableTwoSided: bool = datafield(default=False)
mEnableAlphaTest: bool
mEnableAlphaBlend: bool
mEnablePerspectiveCorrection: bool
mEnableZWrite: bool
mEnableTwoSided: bool
mAlphaRef: int
mAlphaFunc: UTIL_virtools_types.VXCMPFUNC
mZFunc: UTIL_virtools_types.VXCMPFUNC
# Default Value Declarations
cDefaultDiffuse: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(0.7, 0.7, 0.7, 1.0)
cDefaultAmbient: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(0.3, 0.3, 0.3, 1.0)
cDefaultSpecular: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(0.5, 0.5, 0.5, 1.0)
cDefaultEmissive: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(0.0, 0.0, 0.0, 1.0)
cDefaultSpecularPower: typing.ClassVar[float] = 0.0
cDefaultTexture: typing.ClassVar[bpy.types.Image | None] = None
cDefaultTextureBorderColor: typing.ClassVar[UTIL_virtools_types.VxColor] = UTIL_virtools_types.VxColor(0.0, 0.0, 0.0, 0.0)
cDefaultTextureBlendMode: typing.ClassVar[UTIL_virtools_types.VXTEXTURE_BLENDMODE]= UTIL_virtools_types.VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MODULATEALPHA
cDefaultTextureMinMode: typing.ClassVar[UTIL_virtools_types.VXTEXTURE_FILTERMODE] = UTIL_virtools_types.VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEAR
cDefaultTextureMagMode: typing.ClassVar[UTIL_virtools_types.VXTEXTURE_FILTERMODE] = UTIL_virtools_types.VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEAR
cDefaultTextureAddressMode: typing.ClassVar[UTIL_virtools_types.VXTEXTURE_ADDRESSMODE] = UTIL_virtools_types.VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSWRAP
cDefaultSourceBlend: typing.ClassVar[UTIL_virtools_types.VXBLEND_MODE] = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_ONE
cDefaultDestBlend: typing.ClassVar[UTIL_virtools_types.VXBLEND_MODE] = UTIL_virtools_types.VXBLEND_MODE.VXBLEND_ZERO
cDefaultFillMode: typing.ClassVar[UTIL_virtools_types.VXFILL_MODE] = UTIL_virtools_types.VXFILL_MODE.VXFILL_SOLID
cDefaultShadeMode: typing.ClassVar[UTIL_virtools_types.VXSHADE_MODE] = UTIL_virtools_types.VXSHADE_MODE.VXSHADE_GOURAUD
cDefaultEnableAlphaTest: typing.ClassVar[bool] = False
cDefaultEnableAlphaBlend: typing.ClassVar[bool] = False
cDefaultEnablePerspectiveCorrection: typing.ClassVar[bool] = True
cDefaultEnableZWrite: typing.ClassVar[bool] = True
cDefaultEnableTwoSided: typing.ClassVar[bool] = False
cDefaultAlphaRef: typing.ClassVar[int] = 0
cDefaultAlphaFunc: typing.ClassVar[UTIL_virtools_types.VXCMPFUNC] = UTIL_virtools_types.VXCMPFUNC.VXCMP_ALWAYS
cDefaultZFunc: typing.ClassVar[UTIL_virtools_types.VXCMPFUNC] = UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL
def __init__(self, **kwargs):
# assign default value for each component
self.mDiffuse = kwargs.get('mDiffuse', RawVirtoolsMaterial.cDefaultDiffuse).clone()
self.mAmbient = kwargs.get('mAmbient', RawVirtoolsMaterial.cDefaultAmbient).clone()
self.mSpecular = kwargs.get('mSpecular', RawVirtoolsMaterial.cDefaultSpecular).clone()
self.mSpecularPower = kwargs.get('mSpecularPower', RawVirtoolsMaterial.cDefaultSpecularPower)
self.mEmissive = kwargs.get('mEmissive', RawVirtoolsMaterial.cDefaultEmissive).clone()
self.mEnableTwoSided = kwargs.get('mEnableTwoSided', RawVirtoolsMaterial.cDefaultEnableTwoSided)
self.mTexture = kwargs.get('mTexture', RawVirtoolsMaterial.cDefaultTexture)
self.mTextureMinMode = kwargs.get('mTextureMinMode', RawVirtoolsMaterial.cDefaultTextureMinMode)
self.mTextureMagMode = kwargs.get('mTextureMagMode', RawVirtoolsMaterial.cDefaultTextureMagMode)
self.mSourceBlend = kwargs.get('mSourceBlend', RawVirtoolsMaterial.cDefaultSourceBlend)
self.mDestBlend = kwargs.get('mDestBlend', RawVirtoolsMaterial.cDefaultDestBlend)
self.mEnableAlphaBlend = kwargs.get('mEnableAlphaBlend', RawVirtoolsMaterial.cDefaultEnableAlphaBlend)
self.mShadeMode = kwargs.get('mShadeMode', RawVirtoolsMaterial.cDefaultShadeMode)
self.mFillMode = kwargs.get('mFillMode', RawVirtoolsMaterial.cDefaultFillMode)
self.mEnableAlphaTest = kwargs.get('mEnableAlphaTest', RawVirtoolsMaterial.cDefaultEnableAlphaTest)
self.mEnableZWrite = kwargs.get('mEnableZWrite', RawVirtoolsMaterial.cDefaultEnableZWrite)
self.mEnablePerspectiveCorrection = kwargs.get('mEnablePerspectiveCorrection', RawVirtoolsMaterial.cDefaultEnablePerspectiveCorrection)
self.mTextureBlendMode = kwargs.get('mTextureBlendMode', RawVirtoolsMaterial.cDefaultTextureBlendMode)
self.mTextureAddressMode = kwargs.get('mTextureAddressMode', RawVirtoolsMaterial.cDefaultTextureAddressMode)
self.mZFunc = kwargs.get('mZFunc', RawVirtoolsMaterial.cDefaultZFunc)
self.mAlphaFunc = kwargs.get('mAlphaFunc', RawVirtoolsMaterial.cDefaultAlphaFunc)
self.mTextureBorderColor = kwargs.get('mTextureBorderColor', RawVirtoolsMaterial.cDefaultTextureBorderColor).clone()
self.mAlphaRef = kwargs.get('mAlphaRef', RawVirtoolsMaterial.cDefaultAlphaRef)
mAlphaRef: int = datafield(default=0)
mAlphaFunc: UTIL_virtools_types.VXCMPFUNC = datafield(default=UTIL_virtools_types.VXCMPFUNC.VXCMP_ALWAYS)
mZFunc: UTIL_virtools_types.VXCMPFUNC = datafield(default=UTIL_virtools_types.VXCMPFUNC.VXCMP_LESSEQUAL)
def regulate(self) -> None:
# regulate colors
@@ -112,6 +55,8 @@ class RawVirtoolsMaterial():
# specular power
self.mSpecularPower = UTIL_functions.clamp_float(self.mSpecularPower, 0.0, 100.0)
DEFAULT_RAW_VIRTOOLS_MATERIAL = RawVirtoolsMaterial()
#region Blender Enum Prop Helper (Virtools type specified)
_g_Helper_VXTEXTURE_BLENDMODE = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXTEXTURE_BLENDMODE)
@@ -132,7 +77,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
min = 0.0,
max = 1.0,
size = 3,
default = RawVirtoolsMaterial.cDefaultAmbient.to_const_rgb(),
default = DEFAULT_RAW_VIRTOOLS_MATERIAL.mAmbient.to_const_rgb(),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -143,7 +88,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
min = 0.0,
max = 1.0,
size = 4,
default = RawVirtoolsMaterial.cDefaultDiffuse.to_const_rgba(),
default = DEFAULT_RAW_VIRTOOLS_MATERIAL.mDiffuse.to_const_rgba(),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -154,7 +99,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
min = 0.0,
max = 1.0,
size = 3,
default = RawVirtoolsMaterial.cDefaultSpecular.to_const_rgb(),
default = DEFAULT_RAW_VIRTOOLS_MATERIAL.mSpecular.to_const_rgb(),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -165,7 +110,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
min = 0.0,
max = 1.0,
size = 3,
default = RawVirtoolsMaterial.cDefaultEmissive.to_const_rgb(),
default = DEFAULT_RAW_VIRTOOLS_MATERIAL.mEmissive.to_const_rgb(),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -174,7 +119,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
description = "Specular highlight power",
min = 0.0,
max = 100.0,
default = RawVirtoolsMaterial.cDefaultSpecularPower,
default = DEFAULT_RAW_VIRTOOLS_MATERIAL.mSpecularPower,
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -192,7 +137,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
min = 0.0,
max = 1.0,
size = 4,
default = RawVirtoolsMaterial.cDefaultTextureBorderColor.to_const_rgba(),
default = DEFAULT_RAW_VIRTOOLS_MATERIAL.mTextureBorderColor.to_const_rgba(),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -200,7 +145,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
name = "Texture Blend",
description = "Texture blend mode",
items = _g_Helper_VXTEXTURE_BLENDMODE.generate_items(),
default = _g_Helper_VXTEXTURE_BLENDMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureBlendMode),
default = _g_Helper_VXTEXTURE_BLENDMODE.to_selection(DEFAULT_RAW_VIRTOOLS_MATERIAL.mTextureBlendMode),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -208,7 +153,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
name = "Filter Min",
description = "Texture filter mode when the texture is minified",
items = _g_Helper_VXTEXTURE_FILTERMODE.generate_items(),
default = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureMinMode),
default = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(DEFAULT_RAW_VIRTOOLS_MATERIAL.mTextureMinMode),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -216,7 +161,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
name = "Filter Mag",
description = "Texture filter mode when the texture is magnified",
items = _g_Helper_VXTEXTURE_FILTERMODE.generate_items(),
default = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureMagMode),
default = _g_Helper_VXTEXTURE_FILTERMODE.to_selection(DEFAULT_RAW_VIRTOOLS_MATERIAL.mTextureMagMode),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -224,7 +169,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
name = "Address Mode",
description = "The address mode controls how the texture coordinates outside the range 0..1",
items = _g_Helper_VXTEXTURE_ADDRESSMODE.generate_items(),
default = _g_Helper_VXTEXTURE_ADDRESSMODE.to_selection(RawVirtoolsMaterial.cDefaultTextureAddressMode),
default = _g_Helper_VXTEXTURE_ADDRESSMODE.to_selection(DEFAULT_RAW_VIRTOOLS_MATERIAL.mTextureAddressMode),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -232,7 +177,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
name = "Source Blend",
description = "Source blend factor",
items = _g_Helper_VXBLEND_MODE.generate_items(),
default = _g_Helper_VXBLEND_MODE.to_selection(RawVirtoolsMaterial.cDefaultSourceBlend),
default = _g_Helper_VXBLEND_MODE.to_selection(DEFAULT_RAW_VIRTOOLS_MATERIAL.mSourceBlend),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -240,7 +185,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
name = "Destination Blend",
description = "Destination blend factor",
items = _g_Helper_VXBLEND_MODE.generate_items(),
default = _g_Helper_VXBLEND_MODE.to_selection(RawVirtoolsMaterial.cDefaultDestBlend),
default = _g_Helper_VXBLEND_MODE.to_selection(DEFAULT_RAW_VIRTOOLS_MATERIAL.mDestBlend),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -248,7 +193,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
name = "Fill Mode",
description = "Fill mode",
items = _g_Helper_VXFILL_MODE.generate_items(),
default = _g_Helper_VXFILL_MODE.to_selection(RawVirtoolsMaterial.cDefaultFillMode),
default = _g_Helper_VXFILL_MODE.to_selection(DEFAULT_RAW_VIRTOOLS_MATERIAL.mFillMode),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -256,38 +201,38 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
name = "Shade Mode",
description = "Shade mode",
items = _g_Helper_VXSHADE_MODE.generate_items(),
default = _g_Helper_VXSHADE_MODE.to_selection(RawVirtoolsMaterial.cDefaultShadeMode),
default = _g_Helper_VXSHADE_MODE.to_selection(DEFAULT_RAW_VIRTOOLS_MATERIAL.mShadeMode),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
enable_alpha_test: bpy.props.BoolProperty(
name = "Alpha Test",
description = "Whether the alpha test is enabled",
default = RawVirtoolsMaterial.cDefaultEnableAlphaTest,
default = DEFAULT_RAW_VIRTOOLS_MATERIAL.mEnableAlphaTest,
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
enable_alpha_blend: bpy.props.BoolProperty(
name = "Blend",
description = "Whether alpha blending is enabled or not.",
default = RawVirtoolsMaterial.cDefaultEnableAlphaBlend,
default = DEFAULT_RAW_VIRTOOLS_MATERIAL.mEnableAlphaBlend,
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
enable_perspective_correction: bpy.props.BoolProperty(
name = "Perspective Correction",
description = "Whether texture perspective correction is enabled",
default = RawVirtoolsMaterial.cDefaultEnablePerspectiveCorrection,
default = DEFAULT_RAW_VIRTOOLS_MATERIAL.mEnablePerspectiveCorrection,
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
enable_z_write: bpy.props.BoolProperty(
name = "Z-Buffer Write",
description = "Whether writing in ZBuffer is enabled.",
default = RawVirtoolsMaterial.cDefaultEnableZWrite,
default = DEFAULT_RAW_VIRTOOLS_MATERIAL.mEnableZWrite,
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
enable_two_sided: bpy.props.BoolProperty(
name = "Both Sided",
description = "Whether the material is both sided or not",
default = RawVirtoolsMaterial.cDefaultEnableTwoSided,
default = DEFAULT_RAW_VIRTOOLS_MATERIAL.mEnableTwoSided,
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -296,7 +241,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
description = "Alpha referential value",
min = 0,
max = 255,
default = RawVirtoolsMaterial.cDefaultAlphaRef,
default = DEFAULT_RAW_VIRTOOLS_MATERIAL.mAlphaRef,
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -304,7 +249,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
name = "Alpha Test Function",
description = "Alpha comparision function",
items = _g_Helper_VXCMPFUNC.generate_items(),
default = _g_Helper_VXCMPFUNC.to_selection(RawVirtoolsMaterial.cDefaultAlphaFunc),
default = _g_Helper_VXCMPFUNC.to_selection(DEFAULT_RAW_VIRTOOLS_MATERIAL.mAlphaFunc),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore
@@ -312,7 +257,7 @@ class BBP_PG_virtools_material(bpy.types.PropertyGroup):
name = "Z Compare Function",
description = "Z Comparison function",
items = _g_Helper_VXCMPFUNC.generate_items(),
default = _g_Helper_VXCMPFUNC.to_selection(RawVirtoolsMaterial.cDefaultZFunc),
default = _g_Helper_VXCMPFUNC.to_selection(DEFAULT_RAW_VIRTOOLS_MATERIAL.mZFunc),
translation_context = 'BBP_PG_virtools_material/property'
) # type: ignore

View File

@@ -1,18 +1,16 @@
import bpy
import typing, enum
from . import UTIL_functions, UTIL_virtools_types
from dataclasses import dataclass
from dataclasses import field as datafield
from . import UTIL_functions, UTIL_blender_mesh, UTIL_virtools_types
# Raw Data
@dataclass
class RawVirtoolsMesh():
# Instance Member Declarations
mLitMode: UTIL_virtools_types.VXMESH_LITMODE
# Default Value Declarations
cDefaultLitMode: typing.ClassVar[UTIL_virtools_types.VXMESH_LITMODE] = UTIL_virtools_types.VXMESH_LITMODE.VX_LITMESH
def __init__(self, **kwargs):
# assign default value for each component
self.mLitMode = kwargs.get('mLitMode', RawVirtoolsMesh.cDefaultLitMode)
mLitMode: UTIL_virtools_types.VXMESH_LITMODE = datafield(default=UTIL_virtools_types.VXMESH_LITMODE.VX_LITMESH)
DEFAULT_RAW_VIRTOOLS_MESH = RawVirtoolsMesh()
# blender enum prop helper defines
_g_Helper_VXMESH_LITMODE = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VXMESH_LITMODE)
@@ -24,25 +22,27 @@ class BBP_PG_virtools_mesh(bpy.types.PropertyGroup):
name = "Lit Mode",
description = "Lighting mode of the mesh.",
items = _g_Helper_VXMESH_LITMODE.generate_items(),
default = _g_Helper_VXMESH_LITMODE.to_selection(RawVirtoolsMesh.cDefaultLitMode),
default = _g_Helper_VXMESH_LITMODE.to_selection(DEFAULT_RAW_VIRTOOLS_MESH.mLitMode),
translation_context = 'BBP_PG_virtools_mesh/property'
) # type: ignore
# Getter Setter
def get_virtools_mesh(mesh: bpy.types.Mesh) -> BBP_PG_virtools_mesh:
return mesh.virtools_mesh
CanToMesh = bpy.types.Mesh | bpy.types.Curve | bpy.types.SurfaceCurve | bpy.types.TextCurve | bpy.types.MetaBall
def get_raw_virtools_mesh(mesh: bpy.types.Mesh) -> RawVirtoolsMesh:
props: BBP_PG_virtools_mesh = get_virtools_mesh(mesh)
def get_virtools_mesh(meshlike: CanToMesh) -> BBP_PG_virtools_mesh:
return meshlike.virtools_mesh
def get_raw_virtools_mesh(meshlike: CanToMesh) -> RawVirtoolsMesh:
props: BBP_PG_virtools_mesh = get_virtools_mesh(meshlike)
rawdata: RawVirtoolsMesh = RawVirtoolsMesh()
rawdata.mLitMode = _g_Helper_VXMESH_LITMODE.get_selection(props.lit_mode)
return rawdata
def set_raw_virtools_mesh(mesh: bpy.types.Mesh, rawdata: RawVirtoolsMesh) -> None:
props: BBP_PG_virtools_mesh = get_virtools_mesh(mesh)
def set_raw_virtools_mesh(meshlike: CanToMesh, rawdata: RawVirtoolsMesh) -> None:
props: BBP_PG_virtools_mesh = get_virtools_mesh(meshlike)
props.lit_mode = _g_Helper_VXMESH_LITMODE.to_selection(rawdata.mLitMode)
@@ -59,12 +59,22 @@ class BBP_PT_virtools_mesh(bpy.types.Panel):
@classmethod
def poll(cls, context):
return context.mesh is not None
if context.mesh is not None: return True
if context.curve is not None: return True
if context.meta_ball is not None: return True
return False
def draw(self, context):
# get layout and target
# get layout
layout = self.layout
props: BBP_PG_virtools_mesh = get_virtools_mesh(context.mesh)
# get target
datablock: typing.Any
if context.mesh is not None: datablock = context.mesh
elif context.curve is not None: datablock = context.curve
elif context.meta_ball is not None: datablock = context.meta_ball
else: datablock = None
# get mesh properties
props: BBP_PG_virtools_mesh = get_virtools_mesh(datablock)
# draw data
layout.prop(props, 'lit_mode')
@@ -75,11 +85,21 @@ def register() -> None:
bpy.utils.register_class(BBP_PG_virtools_mesh)
bpy.utils.register_class(BBP_PT_virtools_mesh)
# add into mesh metadata
# Add metadata into mesh-like data block.
# according to TemporaryMesh, we need add it into:
# mesh, curve, surface, font, and metaball.
bpy.types.Mesh.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh)
bpy.types.Curve.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh)
bpy.types.SurfaceCurve.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh)
bpy.types.TextCurve.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh)
bpy.types.MetaBall.virtools_mesh = bpy.props.PointerProperty(type = BBP_PG_virtools_mesh)
def unregister() -> None:
# remove from metadata
del bpy.types.MetaBall.virtools_mesh
del bpy.types.TextCurve.virtools_mesh
del bpy.types.SurfaceCurve.virtools_mesh
del bpy.types.Curve.virtools_mesh
del bpy.types.Mesh.virtools_mesh
bpy.utils.unregister_class(BBP_PT_virtools_mesh)

View File

@@ -1,24 +1,17 @@
import bpy
import typing
from dataclasses import dataclass
from dataclasses import field as datafield
from . import UTIL_virtools_types, UTIL_functions
@dataclass
class RawVirtoolsTexture():
# Instance Member Declarations
mSaveOptions: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS
mVideoFormat: UTIL_virtools_types.VX_PIXELFORMAT
mSaveOptions: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS = datafield(default=UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_RAWDATA)
mVideoFormat: UTIL_virtools_types.VX_PIXELFORMAT = datafield(default=UTIL_virtools_types.VX_PIXELFORMAT._16_ARGB1555)
# Default Value Declarations
cDefaultSaveOptions: typing.ClassVar[UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS] = UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_RAWDATA
cDefaultVideoFormat: typing.ClassVar[UTIL_virtools_types.VX_PIXELFORMAT] = UTIL_virtools_types.VX_PIXELFORMAT._16_ARGB1555
DEFAULT_RAW_VIRTOOLS_TEXTURE = RawVirtoolsTexture()
def __init__(self, **kwargs):
# assign default value for each component
self.mSaveOptions = kwargs.get('mSaveOptions', RawVirtoolsTexture.cDefaultSaveOptions)
self.mVideoFormat = kwargs.get('mVideoFormat', RawVirtoolsTexture.cDefaultVideoFormat)
# blender enum prop helper defines
_g_Helper_CK_TEXTURE_SAVEOPTIONS = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS)
_g_Helper_VX_PIXELFORMAT = UTIL_virtools_types.EnumPropHelper(UTIL_virtools_types.VX_PIXELFORMAT)
@@ -29,7 +22,7 @@ class BBP_PG_virtools_texture(bpy.types.PropertyGroup):
name = "Save Options",
description = "When saving a composition textures or sprites can be kept as reference to external files or converted to a given format and saved inside the composition file.",
items = _g_Helper_CK_TEXTURE_SAVEOPTIONS.generate_items(),
default = _g_Helper_CK_TEXTURE_SAVEOPTIONS.to_selection(RawVirtoolsTexture.cDefaultSaveOptions),
default = _g_Helper_CK_TEXTURE_SAVEOPTIONS.to_selection(DEFAULT_RAW_VIRTOOLS_TEXTURE.mSaveOptions),
translation_context = 'BBP_PG_virtools_texture/property'
) # type: ignore
@@ -37,7 +30,7 @@ class BBP_PG_virtools_texture(bpy.types.PropertyGroup):
name = "Video Format",
description = "The desired surface pixel format in video memory.",
items = _g_Helper_VX_PIXELFORMAT.generate_items(),
default = _g_Helper_VX_PIXELFORMAT.to_selection(RawVirtoolsTexture.cDefaultVideoFormat),
default = _g_Helper_VX_PIXELFORMAT.to_selection(DEFAULT_RAW_VIRTOOLS_TEXTURE.mVideoFormat),
translation_context = 'BBP_PG_virtools_texture/property'
) # type: ignore

View File

@@ -105,22 +105,37 @@ def _nest_custom_split_normal(nml_array: array.array) -> typing.Iterator[UTIL_vi
class TemporaryMesh():
"""
Create a temporary mesh for convenient exporting.
When exporting mesh, we need triangulate it first.
We create a temporary mesh to hold the triangulated mesh result.
When exporting mesh, we need evaluate it first, then triangulate it.
We create a temporary mesh to hold the evaluated and triangulated mesh result.
So that original object will not be affected and keep its original geometry.
Please note passed bpy.types.Object must be Mesh Object.
Please note passed bpy.types.Object must be an object which can be converted into mesh.
You can use this class provided static method to check it.
"""
__cAllowedObjectType: typing.ClassVar[set[str]] = {
'MESH', 'CURVE', 'SURFACE', 'FONT', 'META'
}
@staticmethod
def has_geometry(obj: bpy.types.Object):
"""
Check whether given Blender object has geometry.
If it has, it can safely utilize this class for visiting mesh.
"""
return obj.type in TemporaryMesh.__cAllowedObjectType
__mBindingObject: bpy.types.Object
__mEvaluatedObject: bpy.types.Object
__mTempMesh: bpy.types.Mesh
def __init__(self, binding_obj: bpy.types.Object):
depsgraph = bpy.context.evaluated_depsgraph_get()
self.__mBindingObject = binding_obj
self.__mTempMesh = None
self.__mEvaluatedObject = self.__mBindingObject.evaluated_get(depsgraph)
self.__mTempMesh = self.__mEvaluatedObject.to_mesh()
if self.__mBindingObject.data is None:
if self.__mTempMesh is None:
raise UTIL_functions.BBPException('try getting mesh from an object without mesh.')
self.__mTempMesh = self.__mBindingObject.to_mesh()
def __enter__(self):
return self
@@ -129,6 +144,7 @@ class TemporaryMesh():
self.dispose()
def is_valid(self) -> bool:
if self.__mBindingObject is None: return False
if self.__mBindingObject is None: return False
if self.__mTempMesh is None: return False
return True
@@ -136,7 +152,8 @@ class TemporaryMesh():
def dispose(self) -> None:
if self.is_valid():
self.__mTempMesh = None
self.__mBindingObject.to_mesh_clear()
self.__mEvaluatedObject.to_mesh_clear()
self.__mEvaluatedObject = None
self.__mBindingObject = None
def get_temp_mesh(self) -> bpy.types.Mesh:

View File

@@ -37,6 +37,7 @@ class ConflictResolver():
__mObjectStrategy: ConflictStrategy
__mLightStrategy: ConflictStrategy
__mCameraStrategy: ConflictStrategy
__mMeshStrategy: ConflictStrategy
__mMaterialStrategy: ConflictStrategy
__mTextureStrategy: ConflictStrategy
@@ -44,11 +45,13 @@ class ConflictResolver():
def __init__(self,
obj_strategy: ConflictStrategy,
light_strategy: ConflictStrategy,
camera_strategy: ConflictStrategy,
mesh_strategy: ConflictStrategy,
mtl_strategy: ConflictStrategy,
tex_strategy: ConflictStrategy):
self.__mObjectStrategy = obj_strategy
self.__mLightStrategy = light_strategy
self.__mCameraStrategy = camera_strategy
self.__mMeshStrategy = mesh_strategy
self.__mMaterialStrategy = mtl_strategy
self.__mTextureStrategy = tex_strategy
@@ -88,6 +91,22 @@ class ConflictResolver():
new_obj: bpy.types.Object = bpy.data.objects.new(name, new_light)
return (new_obj, new_light, True)
def create_camera(self, name: str) -> tuple[bpy.types.Object, bpy.types.Camera, bool]:
"""
Create camera data block and associated 3d object.
Same execution pattern with light creation.
"""
if self.__mCameraStrategy == ConflictStrategy.Current:
old_obj: bpy.types.Object | None = bpy.data.objects.get(name, None)
if old_obj is not None and old_obj.type == 'CAMERA':
return (old_obj, typing.cast(bpy.types.Camera, old_obj.data), False)
# create new object.
# if object or camera name is conflict, rename it directly without considering conflict strategy
new_camera: bpy.types.Camera = bpy.data.cameras.new(name)
new_obj: bpy.types.Object = bpy.data.objects.new(name, new_camera)
return (new_obj, new_camera, True)
def create_mesh(self, name: str) -> tuple[bpy.types.Mesh, bool]:
if self.__mMeshStrategy == ConflictStrategy.Current:
old: bpy.types.Mesh | None = bpy.data.meshes.get(name, None)
@@ -153,6 +172,14 @@ class ImportParams():
translation_context = 'BBP/UTIL_ioport_shared.ImportParams/property'
) # type: ignore
camera_conflict_strategy: bpy.props.EnumProperty(
name = "Camera Name Conflict",
items = _g_EnumHelper_ConflictStrategy.generate_items(),
description = "Define how to process camera name conflict",
default = _g_EnumHelper_ConflictStrategy.to_selection(ConflictStrategy.Rename),
translation_context = 'BBP/UTIL_ioport_shared.ImportParams/property'
) # type: ignore
object_conflict_strategy: bpy.props.EnumProperty(
name = "Object Name Conflict",
items = _g_EnumHelper_ConflictStrategy.generate_items(),
@@ -173,11 +200,13 @@ class ImportParams():
grid = body.grid_flow(row_major=False, columns=2)
grid.label(text='Object', icon='CUBE', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
grid.label(text='Light', icon='LIGHT', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
grid.label(text='Camera', icon='CAMERA_DATA', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
grid.label(text='Mesh', icon='MESH_DATA', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
grid.label(text='Material', icon='MATERIAL', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
grid.label(text='Texture', icon='TEXTURE', text_ctxt='BBP/UTIL_ioport_shared.ImportParams/draw')
grid.prop(self, 'object_conflict_strategy', text='')
grid.prop(self, 'light_conflict_strategy', text='')
grid.prop(self, 'camera_conflict_strategy', text='')
grid.prop(self, 'mesh_conflict_strategy', text='')
grid.prop(self, 'material_conflict_strategy', text='')
grid.prop(self, 'texture_conflict_strategy', text='')
@@ -194,6 +223,9 @@ class ImportParams():
def general_get_light_conflict_strategy(self) -> ConflictStrategy:
return _g_EnumHelper_ConflictStrategy.get_selection(self.light_conflict_strategy)
def general_get_camera_conflict_strategy(self) -> ConflictStrategy:
return _g_EnumHelper_ConflictStrategy.get_selection(self.camera_conflict_strategy)
def general_get_object_conflict_strategy(self) -> ConflictStrategy:
return _g_EnumHelper_ConflictStrategy.get_selection(self.object_conflict_strategy)
@@ -201,6 +233,7 @@ class ImportParams():
return ConflictResolver(
self.general_get_object_conflict_strategy(),
self.general_get_light_conflict_strategy(),
self.general_get_camera_conflict_strategy(),
self.general_get_mesh_conflict_strategy(),
self.general_get_material_conflict_strategy(),
self.general_get_texture_conflict_strategy()

View File

@@ -1,9 +1,10 @@
import bpy, mathutils
import typing, math
from dataclasses import dataclass
from . import UTIL_functions
# extract all declarations in PyBMap
from .PyBMap.virtools_types import *
# extract all declarations in pybmap
from .pybmap.virtools_types import *
# and add some patches for them
# mainly patch them with functions exchanging data with blender
# and the convertion between differnet coordinate system.
@@ -78,7 +79,7 @@ def vxmatrix_to_blender(self: VxMatrix) -> mathutils.Matrix:
## Hints about Light Matrix
# There is a slight difference between Virtools and Blender.
# In blender, the default direction of all directional light (spot and sun) are Down (-Z).
# Hoewver, in Virtools, the default direction of all directional light (spot and directional) are Forward (+Z).
# However, in Virtools, the default direction of all directional light (spot and directional) are Forward (+Z).
#
# As brief view, in Blender coordinate system, you can see that we got Blender default light direction
# from Virtools default light direction by rotating it around X-axis with -90 degree
@@ -108,130 +109,159 @@ def bldmatrix_restore_light_obj(data: mathutils.Matrix) -> mathutils.Matrix:
# so we simply right multiple it.
return data @ mathutils.Matrix.Rotation(math.radians(-90), 4, 'X')
## Hints about Camera Matrix
# Just like light, camera is also different between Virtools and Blender.
# In Blender, the default camera orientation is looking at -Z and +Y up.
# Oppositely, Virtools camera is looking at +Z and +Y up.
#
# These direction is based on their own coordinate system respectively.
# Accidently this difference is same like light.
# So we can simply copy light strategy in there.
def bldmatrix_patch_camera_obj(data: mathutils.Matrix) -> mathutils.Matrix:
"""
Add patch for camera world matrix to correct its direction.
This function is usually used when importing camera.
"""
# same operation like light matrix patch
return data @ mathutils.Matrix.Rotation(math.radians(90), 4, 'X')
def bldmatrix_restore_camera_obj(data: mathutils.Matrix) -> mathutils.Matrix:
"""
The reverse operation of bldmatrix_patch_camera_mat().
This function is usually used when exporting camera.
"""
# same operation like light matrix patch
return data @ mathutils.Matrix.Rotation(math.radians(-90), 4, 'X')
#endregion
#region Blender EnumProperty Creation
class EnumAnnotation():
mDisplayName: str
mDescription: str
def __init__(self, display_name: str, description: str):
self.mDisplayName = display_name
self.mDescription = description
@dataclass(frozen=True)
class EnumDocstring():
display_name: str
"""The name of this enum entry."""
description: str
"""The description of this enum entry."""
_g_Annotation: dict[type, dict[int, EnumAnnotation]] = {
_g_Docstring: dict[type, dict[int, EnumDocstring]] = {
VXTEXTURE_BLENDMODE: {
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_DECAL.value: EnumAnnotation("Decal", "Texture replace any material information "),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MODULATE.value: EnumAnnotation("Modulate", "Texture and material are combine. Alpha information of the texture replace material alpha component. "),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_DECALALPHA.value: EnumAnnotation("Decal Alpha", "Alpha information in the texture specify how material and texture are combined. Alpha information of the texture replace material alpha component. "),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MODULATEALPHA.value: EnumAnnotation("Modulate Alpha", "Alpha information in the texture specify how material and texture are combined "),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_DECALMASK.value: EnumAnnotation("Decal Mask", ""),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MODULATEMASK.value: EnumAnnotation("Modulate Mask", ""),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_COPY.value: EnumAnnotation("Copy", "Equivalent to DECAL "),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_ADD.value: EnumAnnotation("Add", ""),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_DOTPRODUCT3.value: EnumAnnotation("Dot Product 3", "Perform a Dot Product 3 between texture (normal map) and a referential vector given in VXRENDERSTATE_TEXTUREFACTOR. "),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MAX.value: EnumAnnotation("Max", ""),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_DECAL.value: EnumDocstring("Decal", "Texture replace any material information "),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MODULATE.value: EnumDocstring("Modulate", "Texture and material are combine. Alpha information of the texture replace material alpha component. "),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_DECALALPHA.value: EnumDocstring("Decal Alpha", "Alpha information in the texture specify how material and texture are combined. Alpha information of the texture replace material alpha component. "),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MODULATEALPHA.value: EnumDocstring("Modulate Alpha", "Alpha information in the texture specify how material and texture are combined "),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_DECALMASK.value: EnumDocstring("Decal Mask", ""),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MODULATEMASK.value: EnumDocstring("Modulate Mask", ""),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_COPY.value: EnumDocstring("Copy", "Equivalent to DECAL "),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_ADD.value: EnumDocstring("Add", ""),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_DOTPRODUCT3.value: EnumDocstring("Dot Product 3", "Perform a Dot Product 3 between texture (normal map) and a referential vector given in VXRENDERSTATE_TEXTUREFACTOR. "),
VXTEXTURE_BLENDMODE.VXTEXTUREBLEND_MAX.value: EnumDocstring("Max", ""),
},
VXTEXTURE_FILTERMODE: {
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_NEAREST.value: EnumAnnotation("Nearest", "No Filter "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEAR.value: EnumAnnotation("Linear", "Bilinear Interpolation "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_MIPNEAREST.value: EnumAnnotation("Mip Nearest", "Mip mapping "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_MIPLINEAR.value: EnumAnnotation("Mip Linear", "Mip Mapping with Bilinear interpolation "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEARMIPNEAREST.value: EnumAnnotation("Linear Mip Nearest", "Mip Mapping with Bilinear interpolation between mipmap levels. "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEARMIPLINEAR.value: EnumAnnotation("Linear Mip Linear", "Trilinear Filtering "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_ANISOTROPIC.value: EnumAnnotation("Anisotropic", "Anisotropic filtering "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_NEAREST.value: EnumDocstring("Nearest", "No Filter "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEAR.value: EnumDocstring("Linear", "Bilinear Interpolation "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_MIPNEAREST.value: EnumDocstring("Mip Nearest", "Mip mapping "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_MIPLINEAR.value: EnumDocstring("Mip Linear", "Mip Mapping with Bilinear interpolation "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEARMIPNEAREST.value: EnumDocstring("Linear Mip Nearest", "Mip Mapping with Bilinear interpolation between mipmap levels. "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_LINEARMIPLINEAR.value: EnumDocstring("Linear Mip Linear", "Trilinear Filtering "),
VXTEXTURE_FILTERMODE.VXTEXTUREFILTER_ANISOTROPIC.value: EnumDocstring("Anisotropic", "Anisotropic filtering "),
},
VXBLEND_MODE: {
VXBLEND_MODE.VXBLEND_ZERO.value: EnumAnnotation("Zero", "Blend factor is (0, 0, 0, 0). "),
VXBLEND_MODE.VXBLEND_ONE.value: EnumAnnotation("One", "Blend factor is (1, 1, 1, 1). "),
VXBLEND_MODE.VXBLEND_SRCCOLOR.value: EnumAnnotation("Src Color", "Blend factor is (Rs, Gs, Bs, As). "),
VXBLEND_MODE.VXBLEND_INVSRCCOLOR.value: EnumAnnotation("Inv Src Color", "Blend factor is (1-Rs, 1-Gs, 1-Bs, 1-As). "),
VXBLEND_MODE.VXBLEND_SRCALPHA.value: EnumAnnotation("Src Alpha", "Blend factor is (As, As, As, As). "),
VXBLEND_MODE.VXBLEND_INVSRCALPHA.value: EnumAnnotation("Inv Src Alpha", "Blend factor is (1-As, 1-As, 1-As, 1-As). "),
VXBLEND_MODE.VXBLEND_DESTALPHA.value: EnumAnnotation("Dest Alpha", "Blend factor is (Ad, Ad, Ad, Ad). "),
VXBLEND_MODE.VXBLEND_INVDESTALPHA.value: EnumAnnotation("Inv Dest Alpha", "Blend factor is (1-Ad, 1-Ad, 1-Ad, 1-Ad). "),
VXBLEND_MODE.VXBLEND_DESTCOLOR.value: EnumAnnotation("Dest Color", "Blend factor is (Rd, Gd, Bd, Ad). "),
VXBLEND_MODE.VXBLEND_INVDESTCOLOR.value: EnumAnnotation("Inv Dest Color", "Blend factor is (1-Rd, 1-Gd, 1-Bd, 1-Ad). "),
VXBLEND_MODE.VXBLEND_SRCALPHASAT.value: EnumAnnotation("Src Alpha Sat", "Blend factor is (f, f, f, 1); f = min(As, 1-Ad). "),
#VXBLEND_MODE.VXBLEND_BOTHSRCALPHA.value: EnumAnnotation("Both Src Alpha", "Source blend factor is (As, As, As, As) and destination blend factor is (1-As, 1-As, 1-As, 1-As) "),
#VXBLEND_MODE.VXBLEND_BOTHINVSRCALPHA.value: EnumAnnotation("Both Inv Src Alpha", "Source blend factor is (1-As, 1-As, 1-As, 1-As) and destination blend factor is (As, As, As, As) "),
VXBLEND_MODE.VXBLEND_ZERO.value: EnumDocstring("Zero", "Blend factor is (0, 0, 0, 0). "),
VXBLEND_MODE.VXBLEND_ONE.value: EnumDocstring("One", "Blend factor is (1, 1, 1, 1). "),
VXBLEND_MODE.VXBLEND_SRCCOLOR.value: EnumDocstring("Src Color", "Blend factor is (Rs, Gs, Bs, As). "),
VXBLEND_MODE.VXBLEND_INVSRCCOLOR.value: EnumDocstring("Inv Src Color", "Blend factor is (1-Rs, 1-Gs, 1-Bs, 1-As). "),
VXBLEND_MODE.VXBLEND_SRCALPHA.value: EnumDocstring("Src Alpha", "Blend factor is (As, As, As, As). "),
VXBLEND_MODE.VXBLEND_INVSRCALPHA.value: EnumDocstring("Inv Src Alpha", "Blend factor is (1-As, 1-As, 1-As, 1-As). "),
VXBLEND_MODE.VXBLEND_DESTALPHA.value: EnumDocstring("Dest Alpha", "Blend factor is (Ad, Ad, Ad, Ad). "),
VXBLEND_MODE.VXBLEND_INVDESTALPHA.value: EnumDocstring("Inv Dest Alpha", "Blend factor is (1-Ad, 1-Ad, 1-Ad, 1-Ad). "),
VXBLEND_MODE.VXBLEND_DESTCOLOR.value: EnumDocstring("Dest Color", "Blend factor is (Rd, Gd, Bd, Ad). "),
VXBLEND_MODE.VXBLEND_INVDESTCOLOR.value: EnumDocstring("Inv Dest Color", "Blend factor is (1-Rd, 1-Gd, 1-Bd, 1-Ad). "),
VXBLEND_MODE.VXBLEND_SRCALPHASAT.value: EnumDocstring("Src Alpha Sat", "Blend factor is (f, f, f, 1); f = min(As, 1-Ad). "),
#VXBLEND_MODE.VXBLEND_BOTHSRCALPHA.value: EnumDocstring("Both Src Alpha", "Source blend factor is (As, As, As, As) and destination blend factor is (1-As, 1-As, 1-As, 1-As) "),
#VXBLEND_MODE.VXBLEND_BOTHINVSRCALPHA.value: EnumDocstring("Both Inv Src Alpha", "Source blend factor is (1-As, 1-As, 1-As, 1-As) and destination blend factor is (As, As, As, As) "),
},
VXTEXTURE_ADDRESSMODE: {
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSWRAP.value: EnumAnnotation("Wrap", "Default mesh wrap mode is used (see CKMesh::SetWrapMode) "),
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSMIRROR.value: EnumAnnotation("Mirror", "Texture coordinates outside the range [0..1] are flipped evenly. "),
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSCLAMP.value: EnumAnnotation("Clamp", "Texture coordinates greater than 1.0 are set to 1.0, and values less than 0.0 are set to 0.0. "),
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSBORDER.value: EnumAnnotation("Border", "When texture coordinates are greater than 1.0 or less than 0.0 texture is set to a color defined in CKMaterial::SetTextureBorderColor. "),
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSMIRRORONCE.value: EnumAnnotation("Mirror Once", " "),
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSWRAP.value: EnumDocstring("Wrap", "Default mesh wrap mode is used (see CKMesh::SetWrapMode) "),
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSMIRROR.value: EnumDocstring("Mirror", "Texture coordinates outside the range [0..1] are flipped evenly. "),
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSCLAMP.value: EnumDocstring("Clamp", "Texture coordinates greater than 1.0 are set to 1.0, and values less than 0.0 are set to 0.0. "),
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSBORDER.value: EnumDocstring("Border", "When texture coordinates are greater than 1.0 or less than 0.0 texture is set to a color defined in CKMaterial::SetTextureBorderColor. "),
VXTEXTURE_ADDRESSMODE.VXTEXTURE_ADDRESSMIRRORONCE.value: EnumDocstring("Mirror Once", " "),
},
VXFILL_MODE: {
VXFILL_MODE.VXFILL_POINT.value: EnumAnnotation("Point", "Vertices rendering "),
VXFILL_MODE.VXFILL_WIREFRAME.value: EnumAnnotation("Wireframe", "Edges rendering "),
VXFILL_MODE.VXFILL_SOLID.value: EnumAnnotation("Solid", "Face rendering "),
VXFILL_MODE.VXFILL_POINT.value: EnumDocstring("Point", "Vertices rendering "),
VXFILL_MODE.VXFILL_WIREFRAME.value: EnumDocstring("Wireframe", "Edges rendering "),
VXFILL_MODE.VXFILL_SOLID.value: EnumDocstring("Solid", "Face rendering "),
},
VXSHADE_MODE: {
VXSHADE_MODE.VXSHADE_FLAT.value: EnumAnnotation("Flat", "Flat Shading "),
VXSHADE_MODE.VXSHADE_GOURAUD.value: EnumAnnotation("Gouraud", "Gouraud Shading "),
VXSHADE_MODE.VXSHADE_PHONG.value: EnumAnnotation("Phong", "Phong Shading (Not yet supported by most implementation) "),
VXSHADE_MODE.VXSHADE_FLAT.value: EnumDocstring("Flat", "Flat Shading "),
VXSHADE_MODE.VXSHADE_GOURAUD.value: EnumDocstring("Gouraud", "Gouraud Shading "),
VXSHADE_MODE.VXSHADE_PHONG.value: EnumDocstring("Phong", "Phong Shading (Not yet supported by most implementation) "),
},
VXCMPFUNC: {
VXCMPFUNC.VXCMP_NEVER.value: EnumAnnotation("Never", "Always fail the test. "),
VXCMPFUNC.VXCMP_LESS.value: EnumAnnotation("Less", "Accept if value if less than current value. "),
VXCMPFUNC.VXCMP_EQUAL.value: EnumAnnotation("Equal", "Accept if value if equal than current value. "),
VXCMPFUNC.VXCMP_LESSEQUAL.value: EnumAnnotation("Less Equal", "Accept if value if less or equal than current value. "),
VXCMPFUNC.VXCMP_GREATER.value: EnumAnnotation("Greater", "Accept if value if greater than current value. "),
VXCMPFUNC.VXCMP_NOTEQUAL.value: EnumAnnotation("Not Equal", "Accept if value if different than current value. "),
VXCMPFUNC.VXCMP_GREATEREQUAL.value: EnumAnnotation("Greater Equal", "Accept if value if greater or equal current value. "),
VXCMPFUNC.VXCMP_ALWAYS.value: EnumAnnotation("Always", "Always accept the test. "),
VXCMPFUNC.VXCMP_NEVER.value: EnumDocstring("Never", "Always fail the test. "),
VXCMPFUNC.VXCMP_LESS.value: EnumDocstring("Less", "Accept if value if less than current value. "),
VXCMPFUNC.VXCMP_EQUAL.value: EnumDocstring("Equal", "Accept if value if equal than current value. "),
VXCMPFUNC.VXCMP_LESSEQUAL.value: EnumDocstring("Less Equal", "Accept if value if less or equal than current value. "),
VXCMPFUNC.VXCMP_GREATER.value: EnumDocstring("Greater", "Accept if value if greater than current value. "),
VXCMPFUNC.VXCMP_NOTEQUAL.value: EnumDocstring("Not Equal", "Accept if value if different than current value. "),
VXCMPFUNC.VXCMP_GREATEREQUAL.value: EnumDocstring("Greater Equal", "Accept if value if greater or equal current value. "),
VXCMPFUNC.VXCMP_ALWAYS.value: EnumDocstring("Always", "Always accept the test. "),
},
CK_TEXTURE_SAVEOPTIONS: {
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_RAWDATA.value: EnumAnnotation("Raw Data", "Save raw data inside file. The bitmap is saved in a raw 32 bit per pixel format. "),
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL.value: EnumAnnotation("External", "Store only the file name for the texture. The bitmap file must be present in the bitmap paths when loading the composition. "),
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_IMAGEFORMAT.value: EnumAnnotation("Image Format", "Save using format specified. The bitmap data will be converted to the specified format by the correspondant bitmap plugin and saved inside file. "),
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_USEGLOBAL.value: EnumAnnotation("Use Global", "Use Global settings, that is the settings given with CKContext::SetGlobalImagesSaveOptions. (Not valid when using CKContext::SetImagesSaveOptions). "),
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_INCLUDEORIGINALFILE.value: EnumAnnotation("Include Original File", "Insert original image file inside CMO file. The bitmap file that was used originally for the texture or sprite will be append to the composition file and extracted when the file is loaded. "),
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_RAWDATA.value: EnumDocstring("Raw Data", "Save raw data inside file. The bitmap is saved in a raw 32 bit per pixel format. "),
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL.value: EnumDocstring("External", "Store only the file name for the texture. The bitmap file must be present in the bitmap paths when loading the composition. "),
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_IMAGEFORMAT.value: EnumDocstring("Image Format", "Save using format specified. The bitmap data will be converted to the specified format by the correspondant bitmap plugin and saved inside file. "),
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_USEGLOBAL.value: EnumDocstring("Use Global", "Use Global settings, that is the settings given with CKContext::SetGlobalImagesSaveOptions. (Not valid when using CKContext::SetImagesSaveOptions). "),
CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_INCLUDEORIGINALFILE.value: EnumDocstring("Include Original File", "Insert original image file inside CMO file. The bitmap file that was used originally for the texture or sprite will be append to the composition file and extracted when the file is loaded. "),
},
VX_PIXELFORMAT: {
VX_PIXELFORMAT._32_ARGB8888.value: EnumAnnotation("32 Bits ARGB8888", "32-bit ARGB pixel format with alpha "),
VX_PIXELFORMAT._32_RGB888.value: EnumAnnotation("32 Bits RGB888", "32-bit RGB pixel format without alpha "),
VX_PIXELFORMAT._24_RGB888.value: EnumAnnotation("24 Bits RGB888", "24-bit RGB pixel format "),
VX_PIXELFORMAT._16_RGB565.value: EnumAnnotation("16 Bits RGB565", "16-bit RGB pixel format "),
VX_PIXELFORMAT._16_RGB555.value: EnumAnnotation("16 Bits RGB555", "16-bit RGB pixel format (5 bits per color) "),
VX_PIXELFORMAT._16_ARGB1555.value: EnumAnnotation("16 Bits ARGB1555", "16-bit ARGB pixel format (5 bits per color + 1 bit for alpha) "),
VX_PIXELFORMAT._16_ARGB4444.value: EnumAnnotation("16 Bits ARGB4444", "16-bit ARGB pixel format (4 bits per color) "),
VX_PIXELFORMAT._8_RGB332.value: EnumAnnotation("8 Bits RGB332", "8-bit RGB pixel format "),
VX_PIXELFORMAT._8_ARGB2222.value: EnumAnnotation("8 Bits ARGB2222", "8-bit ARGB pixel format "),
VX_PIXELFORMAT._32_ABGR8888.value: EnumAnnotation("32 Bits ABGR8888", "32-bit ABGR pixel format "),
VX_PIXELFORMAT._32_RGBA8888.value: EnumAnnotation("32 Bits RGBA8888", "32-bit RGBA pixel format "),
VX_PIXELFORMAT._32_BGRA8888.value: EnumAnnotation("32 Bits BGRA8888", "32-bit BGRA pixel format "),
VX_PIXELFORMAT._32_BGR888.value: EnumAnnotation("32 Bits BGR888", "32-bit BGR pixel format "),
VX_PIXELFORMAT._24_BGR888.value: EnumAnnotation("24 Bits BGR888", "24-bit BGR pixel format "),
VX_PIXELFORMAT._16_BGR565.value: EnumAnnotation("16 Bits BGR565", "16-bit BGR pixel format "),
VX_PIXELFORMAT._16_BGR555.value: EnumAnnotation("16 Bits BGR555", "16-bit BGR pixel format (5 bits per color) "),
VX_PIXELFORMAT._16_ABGR1555.value: EnumAnnotation("16 Bits ABGR1555", "16-bit ABGR pixel format (5 bits per color + 1 bit for alpha) "),
VX_PIXELFORMAT._16_ABGR4444.value: EnumAnnotation("16 Bits ABGR4444", "16-bit ABGR pixel format (4 bits per color) "),
VX_PIXELFORMAT._DXT1.value: EnumAnnotation("DXT1", "S3/DirectX Texture Compression 1 "),
VX_PIXELFORMAT._DXT2.value: EnumAnnotation("DXT2", "S3/DirectX Texture Compression 2 "),
VX_PIXELFORMAT._DXT3.value: EnumAnnotation("DXT3", "S3/DirectX Texture Compression 3 "),
VX_PIXELFORMAT._DXT4.value: EnumAnnotation("DXT4", "S3/DirectX Texture Compression 4 "),
VX_PIXELFORMAT._DXT5.value: EnumAnnotation("DXT5", "S3/DirectX Texture Compression 5 "),
VX_PIXELFORMAT._16_V8U8.value: EnumAnnotation("16 Bits V8U8", "16-bit Bump Map format format (8 bits per color) "),
VX_PIXELFORMAT._32_V16U16.value: EnumAnnotation("32 Bits V16U16", "32-bit Bump Map format format (16 bits per color) "),
VX_PIXELFORMAT._16_L6V5U5.value: EnumAnnotation("16 Bits L6V5U5", "16-bit Bump Map format format with luminance "),
VX_PIXELFORMAT._32_X8L8V8U8.value: EnumAnnotation("32 Bits X8L8V8U8", "32-bit Bump Map format format with luminance "),
VX_PIXELFORMAT._8_ABGR8888_CLUT.value: EnumAnnotation("8 Bits ABGR8888 CLUT", "8 bits indexed CLUT (ABGR) "),
VX_PIXELFORMAT._8_ARGB8888_CLUT.value: EnumAnnotation("8 Bits ARGB8888 CLUT", "8 bits indexed CLUT (ARGB) "),
VX_PIXELFORMAT._4_ABGR8888_CLUT.value: EnumAnnotation("4 Bits ABGR8888 CLUT", "4 bits indexed CLUT (ABGR) "),
VX_PIXELFORMAT._4_ARGB8888_CLUT.value: EnumAnnotation("4 Bits ARGB8888 CLUT", "4 bits indexed CLUT (ARGB) "),
VX_PIXELFORMAT._32_ARGB8888.value: EnumDocstring("32 Bits ARGB8888", "32-bit ARGB pixel format with alpha "),
VX_PIXELFORMAT._32_RGB888.value: EnumDocstring("32 Bits RGB888", "32-bit RGB pixel format without alpha "),
VX_PIXELFORMAT._24_RGB888.value: EnumDocstring("24 Bits RGB888", "24-bit RGB pixel format "),
VX_PIXELFORMAT._16_RGB565.value: EnumDocstring("16 Bits RGB565", "16-bit RGB pixel format "),
VX_PIXELFORMAT._16_RGB555.value: EnumDocstring("16 Bits RGB555", "16-bit RGB pixel format (5 bits per color) "),
VX_PIXELFORMAT._16_ARGB1555.value: EnumDocstring("16 Bits ARGB1555", "16-bit ARGB pixel format (5 bits per color + 1 bit for alpha) "),
VX_PIXELFORMAT._16_ARGB4444.value: EnumDocstring("16 Bits ARGB4444", "16-bit ARGB pixel format (4 bits per color) "),
VX_PIXELFORMAT._8_RGB332.value: EnumDocstring("8 Bits RGB332", "8-bit RGB pixel format "),
VX_PIXELFORMAT._8_ARGB2222.value: EnumDocstring("8 Bits ARGB2222", "8-bit ARGB pixel format "),
VX_PIXELFORMAT._32_ABGR8888.value: EnumDocstring("32 Bits ABGR8888", "32-bit ABGR pixel format "),
VX_PIXELFORMAT._32_RGBA8888.value: EnumDocstring("32 Bits RGBA8888", "32-bit RGBA pixel format "),
VX_PIXELFORMAT._32_BGRA8888.value: EnumDocstring("32 Bits BGRA8888", "32-bit BGRA pixel format "),
VX_PIXELFORMAT._32_BGR888.value: EnumDocstring("32 Bits BGR888", "32-bit BGR pixel format "),
VX_PIXELFORMAT._24_BGR888.value: EnumDocstring("24 Bits BGR888", "24-bit BGR pixel format "),
VX_PIXELFORMAT._16_BGR565.value: EnumDocstring("16 Bits BGR565", "16-bit BGR pixel format "),
VX_PIXELFORMAT._16_BGR555.value: EnumDocstring("16 Bits BGR555", "16-bit BGR pixel format (5 bits per color) "),
VX_PIXELFORMAT._16_ABGR1555.value: EnumDocstring("16 Bits ABGR1555", "16-bit ABGR pixel format (5 bits per color + 1 bit for alpha) "),
VX_PIXELFORMAT._16_ABGR4444.value: EnumDocstring("16 Bits ABGR4444", "16-bit ABGR pixel format (4 bits per color) "),
VX_PIXELFORMAT._DXT1.value: EnumDocstring("DXT1", "S3/DirectX Texture Compression 1 "),
VX_PIXELFORMAT._DXT2.value: EnumDocstring("DXT2", "S3/DirectX Texture Compression 2 "),
VX_PIXELFORMAT._DXT3.value: EnumDocstring("DXT3", "S3/DirectX Texture Compression 3 "),
VX_PIXELFORMAT._DXT4.value: EnumDocstring("DXT4", "S3/DirectX Texture Compression 4 "),
VX_PIXELFORMAT._DXT5.value: EnumDocstring("DXT5", "S3/DirectX Texture Compression 5 "),
VX_PIXELFORMAT._16_V8U8.value: EnumDocstring("16 Bits V8U8", "16-bit Bump Map format format (8 bits per color) "),
VX_PIXELFORMAT._32_V16U16.value: EnumDocstring("32 Bits V16U16", "32-bit Bump Map format format (16 bits per color) "),
VX_PIXELFORMAT._16_L6V5U5.value: EnumDocstring("16 Bits L6V5U5", "16-bit Bump Map format format with luminance "),
VX_PIXELFORMAT._32_X8L8V8U8.value: EnumDocstring("32 Bits X8L8V8U8", "32-bit Bump Map format format with luminance "),
VX_PIXELFORMAT._8_ABGR8888_CLUT.value: EnumDocstring("8 Bits ABGR8888 CLUT", "8 bits indexed CLUT (ABGR) "),
VX_PIXELFORMAT._8_ARGB8888_CLUT.value: EnumDocstring("8 Bits ARGB8888 CLUT", "8 bits indexed CLUT (ARGB) "),
VX_PIXELFORMAT._4_ABGR8888_CLUT.value: EnumDocstring("4 Bits ABGR8888 CLUT", "4 bits indexed CLUT (ABGR) "),
VX_PIXELFORMAT._4_ARGB8888_CLUT.value: EnumDocstring("4 Bits ARGB8888 CLUT", "4 bits indexed CLUT (ARGB) "),
},
VXLIGHT_TYPE: {
VXLIGHT_TYPE.VX_LIGHTPOINT.value: EnumAnnotation("Point", "The Light is a point of light "),
VXLIGHT_TYPE.VX_LIGHTSPOT.value: EnumAnnotation("Spot", "The light is a spotlight "),
VXLIGHT_TYPE.VX_LIGHTDIREC.value: EnumAnnotation("Directional", "The light is directional light : Lights comes from an infinite point so only direction of light can be given "),
#VXLIGHT_TYPE.VX_LIGHTPARA.value: EnumAnnotation("Lightpara", "Obsolete, do not use "),
VXLIGHT_TYPE.VX_LIGHTPOINT.value: EnumDocstring("Point", "The Light is a point of light "),
VXLIGHT_TYPE.VX_LIGHTSPOT.value: EnumDocstring("Spot", "The light is a spotlight "),
VXLIGHT_TYPE.VX_LIGHTDIREC.value: EnumDocstring("Directional", "The light is directional light : Lights comes from an infinite point so only direction of light can be given "),
#VXLIGHT_TYPE.VX_LIGHTPARA.value: EnumDocstring("Lightpara", "Obsolete, do not use "),
},
VXMESH_LITMODE: {
VXMESH_LITMODE.VX_PRELITMESH.value: EnumAnnotation("Prelit", "Lighting use color information store with vertices "),
VXMESH_LITMODE.VX_LITMESH.value: EnumAnnotation("Lit", "Lighting is done by renderer using normals and face material information. "),
VXMESH_LITMODE.VX_PRELITMESH.value: EnumDocstring("Prelit", "Lighting use color information store with vertices "),
VXMESH_LITMODE.VX_LITMESH.value: EnumDocstring("Lit", "Lighting is done by renderer using normals and face material information. "),
},
CK_CAMERA_PROJECTION: {
CK_CAMERA_PROJECTION.CK_PERSPECTIVEPROJECTION.value: EnumDocstring("Perspective Projection", ""),
CK_CAMERA_PROJECTION.CK_ORTHOGRAPHICPROJECTION.value: EnumDocstring("Orthographic Projection", ""),
}
}
@@ -241,13 +271,13 @@ class EnumPropHelper(UTIL_functions.EnumPropHelper[_TRawEnum]):
"""
Virtools type specified Blender EnumProp helper.
"""
__mAnnotationDict: dict[int, EnumAnnotation]
__mDocstringDict: dict[int, EnumDocstring]
__mEnumTy: type[_TRawEnum]
def __init__(self, ty: type[_TRawEnum]):
# set enum type and annotation ref first
# set enum type and docstring ref first
self.__mEnumTy = ty
self.__mAnnotationDict = _g_Annotation[ty]
self.__mDocstringDict = _g_Docstring[ty]
# YYC MARK:
# It seems that Pylance has bad generic analyse ability in there.
@@ -259,8 +289,8 @@ class EnumPropHelper(UTIL_functions.EnumPropHelper[_TRawEnum]):
self.__mEnumTy, # enum.Enum its self is iterable
lambda x: str(x.value), # convert enum.Enum's value to string
lambda x: self.__mEnumTy(int(x)), # use stored enum type and int() to get enum member
lambda x: self.__mAnnotationDict[x.value].mDisplayName,
lambda x: self.__mAnnotationDict[x.value].mDescription,
lambda x: self.__mDocstringDict[x.value].display_name,
lambda x: self.__mDocstringDict[x.value].description,
lambda _: ''
)
@@ -273,11 +303,11 @@ def virtools_name_regulator(name: str | None) -> str:
else: return bpy.app.translations.pgettext_data('annoymous', 'BME/UTIL_virtools_types.virtools_name_regulator()')
# YYC MARK:
# There are default encodings for PyBMap. We support Western European and Simplified Chinese in default.
# There are default encodings for pybmap. We support Western European and Simplified Chinese in default.
# Since LibCmo 0.2, the encoding name of LibCmo become universal encoding which is platfoorm independent.
# So no need set it according to different platform.
# Use universal encoding name (like Python).
g_PyBMapDefaultEncodings: tuple[str, ...] = (
g_PybmapDefaultEncodings: tuple[str, ...] = (
'cp1252',
'gbk'
)

View File

@@ -17,7 +17,7 @@ UTIL_icons_manager.register()
# then load other modules
from . import UTIL_translation
from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_light, PROP_virtools_group
from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_light, PROP_virtools_camera, 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
@@ -179,7 +179,6 @@ class BBP_MT_View3DMenu(bpy.types.Menu):
layout.operator(OP_OBJECT_legacy_align.BBP_OT_legacy_align.bl_idname)
layout.separator()
layout.label(text='Camera', icon='CAMERA_DATA', text_ctxt='BBP_MT_View3DMenu/draw')
layout.operator(OP_OBJECT_game_view.BBP_OT_game_resolution.bl_idname)
layout.operator(OP_OBJECT_game_view.BBP_OT_game_camera.bl_idname)
layout.separator()
layout.label(text='Select', icon='SELECT_SET', text_ctxt='BBP_MT_View3DMenu/draw')
@@ -327,6 +326,7 @@ def register() -> None:
PROP_virtools_texture.register()
PROP_virtools_mesh.register()
PROP_virtools_light.register()
PROP_virtools_camera.register()
PROP_virtools_group.register()
PROP_ballance_element.register()
PROP_bme_material.register()
@@ -397,6 +397,7 @@ def unregister() -> None:
PROP_bme_material.unregister()
PROP_ballance_element.unregister()
PROP_virtools_group.unregister()
PROP_virtools_camera.unregister()
PROP_virtools_light.unregister()
PROP_virtools_mesh.unregister()
PROP_virtools_texture.unregister()

View File

@@ -6,7 +6,7 @@ schema_version = "1.0.0"
# Example of manifest file for a Blender extension
# Change the values according to your extension
id = "bbp_ng"
version = "4.3.0"
version = "4.4.0"
name = "Ballance Blender Plugin"
tagline = "The specialized add-on served for creating game map of Ballance"
maintainer = "yyc12345 <yyc12321@outlook.com>"

View File

@@ -1,18 +1,25 @@
# Add Floor
!!! info "Not latest version"
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
!!! info "BME is extensible"
BME's floor adder is extensible, each item in the menu is actually described by a set of JSON data. You can read the [Technical Information](./tech-infos.md) section to learn how we write this JSON, and you can even expand the types of floors that BME can create to suit your needs.
## Start Generating
### From Menu
In the 3D view, click `Add - Floors` to expand the Add Floors menu. The menu is shown below.
![](../imgs/bme-adder.png)
Click on the menu to see all supported floor types in the submenu that pops up. Their names and icons hint at the style and shape of the floor it is intended to create.
Click on the menu to see all supported floor types by categories in the submenu that pops up. Their names and icons hint at the style and shape of the floor it is intended to create.
!!! info "BME is extensible"
BME's floor adder is extensible, each item in the menu is actually described by a set of JSON data. You can read the [Technical Information](./tech-infos.md) section to learn how we write this JSON, and you can even expand the types of floors that BME can create to suit your needs.
### From Sidebar
Additionally, you can press `N` on keyboard to expand the sidebar of 3D view and find `Ballance` tab inside it. You also can find these adder by clicking it and expanding `Floor` panel as presented in following image:
![](../imgs/bme-adder-sidebar.png)
Comparing with menu, the advantage of this solution is that the sidebar is persistent in UI layout. It is more convenient and effective when adding multiple floors by reducing the time of repetitive opening add menu. Also, there is Rail and Component adder panels under this tab. This will not be introduced again in following chapters.
## Configure Floor
@@ -26,11 +33,14 @@ Then it also asks us to provide the height of the platform, which defaults to 5,
Finally, it tells us which sides of the floor we need to configure to display. Note that Top and Bottom are the top and bottom surfaces along the height direction (Z axis), while Front, Back, Left, and Right are the front, back, left, and right surfaces when looking down with your head on the -X axis and your eyes on the -Z axis. You may notice that there is a perspective cube in the center of these six face buttons, and in fact the positions of these six face options correspond to the positions of the six faces of this perspective cube.
## Extra Transform
At the bottom of the BME configuration dialog, you can always find an area called Extra Transform. In this area, there are two options for configuration: extra translation and rotation. These fields are primarily intended for visual edit.
Before explaining the actual functions of these fields, it is important to understand that the floor created by BME is always generated based on the current position of the 3D cursor. In other words, wherever the 3D cursor is located, the newly created surface will be positioned accordingly. This design primarily considers visual edit, allowing users to first move the 3D cursor to the desired location for adding a floor, and then add the corresponding BME structure. This enables users to preview results in real-time while adjusting parameters. Sometimes, the position of your 3D cursor may not be entirely accurate, or the final generated structure may require certain rotation to meet your expectations. In such cases, Extra Transform fields can be utilized to apply extra translation and rotation to the generated structure in relation to the 3D cursor, ensuring it is accurately positioned. In this way, the correct results can be previewed when adjusting the parameters.
## Tips
Each floor type has a different number of configuration entries, so for different floor types, you will need to follow the configuration hint text to understand what the corresponding configuration does. Some floor types may have a large number of configuration entries, while others may have no configuration entries at all.
The default values for the floor type configuration are set to the values that were most commonly used when the floor was created. The values are reset to the defaults each time the floor type is switched or recreated.

View File

@@ -1,25 +1,104 @@
# Compile and Distribute Plugin
!!! info "Not latest version"
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
This page will guide you in compiling the plugin as well as distributing it.
## Compiling LibCmo with BMap
BBP's Virtools file native import/export functionality relies on BMap and its Python binding PyBMap. In order to distribute the plugin, we need to first compile BMap and its predecessor LibCmo, and before doing so, you need to check the version of BMap you need. Because BBP doesn't always use the latest version of BMap, e.g. if you're compiling an older version of BBP, it's obviously not possible to rely on the latest version of BMap. BMap is constantly being upgraded, and the functionality it provides is constantly changing, and different versions of BMap are incompatible. BBP usually states the version of BMap it uses at the time of release, but if BBP doesn't point it out, you may need to look for the most recent version of BMap that compiles with the version of BBP at the time of its release.
BBP's Virtools file native import/export functionality relies on BMap and its Python binding pybmap. In order to distribute the plugin, we need to first compile BMap and its predecessor LibCmo, and before doing so, you need to check the version of BMap you need. Because BBP doesn't always use the latest version of BMap, e.g. if you're compiling an older version of BBP, it's obviously not possible to rely on the latest version of BMap. BMap is constantly being upgraded, and the functionality it provides is constantly changing, and different versions of BMap are incompatible. BBP usually states the version of BMap it uses at the time of release, but if BBP doesn't point it out, you may need to look for the most recent version of BMap that compiles with the version of BBP at the time of its release.
After specifying the version, you need to visit [LibCmo GitHub repository](https://github.com/yyc12345/libcmo21). Then clone the project and use the Git command to go to the corresponding version (or just download the source code of the corresponding version). Then follow LibCmo's compilation manual to compile to get BMap. on Windows, you'll usually get the files `BMap.dll` and `BMap.pdb`. On Linux, it will be `BMap.so`.
Then we need to configure PyBMap, which comes with LibCmo. Please follow the manual of PyBMap to combine the compiled binary BMap library with PyBMap. That is to complete the PyBMap configuration.
Then we need to configure pybmap, which comes with LibCmo. Please follow the manual of pybmap to combine the compiled binary BMap library with pybmap. That is to complete the pybmap configuration.
Then we need to copy the configured PyBMap to our project under `bbp_ng/PyBMap` to complete this step.
Then we need to copy the configured pybmap to our project under `bbp_ng/pybmap` to complete this step.
## Generate Thumbnails and Compress JSON
## Generate Resources
BBP comes with a built-in set of custom icons, as well as the JSON files needed by its component BME to describe the structure. By batch generating thumbnails and compressing JSON operations, the size of these parts can be reduced, making them suitable for loading in Blender and easier to distribute.
BBP needs some resoures to run, and these resources need to be processed before using them.
Go to the `bbp_ng/tools` folder and run `python3 build_icons.py` which will batch generate thumbnails (this requires the PIL library, please install it via pip in advance). It actually generates thumbnails from the original images in the `bbp_ng/raw_icons` directory and stores them in the `bbp_ng/icons` folder. Running `python3 build_jsons.py` will compress the JSON, which actually reads, compresses, and writes the raw JSON files from the `bbp_ng/raw_jsons` directory into the `bbp_ng/jsons` folder.
For generating these resrouces, we firstly need to navigate to `scripts` directory, and execute `uv sync` to restore the environment for scripts (Astral UV required).
### Generate Thumbnails
BBP comes with a built-in set of custom icons, however these icons are stored as their original size in repository for keeping convenient editing and high quality. We need to reduce the size of these icons to make them are easy to be loaded on Blender and easy for distribution by generating thumbnails for them.
Execute `uv run build_icons.py` to generate thumbnails. It actually generates thumbnails from the original images in the `assets/icons` directory and stores them in the `bbp_ng/icons` folder.
### Generate JSONs
The BME component in BBP relies on a series of JSON files to describe the prototype. These profiles are stored in the library in JSON5 format, making them easy for writers to read and write. We converte these JSON5 files to JSON files and compressing their size makes them easier to load in Blender, as well as to facilitate plugin distribution, by batchly generating them.
If you are the plugin developer or writer of these prototypes, you need to do an additional thing before generating these JSON files: verify these JSON files. The BBP plugin will assume that these JSON files are correct when loading them. If you put a JSON file with errors (e.g. missing some fields or has some typos, etc.), it will cause Blender to throw an error when creating prototype. Therefore, it is necessary to verify these JSON files. Execute `uv run validate_jsons.py` to verify all prototype files. If there are no errors, it means that everything is okey. It is important to note that the validator is not perfect, it can only verify the data as much as possible to ensure that some common erros (e.g. typo in field name) will not occur. It can not make 100% sure about that there is no error inside these files.
For compilers, all you need to do is that execute `uv run build_jsons.py` to generate JSON files. It actually reads, compresses, and writes the original JSON5 files in `assets/jsons` directory to the `bbp_ng/jsons` folder in JSON format.
### Generate Element Meshes
BBP has built-in mesh data for all Ballance element placeholders. Execute `uv run build_jsons.py` to deploy these meshes, which simply copies the mesh files under `assets/jsons` folder to `bbp_ng/jsons` folder.
## Translation
The BBP plugin supports multilingual functionality, so we need to extract and update the content to be translated before the official release, and then proceed to the next step after translating all the content.
Blender's multilingual support for plugins is not satisfactory, and BBP's design is relatively special, so BBP adopts a different way to manage translations than the official recommended plugin translation management method of Blender: that is, use PO files to manage translations instead of the officially recommended Python script format.
!!! info "Do NOT submit translations in Python format"
As mentioned above, BBP uses PO files to manage translations, rather than the Python source format recommended by Blender. However, this does not prevent Blender's multilingual plugins from writing translations in the Python source format into the plugin's source code. Submitting duplicate translations not only increases the repository size but also complicates management. Therefore, BBP requires you to delete the Python-format translations before submission.
The specific operational method is to open the `bbp_ng/UTIL_translation.py` file before submission and change the value of the translation tuple variable `translations_tuple` to an empty tuple (i.e., `translations_tuple = ()`).
### Extract Translation Template
Before translating, it is important to first recognize that the text requiring translation for BBP consists of two parts. One part is the BBP plugin itself, whose text to be translated can be extracted by Blender's built-in multilingual plugin. The other part is the JSON file in the BME component that describes the structure, where the names of various showcase fields need to be translated. However, this part cannot be handled by Blender's multilingual plugin, as it is dynamically loaded. Fortunately, we have written an extractor that can extract the relevant text to be translated from the BME's JSON file. To do this, execute `uv run extract_jsons.py` in the folder where the previous script was run, and the script will extract the text to be translated and write it into the `i18n/bme.pot` file. Therefore, the next task is simply to extract the translation for the plugin portion.
First, you need to enable Blender's built-in multilingual plugin, "Manage UI translations". To enable it, you may also need to download the source code and translation repository corresponding to your version of Blender. For specific instructions, please refer to the [official Blender Documentation](https://developer.blender.org/docs/handbook/translating/translator_guide/). Once you have enabled the plugin and configured the appropriate related paths in the preferences, you can find the "I18n Update Translation" panel under the "Render" panel. You can then proceed to extract the translations by following these steps:
1. First, ensure that all Blender processes are closed; otherwise, the plugin will remain in a loading state, and modifications to the translated tuple variables will not take effect.
1. Change the value of the translation tuple variable `translations_tuple` in the plugin to an empty tuple (refer to the earlier mentioned submission notes). Setting the translation tuple to empty can reset the translation status of the plugin, ensuring that subsequent text extraction operations are not affected by any existing translations.
1. Open Blender, go to the "I18n Update Translation" panel, click "Deselect All" to uncheck all languages, and then only check the boxes next to the following languages (as BBP currently supports a limited number of languages):
* Simplified Chinese (简体中文)
1. Click the "Refresh I18n Data" button at the bottom of the section, then in the pop-up window, select "Ballance Blender Plugin". After a short wait, the plugin will complete the extraction of the characters to be translated. At this point, the plugin merely extracts the translation fields into the source code of the plugin in a format recommended by Blender, using Python source code.
1. In order to obtain the desired editable POT file, you need to click the "Export PO" button. In the pop-up window, select the "Ballance Blender Plugin", and you can choose any folder for saving the location (for example, the desktop, as it will generate many files, including our desired POT file). Uncheck the "Update Existing" option on the right and ensure that "Export POT" is checked, then proceed to save. After the export is complete, you will find a translation template file named `blender.pot` and numerous `.po` files named after language identifiers in the folder you selected.
1. You need to copy `blender.pot` to the `i18n` folder and rename it to `bbp_ng.pot`. At this point, we have extracted all the content that needs to be translated.
### Merge Translation Template
There are currently two POT files in the `i18n` folder, which represent two sets of extracted text awaiting translation. We need to merge them. Execute `xgettext -o blender.pot bbp_ng.pot bme.pot` in the `i18n` folder to perform the merge. The merged `i18n/blender.pot` will serve as the translation template.
### Create New Language Translation
If BBP needs to support more languages in the future, you will need to create the corresponding PO translation files for the new languages from the POT files. You can create them through one of the following methods.
* By using software such as Poedit to open the POT file, select function like create new translation from it, and then save to create.
* Create a new language PO translation file using commands such as `msginit -i blender.pot -o zh_HANS.po -l zh_CN.utf8`.
There are various ways to create, but the only point to note is that you need to set the file name (if the file name is incorrect, Blender will refuse to accept it) and the area name (which will be used when using `msginit`, with the purpose of ensuring UTF8 format encoding) as shown in the table below.
|Language|File Name|Area Name|
|:---|:---|:---|
|Simplified Chinese (简体中文)|`zh_HANS.po`|`zh_CN.utf8`|
### Update Language Translation
Creating new language translations is not common; a more common practice is to update existing language translation files based on translation templates. You can update them through one of the following methods.
* Open the PO file using software such as Poedit, and then select to update from the POT file.
* Update using commands such as `msgmerge -U zh-HANS.po blender.pot --backup=none`.
### Start Translating
After updating the PO translation files for all languages, you may choose your preferred method for translation, such as using Poedit or editing directly.
The BBP requires the use of the KDE community's translation standards to standardize the translations of plugins. For example, you can find the KDE community's translation standards for Simplified Chinese on the [KDE China](https://kde-china.org/tutorial.html) website.
### Write Translation Back
The translation in PO format cannot be recognized by Blender. Therefore, after the translation is completed, you also need to utilize Blender's multilingual plugin to convert the PO file back into a translation in Python source code format that Blender can recognize. Due to issues with the design of Blender's multilingual plugin, we cannot directly use the "Import PO" function to convert the PO file back into Python source code format. You need to follow the steps below sequentially in order to import the PO translation into the plugin:
1. First, ensure that all Blender processes are closed; otherwise, the plugin will remain in a loading state, and modifications to the translated tuple variables will not take effect.
1. Change the value of the translation tuple variable `translations_tuple` in the plugin to an empty tuple (refer to the earlier notes regarding submissions). The purpose of this step is to ensure that the entire plugin lacks translation entries, so that when using the "Import PO" feature, Blender's multilingual plugin will consider all fields stored in the PO file as needing translation, thereby preventing situations where only a portion of the translations is imported (as the translations for the BME portion were merged later).
1. Open Blender, navigate to the "I18n Update Translation" panel, and following the procedure used when extracting the translation template, select only the languages that need to be translated from the language list.
1. Click the "Import PO" button in the bottom row, then select the "Ballance Blender Plugin" in the pop-up window, and choose the `i18n` folder for import. In this way, we have completed the process of importing the PO file into a Python source code format recognizable by Blender.
## Packaging
@@ -29,13 +108,11 @@ Assuming that the final output file is `redist/bbp_ng.zip`. If you are in the ro
Blender will package the plugin according to the instructions in `blender_manifest.toml` with the following files excluded:
* `bbp_ng/raw_icons`: raw thumbnail folder.
* `bbp_ng/raw_jsons`: raw JSON folder.
* `bbp_ng/tools`: tools for compiling.
* `bbp_ng/.style.yapf`: code style description file.
* `bbp_ng/.gitignore`: gitignore
* `bbp_ng/icons/.gitkeep`: folder placeholder
* `bbp_ng/jsons/.gitkeep`: folder placeholder
* `__pycache__/`Python cache.
* `.style.yapf`code style description file.
* `.gitignore`gitignore
* `.gitkeep`folder placeholder
* `.md`documentation
## Generating Help Documentation

View File

@@ -19,7 +19,9 @@ The BBP plugin currently has 2 settings to configure.
Please fill in the `Texture` directory of Ballance, from which the plugin will use the external texture files (i.e. the ones Ballance originally came with). Click on the folder button on the right to browse the folders and select it.
This is crucial for BBP to work properly, and only if it is filled out correctly will BBP not make errors during operation.
This option is almost mandatory. If you don't fill it, various core functions like Virtools file import export, BME creation, rail creation, etc. will not be available (button is in grey color).
This is crucial for BBP to work properly, and only if it is filled correctly will BBP not make errors during operation.
### No Component Collection

View File

@@ -1,8 +1,5 @@
# Import and Export Virtools Document
!!! info "Not latest version"
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
!!! warning "This is experimental content"
Native importing and exporting of Virtools documents is experimental content for the BBP plugin, it may have many problems, see the [Report Issue](./report-bugs.md) section to learn more. When problems are encountered, please report them. the authors of the BBP plugin are not responsible for any consequences resulting from problems with the BBP plugin.
@@ -12,7 +9,7 @@ Virtools files can be imported by clicking `File - Import - Virtools File`. Impo
### Conflict Options
The Conflict Options section indicates what to do when the importer encounters duplicate object names. There are 4 levels, for Object, Mesh, Material and Texture. There are 2 ways to handle it: Rename and Use Current. When Rename is selected and a duplicate name is encountered, a suffix will be added to the name to make it unique. By choosing Use Current, the import of the item from the file will be ignored and the item with the same name will be used instead, which already exists in the Blender document.
The Conflict Options section indicates what to do when the importer encounters duplicate object names. There are 5 levels, for Object, Light, Mesh, Material and Texture. There are 2 ways to handle it: Rename and Use Current. When Rename is selected and a duplicate name is encountered, a suffix will be added to the name to make it unique. By choosing Use Current, the import of the item from the file will be ignored and the item with the same name will be used instead, which already exists in the Blender document.
!!! info "Differences from Virtools conflict resolution"
Compared to the conflict resolution dialog in Virtools, the conflict resolution options provided by the BBP plugin do not support replacement, and the granularity is not fine-tuned to individual instances, but only for an entire type. So you can't set a different conflict resolution for each instance of a conflict individually. However, this setting is sufficient for most scenarios.
@@ -21,7 +18,7 @@ The default values for the options in the Conflict Options section are the solut
### Virtools Params
It is well known that Virtools uses a system-based multi-byte character encoding to process documents, and is therefore prone to what is known as garbling; Blender itself does not suffer from garbling, however, if we do not read a Virtools document with the correct encoding, the characters stored in it may still appear garbled when the Virtools document is imported into Blender. The Encodings property in the Virtools Params section specifies the encodings for reading Virtools documents. Multiple encodings can be specified, separated by a `;` (semicolon). Some common encodings are listed below:
It is well known that Virtools uses a system-based multi-byte character encoding to process documents, and is therefore prone to what is known as garbling; Blender itself does not suffer from garbling, however, if we do not read a Virtools document with the correct encoding, the characters stored in it may still appear garbled when the Virtools document is imported into Blender. The Encodings property in the Virtools Params section specifies the encodings for reading Virtools documents. Multiple encodings may be specified, with the encodings higher up in the list being prioritized for use. The next level of encoding will only be utilized when decoding with the current level fails. Some common encodings are listed below:
* cp1252: Western European encoding used by Ballance.
* gbk: The default encoding for Chinese Windows system.
@@ -42,7 +39,12 @@ Virtools files can be exported by clicking `File - Export - Virtools File`. Clic
### Export Target
The Export Target section is used to determine which objects you need to export to a Virtools document. You can choose to export a collection or an object and select the corresponding collection or object below. Note that selecting a collection will export the objects in the internal collection as well, i.e. exporting nested collections is supported.
The Export Target section is used to determine which objects you need to export to a Virtools document. You can choose 1 of 4 options:
* Object: Export single object. Select an object in following input box.
* Collection: Export single collection. **This is the most commonly used option.** Select a collection in following input box. Note that selecting a collection will export the objects in the internal collection as well, i.e. exporting nested collections is supported.
* Selected Object: Export selected objects. Select the objects to be exported before enter this dialog.
* All Objects: Export all objects inside this document. This option should be used with caution. Because it brutely iterate the list of document objects to export, and it is likely to export many objects you don't need.
### Virtools Params
@@ -59,3 +61,11 @@ The Ballance Params section contains parameters that optimize the export process
Successive Sector is an option to work around a bug that occurs when exporting groups of sectors. For some reason, if there are no elements in a sector (actually, no objects are grouped in a sector group), the export plugin thinks that the sector group doesn't exist and misses the export. And since Ballance determines the final sector, i.e. the sector where the spaceships appears, by incrementing the number of sectors from 1 to the last sector group that exists, the combination of the two causes Ballance to incorrectly determine the number of sectors in the map, and thus display the spaceships in the wrong sector, which is an export bug. when this option is checked, the exported document will be pre-defined according to the number of sectors specified in the Ballance Map information in the current Blender file. When this option is checked, the exported document will pre-create all of the sectors according to the number of sectors specified in the Ballance map information in the current Blender file before exporting, so that you don't miss creating some sector groups, and the spaceships will be displayed in the correct sectors.
This option is usually checked when exporting playable maps, if you just want to export some models then you need to turn this option off, otherwise it will create a lot of useless sector groups in the final file.
## Can't Import or Export
In general, after you have properly installed and configured the plugin according to the previously introduced steps, you should theoretically be able to use the import and export functions for Virtools documents. However, unexpected issues may arise. The inability to import or export refers to the buttons for importing and exporting Virtools documents being greyed out and unclickable. You will need to follow the steps outlined below for checking. If you are referring to an error occurring during the import and export process, please refer to the warning information at the top of this page.
First, check whether you have correctly configured the `External Texture Folder` setting of the plugin in the [Configure Plugin](configure-plugin.md) section. If you have not configured it, you will naturally be unable to import or export Virtools files. This is because importing and exporting Virtools files relies on the original Ballance texture data. Please configure this setting carefully according to the tutorial.
If you are certain that you have set the correct texture path, you may try clicking the menu `Window - Toggle System Console` to open the console. There may be some relevant error messages outputted there, such as a failure to load the underlying BMap library, etc. The failure to load the underlying BMap library typically only occurs on machines with rare architectures, such as Windows systems using Snapdragon processors, as the plugin we packaged does not include the Virtools file underlying reading library BMap that supports these platforms. In this situation, you have two options: one is to report this to the developers and wait for support, and the other is to compile the library on yourself (this is only recommended for those with extensive computer knowledge).

View File

@@ -3,6 +3,16 @@
!!! info "May be Outdated"
This document is translated from other languages and may not always be up to date.
<!---
!!! info "Not latest version"
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
--->
<!---
!!! info "No Translation"
This page has not been translated. Please see source page of this page.
--->
Welcome to the Ballance Blender Plugin, the user manual for the free and open source Ballance map creation suite.
Ballance Blender Plugin (aka BBP) is a plugin that focuses on the creation of Ballance custom maps. It offers a wide range of features that can be used to create Ballance maps by anyone from novice to experienced mappers. BBP provides the ability to import and export formats used in Ballance maps, and a series of convenient features tailored to the creation of Ballance maps: for novice mappers, you can quickly assemble a map with prefabricated road blocks, and for the experienced mappers, the mapping tools needed to build complex structures are also provided.

View File

@@ -2,7 +2,7 @@
## Determining the Version
The principle of BBP's Blender support is to support the latest **LTS** version, and to spend some time migrating the plugin after the latest LTS version is released. The current plugin version **4.0** is based on Blender version **4.2.x**.
The principle of BBP's Blender support is to support the latest **LTS** version, and to spend some time migrating the plugin after the latest LTS version is released. The current plugin version **4.3** is based on Blender version **4.5.x**.
Theoretically, BBP will work fine on other versions of Blender if no major changes have been made. For example you can try to run BBP plugin based on Blender 3.6 LTS on Blender 4.0. However, the developers of BBP do not deal with bugs that only appear in non-LTS versions. before installing the plugin, please select the appropriate version.

View File

@@ -14,9 +14,11 @@ Legacy alignment supports aligning multiple objects to a single object by first
In the panel, `Align Axis` specifies the axis you want to align to, you can multi-select here to specify more than one axis, without specifying any axis you will not be able to do the alignment operation, and thus you will not be able to click the `Apply` button.
`Current Object` is the alignment reference object, which is the active object in the scene, usually the last object you selected. This option specifies what value you need to reference for alignment, with `Min` (minimum value on axis), `Center (Bounding Box)` (center of the bounding box), `Center (Axis)` (origin of the object), and `Max` (maximum value on axis) available. These options are consistent with the alignment options in 3ds Max.
`Current Object` indicates which instance was picked as the alignment reference. You may choose an active object in the scene, typically the last object you selected, or the 3D cursor. It is important to note that if you select active object mode, the active object will be excluded from the alignment operation and will not be moved, as the reference object is immovable. Conversely, if you select the 3D cursor, the active object will be included within the scope of the alignment operation.
The `Target Objects` are the objects that are being aligned, there may be many of them, in this option it is also specified what values you need to refer to them for alignment. The options have the same meaning as `Current Object`.
`Current Object Align Mode` is the alignment mode for aligning to a reference object, which only appears when you select the active object in `Current Object`. Since the 3D cursor is merely a point, while objects occupy a certain space, we need to select a point in this space according to a specific pattern (described later) to be used for subsequent alignment operations. In this option, you specify what value you need to align to, with available selections including `Min` (minimum value on axis), `Center (Bounding Box)` (center of the bounding box), `Center (Axis)` (origin of the object), and `Max` (maximum value on axis). These options are consistent with the alignment options in 3ds Max.
The `Target Objects Align Mode` are the objects that are being aligned, there may be many of them, in this option it is also specified what values you need to refer to them for alignment. The options have the same meaning as `Current Object`.
The `Apply` button, when clicked, will press the current page's configuration into the operation stack and reset the settings above, allowing you to start a new round of alignment operations without having to perform a legacy alignment again. The number of operations in the stack is shown below the `Apply` button.

View File

@@ -1,8 +1,5 @@
# Add Rail
!!! info "Not latest version"
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
In the 3D view, click `Add - Rails` to expand the Add Rails menu. The menu is shown on the left side of the image below.
![](../imgs/rail-adder.png)
@@ -63,6 +60,8 @@ The first thing you need to do with an arc rail is to specify the Angle and Radi
Steps of the arc rail, the number of steps indicates the number of segments of the arc rail, the larger the number, the smoother the arc rail looks, relatively, the vertices will be more, the storage space and rendering requirements are also higher, so you need to choose a reasonable value.
The Flip option for arc rail allows you to flip (in other word, mirror) the generated structure along a specified axis, and the flip options available on all three axes can accommodate the generation of all possible arc rail structures.
Arc rails also support double-rail mono-rail selection, you can create mono-rail arc rails and double-rail arc rails. The capping attribute is also supported.
### Spiral Rail
@@ -73,7 +72,7 @@ Spiral rails have an Iterations property, which indicates how many times the rai
The spiral rail also needs to set the Steps property, which has the same meaning as the arc rail. However, it should be noted that the number of steps refers to the number of steps in each iteration, not the overall number of steps. Therefore, when adjusting the iteration attribute, you do not need to change the Steps attribute again.
Side Spiral Rail also has a capping property.
Side Spiral Rail also has capping and flip property.
### Side Spiral Rail
@@ -82,3 +81,9 @@ Side Spiral Rail, similar to spiral rail, but the ball is rolled along the side,
Side Spiral Rail does not have a Screw property, because Side Spiral Rail is designed so that adjacent spins share a common edge, so the screw is fixed.
The Radius, Iterations and Steps attributes in the Side Spiral Rail settings have the same meaning as the spiral rail. Side Spiral Rail also have a capping attribute.
Spiral Rail also has capping and flip property.
## Extra Transform
When adding straight rails and curved rails, you can always set a property known as the Extra Transform. Its effect is consistent with the Extra Transform field in adding floors, both serving the purpose of visual edit.

View File

@@ -1,8 +1,5 @@
# Report Issue
!!! info "Not latest version"
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
## What Can Go Wrong
BBP is not perfect, and since BBP's Virtools file import/export module is written in C++, BBP is more prone to errors than other plugins, and the consequences of errors can be more serious (including but not limited to memory leaks, accidental deletion of user files, etc.).
@@ -15,7 +12,7 @@ In Blender, you will observe if the plugin execution goes wrong:
## What Part Went Wrong
For the BBP plugin, if you observe something like `BMap operation failed` in the Python exception output, or the `IronPad.log` file in the `<Plugin-Install-Location>/PyBMap` folder, it means that The BBP plugin's BMap section, written in C++, is in error, and **you need to immediately save your current Blender document and exit Blender**. Because the plugin is in an abnormal state at this point, you should not continue any operations.
For the BBP plugin, if you observe something like `BMap operation failed` in the Python exception output, or the `blender.exe.<xxx>.log` file (`<xxx>` is a string of numbers) in the `%LOCALAPPDATA%/CrashDumps` folder (`%LOCALAPPDATA%` is a Windows environment variable. If you are not familiar with its purpose, you can simply copy and paste this address directly into the Windows Explorer, which will automatically navigate you to the correct location), it means that The BBP plugin's BMap section, written in C++, is in error, and **you need to immediately save your current Blender document and exit Blender**. Because the plugin is in an abnormal state at this point, you should not continue any operations.
If there is no such thing as the above, then this is just a normal Python code execution error and you don't need to worry too much about it, but the error is still fatal and it is recommended to exit Blender and report the error after doing all the necessary operations.
@@ -29,4 +26,4 @@ If you can't do that and you have proper way to contact with plugin author, then
First of all you need to describe in detail how you raise this error and what are the results of this error. If you can upload the documentation that led to the error, please try to do so (if it's not convenient to post it publicly, you can send it to the author through a private way such as email).
You also need to provide the Python stack report output in the Blender console (use `Window - Toggle System Console` to open the console). If your error is an error in the BMap section, you also need to provide the `IronPad.log` and `IronPad.dmp` files in the `<Plugin-Install-Location>/PyBMap` folder to make it easier for developers to locate the error.
You also need to provide the Python stack report output in the Blender console (use `Window - Toggle System Console` to open the console). If your error is an error in the BMap section, you also need to provide the error log file `blender.exe.<xxx>.log` (where `<xxx>` represents a string of numbers) and the memory dump file `blender.exe.<xxx>.dmp` generated by the BMap module. These files can also be found in the `%LOCALAPPDATA%/CrashDumps` folder.

View File

@@ -1,12 +1,30 @@
# Technical Information
## Standards and Protocol Documentation
* BM File Specification: https://github.com/yyc12345/gist/blob/master/BMFileSpec/BMSpec_ZH.md
* Mapping toolchain standards and format of files in the `meshes' folder: https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md
* Format of the JSON file for BMERevenge: https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_v2.0_ZH.md
## Development Auxiliary Package
This plugin works with the `fake-bpy-module` module to implement type hinting to speed up development. Use the following command to install Blender's type hinting library.
* Blender 3.6: `pip install fake-bpy-module-latest==20230627`
* Blender 4.2: `pip install fake-bpy-module-latest==20240716`
* Blender 4.5: `pip install fake-bpy-module-latest==20250604`
The main reason for doing this is that `fake-bpy-module` doesn't release an official package for the given Blender version, so I had to install it by choosing the daily build closest to the release time of the corresponding Blender version.
The primary reason for this is that the `fake-bpy-module` has not timely released packages suitable for the specified Blender version. Therefore, I can only install it by selecting the daily build version that is closest to the date of the corresponding Blender version leaving from the `main` branch (as daily builds only compile from the `main` branch).
!!! question "Why not use Blender's official bpy module?"
Blender provides an official package named `bpy` on PyPI, but we will not adopt it as our development auxiliary package. This is because it basically repackages Blender into a module (which basically means you are downloading Blender again), allowing you to manipulate Blender through Python. This is contrary to our purpose of using a package that only provides type hints to assist in plugin development.
## Version Rule
The version number format of BBP follows [Semantic Versioning](https://semver.org), but with slight differences:
* The major version number is only increased when the entire plugin is reconstructed.
* The minor version number is used for regular updates.
* The patch version number is incremented when there is no modification for any functionalities. For example, version 4.2.1 only includes updates for macOS Blender without changing any features.
Before the formal release of BBP, there are typically three phased versions: Alpha version, Beta version, and RC version. The Alpha version focuses on functional updates, used to verify whether newly added or modified features are functioning correctly, and does not include documentation or translations. The Beta version is focus on plugin documentation, while the RC version concentrates on plugin translations. However, these three versions do not always exist; if the update content is minimal, some versions may be skipped, or a direct release may occur.

View File

@@ -1,8 +1,5 @@
# Virtools Properties
!!! info "Not latest version"
This translated page is not the latest version because the modification of source page. Please see source page of the latest version.
## Virtools Group
The BBP plugin adds a new property to every Blender object, called Virtools Group. has the same functionality as Group in Virtools. Select an object and the `Virtools Group` panel can be found in the `Object` properties panel.
@@ -13,6 +10,8 @@ In the `Virtools Group` panel, you can click Add to group objects. After clickin
BBP also provides access to Virtools groups in Blender's other menus, see [Group Operation](./group-operations.md).
It is important to note that the Virtools group only works on mesh objects. When you open the Virtools group panel on other objects, you will see a warning message in the panel indicating that the Virtools group is invalid for that object. Although the Virtools group data set on non-mesh objects will be recognized and stored by Blender, it will not be saved to the Virtools file when exporting.
## Virtools Material
The plugin adds a new property to every Blender material, called Virtools Material, which bridges the gap between Virtools materials and Blender materials. Go to the `Material` properties panel and select a material to find the `Virtools Material` panel.
@@ -59,4 +58,12 @@ The BBP plugin adds a new property to all Blender meshes called Virtools Mesh. g
![](../imgs/virtools-mesh.png)
The Virtools Mesh is currently only used as a compatibility feature. It has only one property, Lit Mode, that can be set. Most early maps had black floor issue because they didn't know how to set the material correctly, so the Lit Mode was often set to Prelit to get the floor to show up properly. This attribute exists for compatibility with this compromise and the user usually does not need to set this option.
The Virtools Mesh is currently only used as a compatibility feature. It has only one property, Lit Mode, that can be set. Most early maps had black floor issue because they didn't know how to set the material correctly, so the Lit Mode was often set to Prelit to get the floor to show up properly. This attribute exists for compatibility with this compromise and the user usually does not need to set this option.
## Virtools Light
The BBP plugin adds a new property called Virtools Light to all Blender lights. You can find the Virtools Light panel by navigating to the Data properties panel.
![](../imgs/virtools-light.png)
Similar to the materials in Virtools, the lighting system in Virtools differs significantly from that in Blender. The Virtools Light acts as a bridge, accurately reflecting the settings of Virtools, allowing for seamless storage within Blender files and providing necessary data during import and export. Additionally, this panel includes an Apply button to apply the Virtools lighting settings to Blender's lighting.

View File

@@ -1,4 +1,37 @@
# ZZQ Features
!!! info "No Translation"
This page has not been translated. Please see source page of this page.
This plugin received many suggestions from ZZQ during its development process, resulting in a rather disordered set of features, which are therefore described together on a separate page.
## Snoop Group then to Mesh
Snoop Group then to Mesh, fully referred to as: snoop and copy the Virtools Group infomations into corresponding curve object and convert it into mesh object. You can select certain objects and then right-click to find this function in the context menu of the object.
This function, as its name, converts the selected object into a mesh. If the selected object is a curve and there is a beveled object set, the grouping information of the beveled object will be assigned to the current curve (overriding the curve's current grouping settings). If the selected object is not a curve, or if it is a curve but lacks a beveled object, then this function is basically the same as executing a conversion to mesh. This function is extremely useful in loft modeling, as it only requires the correct grouping of the section object, after which using this function to convert the curve to a mesh will ensure that the grouping of the lofted object is correct.
## Game Camera
Many map makers often lack a clear concept of the size of their maps when creating them, which can easily lead to the production of maps that are either too large or too small. The in-game camera function of the menu item `Ballance - Game Camera` offers a way to preview the map from the perspective of the in-game camera within Blender, allowing map makers to better grasp the dimensions of the map.
In order to use this feature, there must first be a camera in your scene, set as the active camera of the scene (when no camera is present, a newly added camera through the add menu will be automatically set). Additionally, you will need an active object, which cannot be the same as this camera.
!!! question "Why is it necessary to have active object?"
Due to the limitations of the Blender plugin, an active object is required since the in-game camera functionality supports targeting either the 3D cursor **or an active object**.
Typically, the situation where there are no active objects available only occurs in a blank Blender document, which consequently renders the functionality unavailable. In a custom map, there is no any lack of objects which can become active objects; simply clicking on any object is enough (when wishing to use the 3D cursor as a target).
After clicking this function, the view will automatically switch to the perspective of the active camera, facilitating your preview. You can adjust the relevant settings in the panel located at the bottom left.
![](../imgs/game-camera.png)
First, select the target, which essentially means choosing the position of the player's ball. If 3D cursor is selected, it is treated as the origin of player's ball. If active object is selected, the player's ball is placed at the origin of the active object.
!!! info "The position of the player's ball is not that straightforward."
Regardless of whether one chooses 3D cursor or active object, if no extremely fine adjustments are made, the preview results may have slight differences compared with in-game result.
When you use the 3D cursor mode and simply snap it to the ground, it does not represent the position of player ball. Since the radius of player ball is 2, you actually need to move 2 units in +Z direction to achieve the exact same perspective as in the game. However, this operation is too complex, and not performing this action does not cause a significant difference in the preview effect.
When using active object mode, it is important to note that the origin of the object corresponds to the position of the dot displayed in viewport when the object is selected. For most objects, this may not be what you desire, as they may be located inside or outside the object, and are not always positioned on the surface of the object or at the center of a particular face. To address this situation, I recommend using the 3D cursor as a target and, by entering edit mode, utilizing snapping and the `Shift + S` menu to place the cursor in the correct position.
Next is the selection of the camera's rotation angles. We offer 8 common used in-game preset angles, corresponding to 4 each for 90 degrees camera and 45 degrees camera. Furthermore, if these preset angles do not meet your requirements, you can also set custom angles.
Finally, there is the selection of camera perspectives, which can be divided into 3 types: Ordinary (standard perspective), Lift (perspective activated by holding the spacebar), and Easter Egg (special Easter Egg perspective).

View File

@@ -37,7 +37,7 @@
在BME配置对话框的底部您总是可以找到一个被称为额外变换的区域。在这个区域中有额外移动和额外旋转两个选项可以配置。这些字段主要是为了可视化服务的。
在说明这些字段的实际作用前你需要知道BME创建的路面总是以当前3D游标的位置进行创建的即3D游标在哪里新创建的路面就在哪里。这主要是为了可视化来考虑到用户可以先将3D游标移动到想要添加路面的位置再添加对应的BME结构便可以在调整参数的时候即时预览到结果。有时候你的3D游标的位置不是那么的准确又或者最终生成的结构需要一定的旋转才是你期望的此时就可以利用额外变换字段为最终生成的结构增加相对于3D游标的额外的移动和旋转使之处于正确的位置这样就可以在调整参数的时候实时预览到结果了。
在说明这些字段的实际作用前你需要知道BME创建的路面总是以当前3D游标的位置进行创建的即3D游标在哪里新创建的路面就在哪里。这主要是为了可视化来考虑到用户可以先将3D游标移动到想要添加路面的位置再添加对应的BME结构便可以在调整参数的时候即时预览到结果。有时候你的3D游标的位置不是那么的准确又或者最终生成的结构需要一定的旋转才是你期望的此时就可以利用额外变换字段为最终生成的结构增加相对于3D游标的额外的移动和旋转使之处于正确的位置这样就可以在调整参数的时候预览到正确的结果了。
## 小贴士

View File

@@ -4,13 +4,13 @@
## 编译LibCmo与BMap
BBP的Virtools文件原生导入导出功能依赖BMap以及其Python绑定PyBMap来实现。为了分发插件我们需要首先编译BMap及其前置LibCmo。而在编译前你需要先确认你需要的BMap版本。因为BBP并不总是使用最新的BMap例如你正在编译一个旧版的BBP其显然不可能依赖最新的BMap。BMap也在不断升级中其提供的功能也在不断变化不同版本的BMap是不兼容的。BBP通常会在发布时写明其所用的BMap版本如果BBP没有指出你可能需要寻找与BBP发布时最近的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。pybmap是随LibCmo一起提供的。请按照pybmap的手册将编译得到的二进制BMap库pybmap结合在一起。即完成pybmap配置。
然后我们需要将配置好的PyBMap拷贝到本项目的根目录下即可完成此步即存在`bbp_ng/PyBMap`文件夹,为配置好的PyBMap
然后我们需要将配置好的pybmap拷贝到本项目的根目录下即可完成此步即存在`bbp_ng/pybmap`文件夹,为配置好的pybmap
## 生成资源
@@ -69,8 +69,8 @@ Blender对于插件的多语言支持不尽如人意且BBP的设计比较特
如果未来BBP需要支持更多语言你需要从POT文件为新语言创建其对应的PO翻译文件。你可以通过以下方式之一来创建它们。
1. 通过使用Poedit等软件打开POT文件选择创建新的翻译再进行保存来创建。
1. 通过诸如`msginit -i blender.pot -o zh_HANS.po -l zh_CN.utf8`的命令来创建新语言的PO翻译文件。
* 通过使用Poedit等软件打开POT文件选择创建新的翻译再进行保存来创建。
* 通过诸如`msginit -i blender.pot -o zh_HANS.po -l zh_CN.utf8`的命令来创建新语言的PO翻译文件。
创建的方式多种多样唯一需要注意的是你需要按下表所示设定文件名文件名错误Blender会拒绝接受和区域名称使用`msginit`时会用到目的是确保是UTF8格式编码的
@@ -82,8 +82,8 @@ Blender对于插件的多语言支持不尽如人意且BBP的设计比较特
创建新的语言翻译并不常见,更为常见的操作是根据翻译模板,对现有语言翻译文件进行更新。你可以通过以下方式之一来更新它们
1. 通过Poedit等软件打开PO文件再选择从POT文件更新。
1. 通过诸如`msgmerge -U zh-HANS.po blender.pot --backup=none`的命令来更新。
* 通过Poedit等软件打开PO文件再选择从POT文件更新。
* 通过诸如`msgmerge -U zh-HANS.po blender.pot --backup=none`的命令来更新。
### 进行翻译
@@ -108,14 +108,11 @@ PO格式的翻译并不能被Blender识别因此在翻译完成后你还
Blender会根据`blender_manifest.toml`的指示,在排除下列文件的情况下将插件打包:
* `bbp_ng/i18n`:翻译文件夹。
* `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`:文件夹占位符
* `__pycache__/`Python缓存
* `.style.yapf`:代码风格描述文件
* `.gitignore`gitignore
* `.gitkeep`:文件夹占位符
* `.md`:文档
## 生成帮助文档

View File

@@ -9,7 +9,7 @@
### 冲突解决选项
Conflict Options冲突解决选项章节指示了当导入器遇到物体名称重复的情况时该如何处理。分为4个等级分别针对Object物体Light灯光Mesh网格Material材质Texture贴图。处理方式则有2种Rename重命名和Use Current使用当前。选择重命名后当遇到重复名称时将会自动为其添加名称后缀使其名称唯一化。而选择使用当前则会忽略从文件中导入此项转而使用Blender文档中已经存在的同名的项目。
Conflict Options冲突解决选项章节指示了当导入器遇到物体名称重复的情况时该如何处理。分为5个等级分别针对Object物体Light灯光Mesh网格Material材质Texture贴图。处理方式则有2种Rename重命名和Use Current使用当前。选择重命名后当遇到重复名称时将会自动为其添加名称后缀使其名称唯一化。而选择使用当前则会忽略从文件中导入此项转而使用Blender文档中已经存在的同名的项目。
!!! info "与Virtools冲突解决的不同"
相比较于在Virtools的冲突解决对话框BBP插件提供的冲突解决选项不支持替换功能同时其粒度也不支持精细到单个实例只能针对一整个类型进行设定。因此你无法单独为每一个冲突的实例设置不同的冲突解决方案。但目前这种设置已经能满足绝大对数的使用场景了。
@@ -66,6 +66,6 @@ Successive Sector小节连续是一个解决导出小节组时出现的Bug
通常而言在你正确按照之前介绍的步骤安装和配置插件后你理论上就可以使用Virtools文档的导入导出功能了。但难免有意外发生。这里说的无法导入导出指的是导入和导出Virtools文档的按钮是灰色的不可点击的。你需要按照下述步骤检查。如果你所指的是在导入导出过程中出错了请参考本页页首的警告信息。
首先检查你是否已经在[配置插件](configure-plugin.md)章节正确配置了插件的`External Texture Folder`设置项。如果你没有配置则自然无法导入导出Virtools文件。因为导入导出Virtools文件需要依赖Ballance原版关卡数据。请按教程认真配置这一设置项。
首先检查你是否已经在[配置插件](configure-plugin.md)章节正确配置了插件的`External Texture Folder`设置项。如果你没有配置则自然无法导入导出Virtools文件。因为导入导出Virtools文件需要依赖Ballance原版贴图数据。请按教程认真配置这一设置项。
如果你确定你已经设置了正确的贴图路径,你可以尝试点击菜单`Window - Toggle System Console`来打开控制台。在控制台中可能会有一些相关的错误信息输出在其中例如加载底层BMap库时失败等。加载底层BMap库失败通常只发生在一些罕见架构的机器上例如使用Snapdragon处理器的Windows系统等因为我们打包的插件中并未包含支持这些平台的Virtools文件底层读取库BMap。在面对这种情况时你有两个选择一是汇报给开发者等待开发者主动支持二是自行编译该库仅建议有丰富计算机知识的人这样做

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-26 21:47+0800\n"
"POT-Creation-Date: 2025-09-04 09:55+0800\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -1648,6 +1648,7 @@ msgid "Preset Rotation Angle"
msgstr ""
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'1'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg0
msgctxt "BBP_OT_game_camera/property"
msgid "0 Degree"
msgstr ""
@@ -1657,6 +1658,7 @@ msgid "0 degree"
msgstr ""
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'2'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg45
msgctxt "BBP_OT_game_camera/property"
msgid "45 Degree"
msgstr ""
@@ -1666,6 +1668,7 @@ msgid "45 degree"
msgstr ""
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'3'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg90
msgctxt "BBP_OT_game_camera/property"
msgid "90 Degree"
msgstr ""
@@ -1675,6 +1678,7 @@ msgid "90 degree"
msgstr ""
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'4'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg135
msgctxt "BBP_OT_game_camera/property"
msgid "135 Degree"
msgstr ""
@@ -1684,6 +1688,7 @@ msgid "135 degree"
msgstr ""
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'5'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg180
msgctxt "BBP_OT_game_camera/property"
msgid "180 Degree"
msgstr ""
@@ -1693,6 +1698,7 @@ msgid "180 degree"
msgstr ""
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'6'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg225
msgctxt "BBP_OT_game_camera/property"
msgid "225 Degree"
msgstr ""
@@ -1702,6 +1708,7 @@ msgid "225 degree"
msgstr ""
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'7'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg270
msgctxt "BBP_OT_game_camera/property"
msgid "270 Degree"
msgstr ""
@@ -1711,6 +1718,7 @@ msgid "270 degree"
msgstr ""
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'8'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg315
msgctxt "BBP_OT_game_camera/property"
msgid "315 Degree"
msgstr ""
@@ -3708,17 +3716,17 @@ msgctxt "BBP_OT_fix_all_materials/draw"
msgid "Fix {0}/{1} materials."
msgstr ""
#: extensions/user_default/bbp_ng/OP_OBJECT_game_view.py:248
#: extensions/user_default/bbp_ng/OP_OBJECT_game_view.py:305
msgctxt "BBP_OT_game_camera/draw"
msgid "Target"
msgstr ""
#: extensions/user_default/bbp_ng/OP_OBJECT_game_view.py:253
#: extensions/user_default/bbp_ng/OP_OBJECT_game_view.py:310
msgctxt "BBP_OT_game_camera/draw"
msgid "Rotation"
msgstr ""
#: extensions/user_default/bbp_ng/OP_OBJECT_game_view.py:264
#: extensions/user_default/bbp_ng/OP_OBJECT_game_view.py:333
msgctxt "BBP_OT_game_camera/draw"
msgid "Perspective"
msgstr ""

View File

@@ -3,7 +3,7 @@ msgid ""
msgstr ""
"Project-Id-Version: Ballance Blender Plugin 4.2.0 (0)\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-08-26 21:47+0800\n"
"POT-Creation-Date: 2025-09-04 09:55+0800\n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
@@ -1667,6 +1667,7 @@ msgid "Preset Rotation Angle"
msgstr "预设旋转角度"
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'1'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg0
msgctxt "BBP_OT_game_camera/property"
msgid "0 Degree"
msgstr "0 度"
@@ -1676,6 +1677,7 @@ msgid "0 degree"
msgstr "0 度"
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'2'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg45
msgctxt "BBP_OT_game_camera/property"
msgid "45 Degree"
msgstr "45 度"
@@ -1685,6 +1687,7 @@ msgid "45 degree"
msgstr "45 度"
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'3'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg90
msgctxt "BBP_OT_game_camera/property"
msgid "90 Degree"
msgstr "90 度"
@@ -1694,6 +1697,7 @@ msgid "90 degree"
msgstr "90 度"
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'4'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg135
msgctxt "BBP_OT_game_camera/property"
msgid "135 Degree"
msgstr "135 度"
@@ -1703,6 +1707,7 @@ msgid "135 degree"
msgstr "135 度"
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'5'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg180
msgctxt "BBP_OT_game_camera/property"
msgid "180 Degree"
msgstr "180 度"
@@ -1712,6 +1717,7 @@ msgid "180 degree"
msgstr "180 度"
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'6'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg225
msgctxt "BBP_OT_game_camera/property"
msgid "225 Degree"
msgstr "225 度"
@@ -1721,6 +1727,7 @@ msgid "225 degree"
msgstr "225 度"
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'7'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg270
msgctxt "BBP_OT_game_camera/property"
msgid "270 Degree"
msgstr "270 度"
@@ -1730,6 +1737,7 @@ msgid "270 degree"
msgstr "270 度"
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle:'8'
#. :src: bpy.types.BBP_OT_game_camera.preset_rotation_angle_deg315
msgctxt "BBP_OT_game_camera/property"
msgid "315 Degree"
msgstr "315 度"
@@ -3742,17 +3750,17 @@ msgctxt "BBP_OT_fix_all_materials/draw"
msgid "Fix {0}/{1} materials."
msgstr "修复 {0}/{1} 材质。"
#: extensions/user_default/bbp_ng/OP_OBJECT_game_view.py:248
#: extensions/user_default/bbp_ng/OP_OBJECT_game_view.py:305
msgctxt "BBP_OT_game_camera/draw"
msgid "Target"
msgstr "目标"
#: extensions/user_default/bbp_ng/OP_OBJECT_game_view.py:253
#: extensions/user_default/bbp_ng/OP_OBJECT_game_view.py:310
msgctxt "BBP_OT_game_camera/draw"
msgid "Rotation"
msgstr "旋转"
#: extensions/user_default/bbp_ng/OP_OBJECT_game_view.py:264
#: extensions/user_default/bbp_ng/OP_OBJECT_game_view.py:333
msgctxt "BBP_OT_game_camera/draw"
msgid "Perspective"
msgstr "视角"
@@ -5187,6 +5195,30 @@ msgctxt "BBP/BME/Proto/floor_wide_x_crossing/[3]"
msgid "Whether has some faces."
msgstr "是否生成某些面。"
#~ msgid "0 Degree"
#~ msgstr "0 度"
#~ msgid "45 Degree"
#~ msgstr "45 度"
#~ msgid "90 Degree"
#~ msgstr "90 度"
#~ msgid "135 Degree"
#~ msgstr "135 度"
#~ msgid "180 Degree"
#~ msgstr "180 度"
#~ msgid "225 Degree"
#~ msgstr "225 度"
#~ msgid "270 Degree"
#~ msgstr "270 度"
#~ msgid "315 Degree"
#~ msgstr "315 度"
#~ msgctxt "BBP_PG_legacy_align_history/property"
#~ msgid "Target Objects (Selected Objects)"
#~ msgstr "目标物体 (选择的物体)"