Compare commits
18 Commits
v4.0-alpha
...
v4.0-alpha
Author | SHA1 | Date | |
---|---|---|---|
427bad4f6b | |||
209d212287 | |||
2271b0a621 | |||
6940428b88 | |||
aa602a7bb8 | |||
8588f097a2 | |||
5d8ffb7e48 | |||
270fddff52 | |||
084e7fbe61 | |||
190be6ec61 | |||
36e925101e | |||
84e7e8380f | |||
c58af8ce48 | |||
07298fd21c | |||
3a1a0fb0f6 | |||
3396947115 | |||
6cf2ab895d | |||
b039dd8b43 |
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
# disable distribution build folder
|
# disable distribution build folder
|
||||||
redist/
|
redist/
|
||||||
|
!redist/.gitkeep
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import bpy
|
import bpy, mathutils
|
||||||
import typing
|
import typing
|
||||||
from . import PROP_preferences
|
from . import PROP_preferences
|
||||||
from . import UTIL_functions, UTIL_bme
|
from . import UTIL_functions, UTIL_bme
|
||||||
@ -43,7 +43,7 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
|||||||
# So these is the solution about generating cache list according to the change of bme struct type.
|
# So these is the solution about generating cache list according to the change of bme struct type.
|
||||||
# First, update function will only set a "outdated" flag for operator which is a pre-registered Blender property.
|
# First, update function will only set a "outdated" flag for operator which is a pre-registered Blender property.
|
||||||
# The "outdated" flags is not showen and not saved.
|
# The "outdated" flags is not showen and not saved.
|
||||||
# Then call a internal cache list update function at the begin of `invoke` and `draw`.
|
# Then call a internal cache list update function at the begin of `invoke`, `execute` and `draw`.
|
||||||
# In this internal cache list updator, check "outdated" flag first, if cache is outdated, update and reset flag.
|
# In this internal cache list updator, check "outdated" flag first, if cache is outdated, update and reset flag.
|
||||||
# Otherwise do nothing.
|
# Otherwise do nothing.
|
||||||
#
|
#
|
||||||
@ -141,11 +141,34 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
|||||||
type = BBP_PG_bme_adder_cfgs,
|
type = BBP_PG_bme_adder_cfgs,
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
|
## Extra transform for good "what you see is what you gotten".
|
||||||
|
# Extra transform will be added after moving this object to cursor.
|
||||||
|
extra_translation: bpy.props.FloatVectorProperty(
|
||||||
|
name = "Extra Translation",
|
||||||
|
description = "The extra translation applied to object after moving to cursor.",
|
||||||
|
size = 3,
|
||||||
|
subtype = 'TRANSLATION',
|
||||||
|
step = 50, # same step as the float entry of BBP_PG_bme_adder_cfgs
|
||||||
|
default = (0.0, 0.0, 0.0)
|
||||||
|
) # type: ignore
|
||||||
|
extra_rotation: bpy.props.FloatVectorProperty(
|
||||||
|
name = "Extra Rotation",
|
||||||
|
description = "The extra rotation applied to object after moving to cursor.",
|
||||||
|
size = 3,
|
||||||
|
subtype = 'EULER',
|
||||||
|
step = 100, # We choosen 100, mean 1. Sync with property window.
|
||||||
|
default = (0.0, 0.0, 0.0)
|
||||||
|
) # type: ignore
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(self, context):
|
def poll(cls, context):
|
||||||
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
|
# reset extra transform to identy
|
||||||
|
self.extra_translation = (0.0, 0.0, 0.0)
|
||||||
|
self.extra_rotation = (0.0, 0.0, 0.0)
|
||||||
|
self.extra_scale = (1.0, 1.0, 1.0)
|
||||||
# create internal list
|
# create internal list
|
||||||
self.bme_struct_cfg_index_cache = []
|
self.bme_struct_cfg_index_cache = []
|
||||||
# trigger default bme struct type updator
|
# trigger default bme struct type updator
|
||||||
@ -156,6 +179,9 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
|||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
|
# call internal updator
|
||||||
|
self.__internal_update_bme_struct_type()
|
||||||
|
|
||||||
# collect cfgs data
|
# collect cfgs data
|
||||||
cfgs: dict[str, typing.Any] = {}
|
cfgs: dict[str, typing.Any] = {}
|
||||||
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
|
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
|
||||||
@ -178,8 +204,14 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
|||||||
cfgs
|
cfgs
|
||||||
)
|
)
|
||||||
|
|
||||||
# move to cursor
|
# add into scene and move to cursor
|
||||||
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
||||||
|
# add extra transform
|
||||||
|
obj.matrix_world = obj.matrix_world @ mathutils.Matrix.LocRotScale(
|
||||||
|
mathutils.Vector(self.extra_translation),
|
||||||
|
mathutils.Euler(self.extra_rotation, 'XYZ'),
|
||||||
|
mathutils.Vector((1.0, 1.0, 1.0)) # no scale
|
||||||
|
)
|
||||||
# select created object
|
# select created object
|
||||||
UTIL_functions.select_certain_objects((obj, ))
|
UTIL_functions.select_certain_objects((obj, ))
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
@ -194,6 +226,7 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
|||||||
layout.prop(self, 'bme_struct_type')
|
layout.prop(self, 'bme_struct_type')
|
||||||
|
|
||||||
# visit cfgs cache list to show cfg
|
# visit cfgs cache list to show cfg
|
||||||
|
layout.label(text = "Prototype Configurations:")
|
||||||
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
|
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
|
||||||
# create box for cfgs
|
# create box for cfgs
|
||||||
box_layout: bpy.types.UILayout = layout.box()
|
box_layout: bpy.types.UILayout = layout.box()
|
||||||
@ -225,12 +258,24 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
|||||||
grids.prop(self.bme_struct_cfgs[cfg_index + 1], 'prop_bool', text = 'Bottom') # bottom
|
grids.prop(self.bme_struct_cfgs[cfg_index + 1], 'prop_bool', text = 'Bottom') # bottom
|
||||||
grids.separator()
|
grids.separator()
|
||||||
|
|
||||||
|
# show extra transform props
|
||||||
|
# forcely order that each one are placed horizontally
|
||||||
|
layout.label(text = "Extra Transform:")
|
||||||
|
# translation
|
||||||
|
layout.label(text = 'Translation')
|
||||||
|
hbox_layout: bpy.types.UILayout = layout.row()
|
||||||
|
hbox_layout.prop(self, 'extra_translation', text = '')
|
||||||
|
# rotation
|
||||||
|
layout.label(text = 'Rotation')
|
||||||
|
hbox_layout = layout.row()
|
||||||
|
hbox_layout.prop(self, 'extra_rotation', text = '')
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def draw_blc_menu(self, layout: bpy.types.UILayout):
|
def draw_blc_menu(cls, layout: bpy.types.UILayout):
|
||||||
for ident in _g_EnumHelper_BmeStructType.get_bme_identifiers():
|
for ident in _g_EnumHelper_BmeStructType.get_bme_identifiers():
|
||||||
# draw operator
|
# draw operator
|
||||||
cop = layout.operator(
|
cop = layout.operator(
|
||||||
self.bl_idname,
|
cls.bl_idname,
|
||||||
text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident),
|
text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident),
|
||||||
icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident)
|
icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident)
|
||||||
)
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import bpy, mathutils
|
import bpy, mathutils
|
||||||
import math, typing
|
import math, typing
|
||||||
from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convension
|
from . import UTIL_functions, UTIL_icons_manager, UTIL_naming_convension
|
||||||
from . import PROP_ballance_element, PROP_virtools_group
|
from . import PROP_ballance_element, PROP_virtools_group, PROP_ballance_map_info
|
||||||
|
|
||||||
#region Param Help Classes
|
#region Param Help Classes
|
||||||
|
|
||||||
@ -128,6 +128,7 @@ class _GeneralComponentCreator():
|
|||||||
"""
|
"""
|
||||||
# get element info first
|
# get element info first
|
||||||
ele_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector)
|
ele_info: UTIL_naming_convension.BallanceObjectInfo = _get_component_info(comp_type, comp_sector)
|
||||||
|
|
||||||
# create blc element context
|
# create blc element context
|
||||||
with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator:
|
with PROP_ballance_element.BallanceElementsHelper(bpy.context.scene) as creator:
|
||||||
# object creation counter
|
# object creation counter
|
||||||
@ -143,6 +144,27 @@ class _GeneralComponentCreator():
|
|||||||
# put into created object list
|
# put into created object list
|
||||||
self.__mObjList.append(obj)
|
self.__mObjList.append(obj)
|
||||||
|
|
||||||
|
# enlarge scene sector field for non-PS (start point) PE (end point) component
|
||||||
|
# read from scene and create var for enlarged sector count
|
||||||
|
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||||
|
enlarged_sector: int
|
||||||
|
# check component type to get enlarged value
|
||||||
|
match(ele_info.mBasicType):
|
||||||
|
case UTIL_naming_convension.BallanceObjectType.COMPONENT:
|
||||||
|
enlarged_sector = comp_sector
|
||||||
|
case UTIL_naming_convension.BallanceObjectType.CHECKPOINT:
|
||||||
|
# checkpoint 1 means that there is sector 2, so we plus 1 for it.
|
||||||
|
enlarged_sector = comp_sector + 1
|
||||||
|
case UTIL_naming_convension.BallanceObjectType.RESETPOINT:
|
||||||
|
enlarged_sector = comp_sector
|
||||||
|
case _:
|
||||||
|
# this component is not a sector based component
|
||||||
|
# so we do not change it (use original value)
|
||||||
|
enlarged_sector = map_info.mSectorCount
|
||||||
|
# enlarge it
|
||||||
|
map_info.mSectorCount = max(map_info.mSectorCount, enlarged_sector)
|
||||||
|
PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info)
|
||||||
|
|
||||||
def finish_component(self) -> None:
|
def finish_component(self) -> None:
|
||||||
"""
|
"""
|
||||||
Finish up component creation.
|
Finish up component creation.
|
||||||
|
@ -33,6 +33,50 @@ c_SideSpiralRailScrew: float = 3.6
|
|||||||
|
|
||||||
#region Operator Helpers
|
#region Operator Helpers
|
||||||
|
|
||||||
|
class SharedExtraTransform():
|
||||||
|
"""
|
||||||
|
This class is served for all rail creation which allow user
|
||||||
|
provide extra transform after moving created rail to cursor.
|
||||||
|
For "what you look is what you gotten" experience, this extra transform is essential.
|
||||||
|
"""
|
||||||
|
|
||||||
|
extra_translation: bpy.props.FloatVectorProperty(
|
||||||
|
name = "Extra Translation",
|
||||||
|
description = "The extra translation applied to object after moving to cursor.",
|
||||||
|
size = 3,
|
||||||
|
subtype = 'TRANSLATION',
|
||||||
|
step = 50, # same step as the float entry of BBP_PG_bme_adder_cfgs
|
||||||
|
default = (0.0, 0.0, 0.0)
|
||||||
|
) # type: ignore
|
||||||
|
extra_rotation: bpy.props.FloatVectorProperty(
|
||||||
|
name = "Extra Rotation",
|
||||||
|
description = "The extra rotation applied to object after moving to cursor.",
|
||||||
|
size = 3,
|
||||||
|
subtype = 'EULER',
|
||||||
|
step = 100, # We choosen 100, mean 1. Sync with property window.
|
||||||
|
default = (0.0, 0.0, 0.0)
|
||||||
|
) # type: ignore
|
||||||
|
|
||||||
|
def draw_extra_transform_input(self, layout: bpy.types.UILayout) -> None:
|
||||||
|
# show extra transform props
|
||||||
|
# forcely order that each one are placed horizontally
|
||||||
|
layout.label(text = "Extra Transform:")
|
||||||
|
# translation
|
||||||
|
layout.label(text = 'Translation')
|
||||||
|
row = layout.row()
|
||||||
|
row.prop(self, 'extra_translation', text = '')
|
||||||
|
# rotation
|
||||||
|
layout.label(text = 'Rotation')
|
||||||
|
row = layout.row()
|
||||||
|
row.prop(self, 'extra_rotation', text = '')
|
||||||
|
|
||||||
|
def general_get_extra_transform(self) -> mathutils.Matrix:
|
||||||
|
return mathutils.Matrix.LocRotScale(
|
||||||
|
mathutils.Vector(self.extra_translation),
|
||||||
|
mathutils.Euler(self.extra_rotation, 'XYZ'),
|
||||||
|
mathutils.Vector((1.0, 1.0, 1.0)) # no scale
|
||||||
|
)
|
||||||
|
|
||||||
class SharedRailSectionInputProperty():
|
class SharedRailSectionInputProperty():
|
||||||
"""
|
"""
|
||||||
This class is served for user to pick the transition type of rail.
|
This class is served for user to pick the transition type of rail.
|
||||||
@ -112,7 +156,7 @@ class SharedScrewRailInputProperty():
|
|||||||
rail_screw_steps: bpy.props.IntProperty(
|
rail_screw_steps: bpy.props.IntProperty(
|
||||||
name = "Steps",
|
name = "Steps",
|
||||||
description = "The segment count per iteration. More segment, more smooth but lower performance.",
|
description = "The segment count per iteration. More segment, more smooth but lower performance.",
|
||||||
default = 16,
|
default = 28,
|
||||||
min = 1,
|
min = 1,
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
@ -147,7 +191,8 @@ class BBP_OT_add_rail_section(SharedRailSectionInputProperty, bpy.types.Operator
|
|||||||
lambda bm: _create_rail_section(
|
lambda bm: _create_rail_section(
|
||||||
bm, self.general_get_is_monorail(),
|
bm, self.general_get_is_monorail(),
|
||||||
c_DefaultRailRadius, c_DefaultRailSpan
|
c_DefaultRailRadius, c_DefaultRailSpan
|
||||||
)
|
),
|
||||||
|
mathutils.Matrix.Identity(4)
|
||||||
)
|
)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -163,7 +208,8 @@ class BBP_OT_add_transition_section(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
_rail_creator_wrapper(
|
_rail_creator_wrapper(
|
||||||
lambda bm: _create_transition_section(bm, c_DefaultRailRadius, c_DefaultRailSpan)
|
lambda bm: _create_transition_section(bm, c_DefaultRailRadius, c_DefaultRailSpan),
|
||||||
|
mathutils.Matrix.Identity(4)
|
||||||
)
|
)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -171,7 +217,7 @@ class BBP_OT_add_transition_section(bpy.types.Operator):
|
|||||||
layout = self.layout
|
layout = self.layout
|
||||||
layout.label(text = 'No Options Available')
|
layout.label(text = 'No Options Available')
|
||||||
|
|
||||||
class BBP_OT_add_straight_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
class BBP_OT_add_straight_rail(SharedExtraTransform, SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
||||||
"""Add Straight Rail"""
|
"""Add Straight Rail"""
|
||||||
bl_idname = "bbp.add_straight_rail"
|
bl_idname = "bbp.add_straight_rail"
|
||||||
bl_label = "Straight Rail"
|
bl_label = "Straight Rail"
|
||||||
@ -184,7 +230,8 @@ class BBP_OT_add_straight_rail(SharedRailSectionInputProperty, SharedRailCapInpu
|
|||||||
self.general_get_is_monorail(), c_DefaultRailRadius, c_DefaultRailSpan,
|
self.general_get_is_monorail(), c_DefaultRailRadius, c_DefaultRailSpan,
|
||||||
self.general_get_rail_length(), 0,
|
self.general_get_rail_length(), 0,
|
||||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
|
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
|
||||||
)
|
),
|
||||||
|
self.general_get_extra_transform()
|
||||||
)
|
)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -196,8 +243,10 @@ class BBP_OT_add_straight_rail(SharedRailSectionInputProperty, SharedRailCapInpu
|
|||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text = 'Rail Cap')
|
layout.label(text = 'Rail Cap')
|
||||||
self.draw_rail_cap_input(layout)
|
self.draw_rail_cap_input(layout)
|
||||||
|
layout.separator()
|
||||||
|
self.draw_extra_transform_input(layout)
|
||||||
|
|
||||||
class BBP_OT_add_transition_rail(SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
class BBP_OT_add_transition_rail(SharedExtraTransform, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
||||||
"""Add Transition Rail"""
|
"""Add Transition Rail"""
|
||||||
bl_idname = "bbp.add_transition_rail"
|
bl_idname = "bbp.add_transition_rail"
|
||||||
bl_label = "Transition Rail"
|
bl_label = "Transition Rail"
|
||||||
@ -210,7 +259,8 @@ class BBP_OT_add_transition_rail(SharedRailCapInputProperty, SharedStraightRailI
|
|||||||
c_DefaultRailRadius, c_DefaultRailSpan,
|
c_DefaultRailRadius, c_DefaultRailSpan,
|
||||||
self.general_get_rail_length(),
|
self.general_get_rail_length(),
|
||||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
|
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
|
||||||
)
|
),
|
||||||
|
self.general_get_extra_transform()
|
||||||
)
|
)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -221,8 +271,10 @@ class BBP_OT_add_transition_rail(SharedRailCapInputProperty, SharedStraightRailI
|
|||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text = 'Rail Cap')
|
layout.label(text = 'Rail Cap')
|
||||||
self.draw_rail_cap_input(layout)
|
self.draw_rail_cap_input(layout)
|
||||||
|
layout.separator()
|
||||||
|
self.draw_extra_transform_input(layout)
|
||||||
|
|
||||||
class BBP_OT_add_side_rail(SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
class BBP_OT_add_side_rail(SharedExtraTransform, SharedRailCapInputProperty, SharedStraightRailInputProperty, bpy.types.Operator):
|
||||||
"""Add Side Rail"""
|
"""Add Side Rail"""
|
||||||
bl_idname = "bbp.add_side_rail"
|
bl_idname = "bbp.add_side_rail"
|
||||||
bl_label = "Side Rail"
|
bl_label = "Side Rail"
|
||||||
@ -246,7 +298,8 @@ class BBP_OT_add_side_rail(SharedRailCapInputProperty, SharedStraightRailInputPr
|
|||||||
self.general_get_rail_length(),
|
self.general_get_rail_length(),
|
||||||
c_NormalSideRailAngle if self.side_rail_type == 'NORMAL' else c_StoneSideRailAngle,
|
c_NormalSideRailAngle if self.side_rail_type == 'NORMAL' else c_StoneSideRailAngle,
|
||||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
|
self.general_get_rail_start_cap(), self.general_get_rail_end_cap()
|
||||||
)
|
),
|
||||||
|
self.general_get_extra_transform()
|
||||||
)
|
)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -258,8 +311,10 @@ class BBP_OT_add_side_rail(SharedRailCapInputProperty, SharedStraightRailInputPr
|
|||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text = 'Rail Cap')
|
layout.label(text = 'Rail Cap')
|
||||||
self.draw_rail_cap_input(layout)
|
self.draw_rail_cap_input(layout)
|
||||||
|
layout.separator()
|
||||||
|
self.draw_extra_transform_input(layout)
|
||||||
|
|
||||||
class BBP_OT_add_arc_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
class BBP_OT_add_arc_rail(SharedExtraTransform, SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
||||||
"""Add Arc Rail"""
|
"""Add Arc Rail"""
|
||||||
bl_idname = "bbp.add_arc_rail"
|
bl_idname = "bbp.add_arc_rail"
|
||||||
bl_label = "Arc Rail"
|
bl_label = "Arc Rail"
|
||||||
@ -281,7 +336,8 @@ class BBP_OT_add_arc_rail(SharedRailSectionInputProperty, SharedRailCapInputProp
|
|||||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
||||||
math.degrees(self.rail_screw_angle), 0, 1, # blender passed value is in radians
|
math.degrees(self.rail_screw_angle), 0, 1, # blender passed value is in radians
|
||||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
|
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
|
||||||
)
|
),
|
||||||
|
self.general_get_extra_transform()
|
||||||
)
|
)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -294,8 +350,10 @@ class BBP_OT_add_arc_rail(SharedRailSectionInputProperty, SharedRailCapInputProp
|
|||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text = 'Rail Cap')
|
layout.label(text = 'Rail Cap')
|
||||||
self.draw_rail_cap_input(layout)
|
self.draw_rail_cap_input(layout)
|
||||||
|
layout.separator()
|
||||||
|
self.draw_extra_transform_input(layout)
|
||||||
|
|
||||||
class BBP_OT_add_spiral_rail(SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
class BBP_OT_add_spiral_rail(SharedExtraTransform, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
||||||
"""Add Spiral Rail"""
|
"""Add Spiral Rail"""
|
||||||
bl_idname = "bbp.add_spiral_rail"
|
bl_idname = "bbp.add_spiral_rail"
|
||||||
bl_label = "Spiral Rail"
|
bl_label = "Spiral Rail"
|
||||||
@ -323,7 +381,8 @@ class BBP_OT_add_spiral_rail(SharedRailCapInputProperty, SharedScrewRailInputPro
|
|||||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
||||||
360, self.rail_screw_screw, self.rail_screw_iterations,
|
360, self.rail_screw_screw, self.rail_screw_iterations,
|
||||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
|
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
|
||||||
)
|
),
|
||||||
|
self.general_get_extra_transform()
|
||||||
)
|
)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -336,8 +395,10 @@ class BBP_OT_add_spiral_rail(SharedRailCapInputProperty, SharedScrewRailInputPro
|
|||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text = 'Rail Cap')
|
layout.label(text = 'Rail Cap')
|
||||||
self.draw_rail_cap_input(layout)
|
self.draw_rail_cap_input(layout)
|
||||||
|
layout.separator()
|
||||||
|
self.draw_extra_transform_input(layout)
|
||||||
|
|
||||||
class BBP_OT_add_side_spiral_rail(SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
class BBP_OT_add_side_spiral_rail(SharedExtraTransform, SharedRailSectionInputProperty, SharedRailCapInputProperty, SharedScrewRailInputProperty, bpy.types.Operator):
|
||||||
"""Add Side Spiral Rail"""
|
"""Add Side Spiral Rail"""
|
||||||
bl_idname = "bbp.add_side_spiral_rail"
|
bl_idname = "bbp.add_side_spiral_rail"
|
||||||
bl_label = "Side Spiral Rail"
|
bl_label = "Side Spiral Rail"
|
||||||
@ -360,7 +421,8 @@ class BBP_OT_add_side_spiral_rail(SharedRailSectionInputProperty, SharedRailCapI
|
|||||||
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
self.general_get_rail_start_cap(), self.general_get_rail_end_cap(),
|
||||||
360, c_SideSpiralRailScrew, self.rail_screw_iterations,
|
360, c_SideSpiralRailScrew, self.rail_screw_iterations,
|
||||||
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
|
self.general_get_rail_screw_steps(), self.general_get_rail_screw_radius()
|
||||||
)
|
),
|
||||||
|
self.general_get_extra_transform()
|
||||||
)
|
)
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -372,6 +434,8 @@ class BBP_OT_add_side_spiral_rail(SharedRailSectionInputProperty, SharedRailCapI
|
|||||||
layout.separator()
|
layout.separator()
|
||||||
layout.label(text = 'Rail Cap')
|
layout.label(text = 'Rail Cap')
|
||||||
self.draw_rail_cap_input(layout)
|
self.draw_rail_cap_input(layout)
|
||||||
|
layout.separator()
|
||||||
|
self.draw_extra_transform_input(layout)
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -460,7 +524,7 @@ def _bmesh_cap(bm: bmesh.types.BMesh, edges: list[bmesh.types.BMEdge]) -> None:
|
|||||||
|
|
||||||
#region Real Rail Creators
|
#region Real Rail Creators
|
||||||
|
|
||||||
def _rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], None]) -> bpy.types.Object:
|
def _rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], None], extra_transform: mathutils.Matrix) -> bpy.types.Object:
|
||||||
# create mesh first
|
# create mesh first
|
||||||
bm: bmesh.types.BMesh = bmesh.new()
|
bm: bmesh.types.BMesh = bmesh.new()
|
||||||
|
|
||||||
@ -473,8 +537,6 @@ def _rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], No
|
|||||||
bm.free()
|
bm.free()
|
||||||
|
|
||||||
# setup smooth for mesh
|
# setup smooth for mesh
|
||||||
mesh.use_auto_smooth = True
|
|
||||||
mesh.auto_smooth_angle = math.radians(50)
|
|
||||||
mesh.shade_smooth()
|
mesh.shade_smooth()
|
||||||
|
|
||||||
# create object and assoc with it
|
# create object and assoc with it
|
||||||
@ -492,6 +554,8 @@ def _rail_creator_wrapper(fct_poly_cret: typing.Callable[[bmesh.types.BMesh], No
|
|||||||
|
|
||||||
# move to cursor
|
# move to cursor
|
||||||
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
UTIL_functions.add_into_scene_and_move_to_cursor(obj)
|
||||||
|
# add extra transform
|
||||||
|
obj.matrix_world = obj.matrix_world @ extra_transform
|
||||||
# select created object
|
# select created object
|
||||||
UTIL_functions.select_certain_objects((obj, ))
|
UTIL_functions.select_certain_objects((obj, ))
|
||||||
|
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy_extras.wm_utils.progress_report import ProgressReport
|
from bpy_extras.wm_utils.progress_report import ProgressReport
|
||||||
import tempfile, os, typing, re
|
import tempfile, os, typing
|
||||||
from . import PROP_preferences, UTIL_ioport_shared
|
from . import PROP_preferences, UTIL_ioport_shared
|
||||||
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_icons_manager
|
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_icons_manager, UTIL_naming_convension
|
||||||
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture
|
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_ballance_map_info
|
||||||
from .PyBMap import bmap_wrapper as bmap
|
from .PyBMap import bmap_wrapper as bmap
|
||||||
|
|
||||||
# define global tex save opt blender enum prop helper
|
# define global tex save opt blender enum prop helper
|
||||||
@ -20,19 +20,26 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
|
|||||||
description = "Decide how texture saved if texture is specified as Use Global as its Save Options.",
|
description = "Decide how texture saved if texture is specified as Use Global as its Save Options.",
|
||||||
items = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.generate_items(),
|
items = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.generate_items(),
|
||||||
default = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.to_selection(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL)
|
default = _g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.to_selection(UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS.CKTEXTURE_EXTERNAL)
|
||||||
)
|
) # type: ignore
|
||||||
|
|
||||||
use_compress: bpy.props.BoolProperty(
|
use_compress: bpy.props.BoolProperty(
|
||||||
name="Use Compress",
|
name="Use Compress",
|
||||||
|
description = "Whether use ZLib to compress result when saving composition.",
|
||||||
default = True,
|
default = True,
|
||||||
)
|
) # type: ignore
|
||||||
|
|
||||||
compress_level: bpy.props.IntProperty(
|
compress_level: bpy.props.IntProperty(
|
||||||
name = "Compress Level",
|
name = "Compress Level",
|
||||||
description = "The ZLib compress level used by Virtools Engine when saving composition.",
|
description = "The ZLib compress level used by Virtools Engine when saving composition.",
|
||||||
min = 1, max = 9,
|
min = 1, max = 9,
|
||||||
default = 5,
|
default = 5,
|
||||||
)
|
) # type: ignore
|
||||||
|
|
||||||
|
successive_sector: bpy.props.BoolProperty(
|
||||||
|
name="Successive Sector",
|
||||||
|
description = "Whether order exporter to use document specified sector count to make sure sector is successive.",
|
||||||
|
default = True,
|
||||||
|
) # type: ignore
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(self, context):
|
def poll(self, context):
|
||||||
@ -59,6 +66,7 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
|
|||||||
_g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.get_selection(self.texture_save_opt),
|
_g_EnumHelper_CK_TEXTURE_SAVEOPTIONS.get_selection(self.texture_save_opt),
|
||||||
self.use_compress,
|
self.use_compress,
|
||||||
self.compress_level,
|
self.compress_level,
|
||||||
|
self.successive_sector,
|
||||||
objls
|
objls
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -84,6 +92,14 @@ class BBP_OT_export_virtools(bpy.types.Operator, UTIL_file_browser.ExportVirtool
|
|||||||
if self.use_compress:
|
if self.use_compress:
|
||||||
box.prop(self, 'compress_level')
|
box.prop(self, 'compress_level')
|
||||||
|
|
||||||
|
# show sector info to notice user
|
||||||
|
layout.separator()
|
||||||
|
layout.label(text = 'Ballance Params')
|
||||||
|
box = layout.box()
|
||||||
|
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||||
|
box.prop(self, 'successive_sector')
|
||||||
|
box.label(text = f'Map Sectors: {map_info.mSectorCount}')
|
||||||
|
|
||||||
_TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject]
|
_TObj3dPair = tuple[bpy.types.Object, bmap.BM3dObject]
|
||||||
_TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh]
|
_TMeshPair = tuple[bpy.types.Object, bpy.types.Mesh, bmap.BMMesh]
|
||||||
_TMaterialPair = tuple[bpy.types.Material, bmap.BMMaterial]
|
_TMaterialPair = tuple[bpy.types.Material, bmap.BMMaterial]
|
||||||
@ -95,6 +111,7 @@ def _export_virtools(
|
|||||||
texture_save_opt_: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS,
|
texture_save_opt_: UTIL_virtools_types.CK_TEXTURE_SAVEOPTIONS,
|
||||||
use_compress_: bool,
|
use_compress_: bool,
|
||||||
compress_level_: int,
|
compress_level_: int,
|
||||||
|
successive_sector_: bool,
|
||||||
export_objects: tuple[bpy.types.Object, ...]
|
export_objects: tuple[bpy.types.Object, ...]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
@ -114,7 +131,7 @@ def _export_virtools(
|
|||||||
obj3d_crets: tuple[_TObj3dPair, ...] = _prepare_virtools_3dobjects(
|
obj3d_crets: tuple[_TObj3dPair, ...] = _prepare_virtools_3dobjects(
|
||||||
writer, progress, export_objects)
|
writer, progress, export_objects)
|
||||||
# export group and 3dobject by prepared 3dobject
|
# export group and 3dobject by prepared 3dobject
|
||||||
_export_virtools_groups(writer, progress, obj3d_crets)
|
_export_virtools_groups(writer, progress, successive_sector_, obj3d_crets)
|
||||||
mesh_crets: tuple[_TMeshPair, ...] = _export_virtools_3dobjects(
|
mesh_crets: tuple[_TMeshPair, ...] = _export_virtools_3dobjects(
|
||||||
writer, progress, obj3d_crets)
|
writer, progress, obj3d_crets)
|
||||||
# export mesh
|
# export mesh
|
||||||
@ -163,6 +180,7 @@ def _prepare_virtools_3dobjects(
|
|||||||
def _export_virtools_groups(
|
def _export_virtools_groups(
|
||||||
writer: bmap.BMFileWriter,
|
writer: bmap.BMFileWriter,
|
||||||
progress: ProgressReport,
|
progress: ProgressReport,
|
||||||
|
successive_sector: bool,
|
||||||
obj3d_crets: tuple[_TObj3dPair, ...]
|
obj3d_crets: tuple[_TObj3dPair, ...]
|
||||||
) -> None:
|
) -> None:
|
||||||
# create virtools group
|
# create virtools group
|
||||||
@ -170,8 +188,24 @@ def _export_virtools_groups(
|
|||||||
# start saving
|
# start saving
|
||||||
progress.enter_substeps(len(obj3d_crets), "Saving Groups")
|
progress.enter_substeps(len(obj3d_crets), "Saving Groups")
|
||||||
|
|
||||||
# create group exporting helper
|
# create sector group first if user ordered
|
||||||
group_cret_guard: VirtoolsGroupCreationGuard = VirtoolsGroupCreationGuard(writer)
|
# This step is designed for ensure that the created sector group is successive.
|
||||||
|
#
|
||||||
|
# Due to the design of Ballance, Ballance rely on checking the existance of Sector_XX to get how many sectors this map have.
|
||||||
|
# Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash
|
||||||
|
# or be ended at a accident sector.
|
||||||
|
#
|
||||||
|
# So we create all needed sector group in here to make sure exported virtools file can be read by Ballancde correctly.
|
||||||
|
if successive_sector:
|
||||||
|
map_info: PROP_ballance_map_info.RawBallanceMapInfo
|
||||||
|
map_info = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||||
|
for i in range(map_info.mSectorCount):
|
||||||
|
gp_name: str = UTIL_naming_convension.build_name_from_sector_index(i + 1)
|
||||||
|
vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None)
|
||||||
|
if vtgroup is None:
|
||||||
|
vtgroup = writer.create_group()
|
||||||
|
vtgroup.set_name(gp_name)
|
||||||
|
group_cret_map[gp_name] = vtgroup
|
||||||
|
|
||||||
for obj3d, vtobj3d in obj3d_crets:
|
for obj3d, vtobj3d in obj3d_crets:
|
||||||
# open group visitor
|
# open group visitor
|
||||||
@ -180,8 +214,8 @@ def _export_virtools_groups(
|
|||||||
# get group or create new group from guard
|
# get group or create new group from guard
|
||||||
vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None)
|
vtgroup: bmap.BMGroup | None = group_cret_map.get(gp_name, None)
|
||||||
if vtgroup is None:
|
if vtgroup is None:
|
||||||
# note: no need to set name, guard has set it.
|
vtgroup = writer.create_group()
|
||||||
vtgroup = group_cret_guard.create_group(gp_name)
|
vtgroup.set_name(gp_name)
|
||||||
group_cret_map[gp_name] = vtgroup
|
group_cret_map[gp_name] = vtgroup
|
||||||
|
|
||||||
# group this object
|
# group this object
|
||||||
@ -471,73 +505,6 @@ def _save_virtools_document(
|
|||||||
progress.step()
|
progress.step()
|
||||||
progress.leave_substeps()
|
progress.leave_substeps()
|
||||||
|
|
||||||
class VirtoolsGroupCreationGuard():
|
|
||||||
"""
|
|
||||||
This class is designed for ensure that the created sector group is successive.
|
|
||||||
|
|
||||||
Due to the design of Ballance, Ballance rely on checking the existance of Sector_XX to get how many sectors this map have.
|
|
||||||
Thus if there are no component in a sector, it still need to create a empty Sector_XX group, otherwise the game will crash
|
|
||||||
or be ended at a accident sector.
|
|
||||||
|
|
||||||
This class hook the operation of Virtools group creation and check all Sector group creation.
|
|
||||||
Create essential group to make Sector_XX group successive.
|
|
||||||
Thus all group creation in this module should be passed by this class.
|
|
||||||
"""
|
|
||||||
cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __get_group_index(group_name: str) -> int | None:
|
|
||||||
"""
|
|
||||||
Return the sector index of group name if it is. Otherwise None.
|
|
||||||
"""
|
|
||||||
regex_result = VirtoolsGroupCreationGuard.cRegexGroupSector.match(group_name)
|
|
||||||
if regex_result is not None:
|
|
||||||
return int(regex_result.group(1))
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __get_group_name(group_index: int) -> str:
|
|
||||||
"""
|
|
||||||
Output Sector group name by given sector index
|
|
||||||
"""
|
|
||||||
if group_index == 9:
|
|
||||||
return 'Sector_9'
|
|
||||||
else:
|
|
||||||
return f'Sector_{group_index:0>2d}'
|
|
||||||
|
|
||||||
__mWriter: bmap.BMFileWriter
|
|
||||||
__mSectors: list[bmap.BMGroup]
|
|
||||||
|
|
||||||
def __init__(self, assoc_writer: bmap.BMFileWriter):
|
|
||||||
self.__mWriter = assoc_writer
|
|
||||||
self.__mSectors = []
|
|
||||||
|
|
||||||
def create_group(self, group_name: str) -> bmap.BMGroup:
|
|
||||||
"""
|
|
||||||
The hooked group creation function.
|
|
||||||
|
|
||||||
Please note the passed group name argument is just for name checking.
|
|
||||||
This function will set group name for you, not like BMFileWriter.create_group() operated.
|
|
||||||
"""
|
|
||||||
# check whether it is sector group
|
|
||||||
# note: the return sector index is 1 based, not 0
|
|
||||||
sector_idx: int | None = VirtoolsGroupCreationGuard.__get_group_index(group_name)
|
|
||||||
# if it is regular group, return normal creation
|
|
||||||
if sector_idx is None:
|
|
||||||
gp: bmap.BMGroup = self.__mWriter.create_group()
|
|
||||||
gp.set_name(group_name)
|
|
||||||
return gp
|
|
||||||
|
|
||||||
# get from sector cahce list
|
|
||||||
# enlarge sector cache list if it is not fulfilled given sector index
|
|
||||||
while sector_idx > len(self.__mSectors):
|
|
||||||
gp: bmap.BMGroup = self.__mWriter.create_group()
|
|
||||||
self.__mSectors.append(gp)
|
|
||||||
gp.set_name(self.__get_group_name(len(self.__mSectors)))
|
|
||||||
# return ordered sector from sector caches
|
|
||||||
return self.__mSectors[sector_idx - 1]
|
|
||||||
|
|
||||||
def register() -> None:
|
def register() -> None:
|
||||||
bpy.utils.register_class(BBP_OT_export_virtools)
|
bpy.utils.register_class(BBP_OT_export_virtools)
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ import bpy
|
|||||||
from bpy_extras.wm_utils.progress_report import ProgressReport
|
from bpy_extras.wm_utils.progress_report import ProgressReport
|
||||||
import tempfile, os, typing
|
import tempfile, os, typing
|
||||||
from . import PROP_preferences, UTIL_ioport_shared
|
from . import PROP_preferences, UTIL_ioport_shared
|
||||||
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture
|
from . import UTIL_virtools_types, UTIL_functions, UTIL_file_browser, UTIL_blender_mesh, UTIL_ballance_texture, UTIL_naming_convension
|
||||||
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture
|
from . import PROP_virtools_group, PROP_virtools_material, PROP_virtools_mesh, PROP_virtools_texture, PROP_ballance_map_info
|
||||||
from .PyBMap import bmap_wrapper as bmap
|
from .PyBMap import bmap_wrapper as bmap
|
||||||
|
|
||||||
class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams):
|
class BBP_OT_import_virtools(bpy.types.Operator, UTIL_file_browser.ImportVirtoolsFile, UTIL_ioport_shared.ImportParams, UTIL_ioport_shared.VirtoolsParams):
|
||||||
@ -344,10 +344,12 @@ def _import_virtools_groups(
|
|||||||
reader: bmap.BMFileReader,
|
reader: bmap.BMFileReader,
|
||||||
progress: ProgressReport,
|
progress: ProgressReport,
|
||||||
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object]
|
obj3d_cret_map: dict[bmap.BM3dObject, bpy.types.Object]
|
||||||
) -> dict[bmap.BM3dObject, bpy.types.Object]:
|
) -> None:
|
||||||
# we need iterate all groups to construct a reversed map
|
# we need iterate all groups to construct a reversed map
|
||||||
# to indicate which groups should this 3dobject be grouped into.
|
# to indicate which groups should this 3dobject be grouped into.
|
||||||
reverse_map: dict[bmap.BM3dObject, set[str]] = {}
|
reverse_map: dict[bmap.BM3dObject, set[str]] = {}
|
||||||
|
# sector counter to record the maximum sector we have processed.
|
||||||
|
sector_count: int = 1
|
||||||
|
|
||||||
# prepare progress
|
# prepare progress
|
||||||
progress.enter_substeps(reader.get_material_count(), "Loading Groups")
|
progress.enter_substeps(reader.get_material_count(), "Loading Groups")
|
||||||
@ -357,6 +359,12 @@ def _import_virtools_groups(
|
|||||||
group_name: str | None = vtgroup.get_name()
|
group_name: str | None = vtgroup.get_name()
|
||||||
if group_name is None: continue
|
if group_name is None: continue
|
||||||
|
|
||||||
|
# try extracting sector info
|
||||||
|
potential_sector_count: int | None = UTIL_naming_convension.extract_sector_from_name(group_name)
|
||||||
|
if potential_sector_count is not None:
|
||||||
|
sector_count = max(sector_count, potential_sector_count)
|
||||||
|
|
||||||
|
# creating map
|
||||||
for item in vtgroup.get_objects():
|
for item in vtgroup.get_objects():
|
||||||
# get or create set
|
# get or create set
|
||||||
objgroups: set[str] = reverse_map.get(item, None)
|
objgroups: set[str] = reverse_map.get(item, None)
|
||||||
@ -370,6 +378,11 @@ def _import_virtools_groups(
|
|||||||
# step
|
# step
|
||||||
progress.step()
|
progress.step()
|
||||||
|
|
||||||
|
# assign to ballance map info according to gotten sector count
|
||||||
|
map_info: PROP_ballance_map_info.RawBallanceMapInfo = PROP_ballance_map_info.get_raw_ballance_map_info(bpy.context.scene)
|
||||||
|
map_info.mSectorCount = max(map_info.mSectorCount, sector_count)
|
||||||
|
PROP_ballance_map_info.set_raw_ballance_map_info(bpy.context.scene, map_info)
|
||||||
|
|
||||||
# leave progress
|
# leave progress
|
||||||
progress.leave_substeps()
|
progress.leave_substeps()
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import bpy
|
import bpy
|
||||||
|
from . import UTIL_functions
|
||||||
from . import PROP_virtools_material, PROP_preferences
|
from . import PROP_virtools_material, PROP_preferences
|
||||||
|
|
||||||
class BBP_OT_fix_all_material(bpy.types.Operator):
|
class BBP_OT_fix_all_material(bpy.types.Operator):
|
||||||
@ -9,7 +10,9 @@ class BBP_OT_fix_all_material(bpy.types.Operator):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(cls, context):
|
def poll(cls, context):
|
||||||
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder()
|
# only enable this when plugin have a valid ballance texture folder
|
||||||
|
# and we are in object mode
|
||||||
|
return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder() and UTIL_functions.is_in_object_mode()
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
wm = context.window_manager
|
wm = context.window_manager
|
||||||
|
@ -43,25 +43,25 @@ class BBP_PG_legacy_align_history(bpy.types.PropertyGroup):
|
|||||||
align_x: bpy.props.BoolProperty(
|
align_x: bpy.props.BoolProperty(
|
||||||
name = "X Position",
|
name = "X Position",
|
||||||
default = False,
|
default = False,
|
||||||
)
|
) # type: ignore
|
||||||
align_y: bpy.props.BoolProperty(
|
align_y: bpy.props.BoolProperty(
|
||||||
name = "Y Position",
|
name = "Y Position",
|
||||||
default = False,
|
default = False,
|
||||||
)
|
) # type: ignore
|
||||||
align_z: bpy.props.BoolProperty(
|
align_z: bpy.props.BoolProperty(
|
||||||
name = "Z Position",
|
name = "Z Position",
|
||||||
default = False,
|
default = False,
|
||||||
)
|
) # type: ignore
|
||||||
current_align_mode: bpy.props.EnumProperty(
|
current_align_mode: bpy.props.EnumProperty(
|
||||||
name = "Current Object (Active Object)",
|
name = "Current Object (Active Object)",
|
||||||
items = _g_EnumHelper_AlignMode.generate_items(),
|
items = _g_EnumHelper_AlignMode.generate_items(),
|
||||||
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
||||||
)
|
) # type: ignore
|
||||||
target_align_mode: bpy.props.EnumProperty(
|
target_align_mode: bpy.props.EnumProperty(
|
||||||
name = "Target Objects (Other Objects)",
|
name = "Target Objects (Other Objects)",
|
||||||
items = _g_EnumHelper_AlignMode.generate_items(),
|
items = _g_EnumHelper_AlignMode.generate_items(),
|
||||||
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
default = _g_EnumHelper_AlignMode.to_selection(AlignMode.AxisCenter),
|
||||||
)
|
) # type: ignore
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -109,21 +109,21 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
options = {'HIDDEN', 'SKIP_SAVE'},
|
options = {'HIDDEN', 'SKIP_SAVE'},
|
||||||
default = True, # default True value to make it as a "light" button, not a grey one.
|
default = True, # default True value to make it as a "light" button, not a grey one.
|
||||||
update = apply_flag_updated,
|
update = apply_flag_updated,
|
||||||
)
|
) # type: ignore
|
||||||
recursive_hinder: bpy.props.BoolProperty(
|
recursive_hinder: bpy.props.BoolProperty(
|
||||||
name = "Recursive Hinder",
|
name = "Recursive Hinder",
|
||||||
description = "An internal flag to prevent the loop calling to apply_flags's updator.",
|
description = "An internal flag to prevent the loop calling to apply_flags's updator.",
|
||||||
options = {'HIDDEN', 'SKIP_SAVE'},
|
options = {'HIDDEN', 'SKIP_SAVE'},
|
||||||
default = False,
|
default = False,
|
||||||
)
|
) # type: ignore
|
||||||
align_history : bpy.props.CollectionProperty(
|
align_history : bpy.props.CollectionProperty(
|
||||||
name = "Historys",
|
name = "Historys",
|
||||||
description = "Align history.",
|
description = "Align history.",
|
||||||
type = BBP_PG_legacy_align_history,
|
type = BBP_PG_legacy_align_history,
|
||||||
)
|
) # type: ignore
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(self, context):
|
def poll(cls, context):
|
||||||
return _check_align_requirement()
|
return _check_align_requirement()
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
@ -180,10 +180,13 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
#region Core Functions
|
#region Core Functions
|
||||||
|
|
||||||
def _check_align_requirement() -> bool:
|
def _check_align_requirement() -> bool:
|
||||||
|
# if we are not in object mode, do not do legacy align
|
||||||
|
if not UTIL_functions.is_in_object_mode():
|
||||||
|
return False
|
||||||
|
|
||||||
# check current obj
|
# check current obj
|
||||||
if bpy.context.active_object is None:
|
if bpy.context.active_object is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# check target obj with filter of current obj
|
# check target obj with filter of current obj
|
||||||
length = len(bpy.context.selected_objects)
|
length = len(bpy.context.selected_objects)
|
||||||
if bpy.context.active_object in bpy.context.selected_objects:
|
if bpy.context.active_object in bpy.context.selected_objects:
|
||||||
|
@ -38,7 +38,11 @@ class BBP_OT_select_object_by_virtools_group(bpy.types.Operator, PROP_virtools_g
|
|||||||
description = "Selection mode",
|
description = "Selection mode",
|
||||||
items = _g_EnumHelper_SelectMode.generate_items(),
|
items = _g_EnumHelper_SelectMode.generate_items(),
|
||||||
default = _g_EnumHelper_SelectMode.to_selection(SelectMode.Intersect)
|
default = _g_EnumHelper_SelectMode.to_selection(SelectMode.Intersect)
|
||||||
)
|
) # type: ignore
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return UTIL_functions.is_in_object_mode()
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
wm = context.window_manager
|
wm = context.window_manager
|
||||||
@ -119,7 +123,7 @@ class BBP_OT_add_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.
|
|||||||
bl_options = {'UNDO'}
|
bl_options = {'UNDO'}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(self, context):
|
def poll(cls, context):
|
||||||
return len(bpy.context.selected_objects) != 0
|
return len(bpy.context.selected_objects) != 0
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
@ -131,6 +135,7 @@ class BBP_OT_add_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.
|
|||||||
for obj in bpy.context.selected_objects:
|
for obj in bpy.context.selected_objects:
|
||||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||||
gp.add_group(group_name)
|
gp.add_group(group_name)
|
||||||
|
self.report({'INFO'}, "Grouping objects successfully.")
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
@ -143,7 +148,7 @@ class BBP_OT_rm_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.S
|
|||||||
bl_options = {'UNDO'}
|
bl_options = {'UNDO'}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(self, context):
|
def poll(cls, context):
|
||||||
return len(bpy.context.selected_objects) != 0
|
return len(bpy.context.selected_objects) != 0
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
@ -155,6 +160,7 @@ class BBP_OT_rm_objects_virtools_group(bpy.types.Operator, PROP_virtools_group.S
|
|||||||
for obj in bpy.context.selected_objects:
|
for obj in bpy.context.selected_objects:
|
||||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||||
gp.remove_group(group_name)
|
gp.remove_group(group_name)
|
||||||
|
self.report({'INFO'}, "Ungrouping objects successfully.")
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
@ -167,7 +173,7 @@ class BBP_OT_clear_objects_virtools_group(bpy.types.Operator):
|
|||||||
bl_options = {'UNDO'}
|
bl_options = {'UNDO'}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def poll(self, context):
|
def poll(cls, context):
|
||||||
return len(bpy.context.selected_objects) != 0
|
return len(bpy.context.selected_objects) != 0
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
@ -179,6 +185,7 @@ class BBP_OT_clear_objects_virtools_group(bpy.types.Operator):
|
|||||||
for obj in bpy.context.selected_objects:
|
for obj in bpy.context.selected_objects:
|
||||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as gp:
|
||||||
gp.clear_groups()
|
gp.clear_groups()
|
||||||
|
self.report({'INFO'}, "Clear objects groups successfully.")
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
81
bbp_ng/PROP_ballance_map_info.py
Normal file
81
bbp_ng/PROP_ballance_map_info.py
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
import bpy
|
||||||
|
import typing
|
||||||
|
from . import UTIL_functions
|
||||||
|
|
||||||
|
class RawBallanceMapInfo():
|
||||||
|
cSectorCount: typing.ClassVar[int] = 1
|
||||||
|
|
||||||
|
mSectorCount: int
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
self.mSectorCount = kwargs.get("mSectorCount", RawBallanceMapInfo.cSectorCount)
|
||||||
|
|
||||||
|
def regulate(self):
|
||||||
|
self.mSectorCount = UTIL_functions.clamp_int(self.mSectorCount, 1, 999)
|
||||||
|
|
||||||
|
#region Prop Decl & Getter Setter
|
||||||
|
|
||||||
|
class BBP_PG_ballance_map_info(bpy.types.PropertyGroup):
|
||||||
|
sector_count: bpy.props.IntProperty(
|
||||||
|
name = "Sector",
|
||||||
|
description = "The sector count of this Ballance map which is used in exporting map and may be changed when importing map.",
|
||||||
|
default = 1,
|
||||||
|
max = 999, min = 1,
|
||||||
|
soft_max = 8, soft_min = 1,
|
||||||
|
step = 1
|
||||||
|
) # type: ignore
|
||||||
|
|
||||||
|
def get_ballance_map_info(scene: bpy.types.Scene) -> BBP_PG_ballance_map_info:
|
||||||
|
return scene.ballance_map_info
|
||||||
|
|
||||||
|
def get_raw_ballance_map_info(scene: bpy.types.Scene) -> RawBallanceMapInfo:
|
||||||
|
props: BBP_PG_ballance_map_info = get_ballance_map_info(scene)
|
||||||
|
rawdata: RawBallanceMapInfo = RawBallanceMapInfo()
|
||||||
|
|
||||||
|
rawdata.mSectorCount = props.sector_count
|
||||||
|
|
||||||
|
rawdata.regulate()
|
||||||
|
return rawdata
|
||||||
|
|
||||||
|
def set_raw_ballance_map_info(scene: bpy.types.Scene, rawdata: RawBallanceMapInfo) -> None:
|
||||||
|
props: BBP_PG_ballance_map_info = get_ballance_map_info(scene)
|
||||||
|
|
||||||
|
props.sector_count = rawdata.mSectorCount
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
class BBP_PT_ballance_map_info(bpy.types.Panel):
|
||||||
|
"""Show Ballance Map Infos."""
|
||||||
|
bl_label = "Ballance Map"
|
||||||
|
bl_idname = "BBP_PT_ballance_map_info"
|
||||||
|
bl_space_type = 'PROPERTIES'
|
||||||
|
bl_region_type = 'WINDOW'
|
||||||
|
bl_context = "scene"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def poll(cls, context):
|
||||||
|
return context.scene is not None
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
layout: bpy.types.UILayout = self.layout
|
||||||
|
target: bpy.types.Scene = context.scene
|
||||||
|
props: BBP_PG_ballance_map_info = get_ballance_map_info(target)
|
||||||
|
|
||||||
|
# show map sector count numberbox
|
||||||
|
layout.prop(props, 'sector_count')
|
||||||
|
|
||||||
|
def register() -> None:
|
||||||
|
# register
|
||||||
|
bpy.utils.register_class(BBP_PG_ballance_map_info)
|
||||||
|
bpy.utils.register_class(BBP_PT_ballance_map_info)
|
||||||
|
|
||||||
|
# add into scene metadata
|
||||||
|
bpy.types.Scene.ballance_map_info = bpy.props.PointerProperty(type = BBP_PG_ballance_map_info)
|
||||||
|
|
||||||
|
def unregister() -> None:
|
||||||
|
# del from scene metadata
|
||||||
|
del bpy.types.Scene.ballance_map_info
|
||||||
|
|
||||||
|
# unregister
|
||||||
|
bpy.utils.unregister_class(BBP_PG_ballance_map_info)
|
||||||
|
bpy.utils.unregister_class(BBP_PT_ballance_map_info)
|
@ -8,7 +8,7 @@ class BBP_PG_virtools_group(bpy.types.PropertyGroup):
|
|||||||
group_name: bpy.props.StringProperty(
|
group_name: bpy.props.StringProperty(
|
||||||
name = "Group Name",
|
name = "Group Name",
|
||||||
default = ""
|
default = ""
|
||||||
)
|
) # type: ignore
|
||||||
|
|
||||||
def get_virtools_groups(obj: bpy.types.Object) -> bpy.types.CollectionProperty:
|
def get_virtools_groups(obj: bpy.types.Object) -> bpy.types.CollectionProperty:
|
||||||
return obj.virtools_groups
|
return obj.virtools_groups
|
||||||
@ -405,14 +405,14 @@ def register() -> None:
|
|||||||
bpy.utils.register_class(BBP_OT_clear_virtools_groups)
|
bpy.utils.register_class(BBP_OT_clear_virtools_groups)
|
||||||
bpy.utils.register_class(BBP_PT_virtools_groups)
|
bpy.utils.register_class(BBP_PT_virtools_groups)
|
||||||
|
|
||||||
# add into scene metadata
|
# add into object metadata
|
||||||
bpy.types.Object.virtools_groups = bpy.props.CollectionProperty(type = BBP_PG_virtools_group)
|
bpy.types.Object.virtools_groups = bpy.props.CollectionProperty(type = BBP_PG_virtools_group)
|
||||||
bpy.types.Object.active_virtools_groups = bpy.props.IntProperty()
|
bpy.types.Object.active_virtools_groups = bpy.props.IntProperty()
|
||||||
|
|
||||||
def unregister() -> None:
|
def unregister() -> None:
|
||||||
# del from scene metadata
|
# del from object metadata
|
||||||
del bpy.types.Scene.active_virtools_groups
|
del bpy.types.Object.active_virtools_groups
|
||||||
del bpy.types.Scene.virtools_groups
|
del bpy.types.Object.virtools_groups
|
||||||
|
|
||||||
bpy.utils.unregister_class(BBP_PT_virtools_groups)
|
bpy.utils.unregister_class(BBP_PT_virtools_groups)
|
||||||
bpy.utils.unregister_class(BBP_OT_clear_virtools_groups)
|
bpy.utils.unregister_class(BBP_OT_clear_virtools_groups)
|
||||||
|
@ -397,13 +397,20 @@ def apply_to_blender_material(mtl: bpy.types.Material):
|
|||||||
# bnode.inputs["Emission"].default_value = rawdata.mEmissive.to_const_rgba()
|
# bnode.inputs["Emission"].default_value = rawdata.mEmissive.to_const_rgba()
|
||||||
|
|
||||||
mtl.specular_intensity = rawdata.mSpecularPower
|
mtl.specular_intensity = rawdata.mSpecularPower
|
||||||
bnode.inputs["Specular"].default_value = UTIL_functions.clamp_float(
|
bnode.inputs["Specular IOR Level"].default_value = UTIL_functions.clamp_float(
|
||||||
rawdata.mSpecularPower, 0.0, 1.0
|
rawdata.mSpecularPower, 0.0, 1.0
|
||||||
)
|
)
|
||||||
|
|
||||||
# set some alpha data
|
# set some alpha data
|
||||||
mtl.use_backface_culling = not rawdata.mEnableTwoSided
|
mtl.use_backface_culling = not rawdata.mEnableTwoSided
|
||||||
mtl.blend_method = 'BLEND' if rawdata.mEnableAlphaBlend else 'OPAQUE'
|
if rawdata.mEnableAlphaBlend:
|
||||||
|
# In old format: mtl.blend_method = 'BLEND'
|
||||||
|
mtl.surface_render_method = 'BLENDED'
|
||||||
|
mtl.use_raytrace_refraction = True
|
||||||
|
else:
|
||||||
|
# In old format: mtl.blend_method = 'OPAQUE'
|
||||||
|
mtl.surface_render_method = 'DITHERED'
|
||||||
|
mtl.use_raytrace_refraction = False
|
||||||
|
|
||||||
# set texture
|
# set texture
|
||||||
if rawdata.mTexture is not None:
|
if rawdata.mTexture is not None:
|
||||||
|
@ -107,8 +107,8 @@ class TemporaryMesh():
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__mBindingObject: bpy.types.Object | None
|
__mBindingObject: bpy.types.Object
|
||||||
__mTempMesh: bpy.types.Mesh | None
|
__mTempMesh: bpy.types.Mesh
|
||||||
|
|
||||||
def __init__(self, binding_obj: bpy.types.Object):
|
def __init__(self, binding_obj: bpy.types.Object):
|
||||||
self.__mBindingObject = binding_obj
|
self.__mBindingObject = binding_obj
|
||||||
@ -149,7 +149,7 @@ class MeshReader():
|
|||||||
A helper class TemporaryMesh can help you do this.
|
A helper class TemporaryMesh can help you do this.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__mAssocMesh: bpy.types.Mesh | None ##< The binding mesh for this reader. None if this reader is invalid.
|
__mAssocMesh: bpy.types.Mesh ##< The binding mesh for this reader. None if this reader is invalid.
|
||||||
|
|
||||||
def __init__(self, assoc_mesh: bpy.types.Mesh):
|
def __init__(self, assoc_mesh: bpy.types.Mesh):
|
||||||
self.__mAssocMesh = assoc_mesh
|
self.__mAssocMesh = assoc_mesh
|
||||||
@ -157,7 +157,6 @@ class MeshReader():
|
|||||||
# triangulate temp mesh
|
# triangulate temp mesh
|
||||||
if self.is_valid():
|
if self.is_valid():
|
||||||
self.__triangulate_mesh()
|
self.__triangulate_mesh()
|
||||||
self.__mAssocMesh.calc_normals_split()
|
|
||||||
|
|
||||||
def is_valid(self) -> bool:
|
def is_valid(self) -> bool:
|
||||||
return self.__mAssocMesh is not None
|
return self.__mAssocMesh is not None
|
||||||
@ -171,7 +170,6 @@ class MeshReader():
|
|||||||
def dispose(self) -> None:
|
def dispose(self) -> None:
|
||||||
if self.is_valid():
|
if self.is_valid():
|
||||||
# reset mesh
|
# reset mesh
|
||||||
self.__mAssocMesh.free_normals_split()
|
|
||||||
self.__mAssocMesh = None
|
self.__mAssocMesh = None
|
||||||
|
|
||||||
def get_vertex_position_count(self) -> int:
|
def get_vertex_position_count(self) -> int:
|
||||||
@ -203,10 +201,10 @@ class MeshReader():
|
|||||||
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
raise UTIL_functions.BBPException('try to call an invalid MeshReader.')
|
||||||
|
|
||||||
cache: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
cache: UTIL_virtools_types.VxVector3 = UTIL_virtools_types.VxVector3()
|
||||||
for nml in self.__mAssocMesh.loops:
|
for nml in self.__mAssocMesh.corner_normals:
|
||||||
cache.x = nml.normal.x
|
cache.x = nml.vector.x
|
||||||
cache.y = nml.normal.y
|
cache.y = nml.vector.y
|
||||||
cache.z = nml.normal.z
|
cache.z = nml.vector.z
|
||||||
yield cache
|
yield cache
|
||||||
|
|
||||||
def get_vertex_uv_count(self) -> int:
|
def get_vertex_uv_count(self) -> int:
|
||||||
@ -318,7 +316,7 @@ class MeshWriter():
|
|||||||
then refer it to all face uv.
|
then refer it to all face uv.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__mAssocMesh: bpy.types.Mesh | None ##< The binding mesh for this writer. None if this writer is invalid.
|
__mAssocMesh: bpy.types.Mesh ##< The binding mesh for this writer. None if this writer is invalid.
|
||||||
|
|
||||||
__mVertexPos: array.array ##< Array item is float(f). Length must be an integer multiple of 3.
|
__mVertexPos: array.array ##< Array item is float(f). Length must be an integer multiple of 3.
|
||||||
__mVertexNormal: array.array ##< Array item is float(f). Length must be an integer multiple of 3.
|
__mVertexNormal: array.array ##< Array item is float(f). Length must be an integer multiple of 3.
|
||||||
@ -348,6 +346,9 @@ class MeshWriter():
|
|||||||
# Value is key's index in __mMtlSlot.
|
# Value is key's index in __mMtlSlot.
|
||||||
__mMtlSlotMap: dict[bpy.types.Material | None, int]
|
__mMtlSlotMap: dict[bpy.types.Material | None, int]
|
||||||
|
|
||||||
|
## The attribute name storing temporary normals data inside mesh.
|
||||||
|
__cTempNormalAttrName: typing.ClassVar[str] = 'temp_custom_normals'
|
||||||
|
|
||||||
def __init__(self, assoc_mesh: bpy.types.Mesh):
|
def __init__(self, assoc_mesh: bpy.types.Mesh):
|
||||||
self.__mAssocMesh = assoc_mesh
|
self.__mAssocMesh = assoc_mesh
|
||||||
|
|
||||||
@ -446,15 +447,19 @@ class MeshWriter():
|
|||||||
self.__mAssocMesh.polygons.add(len(self.__mFaceVertexCount))
|
self.__mAssocMesh.polygons.add(len(self.__mFaceVertexCount))
|
||||||
# create uv layer
|
# create uv layer
|
||||||
self.__mAssocMesh.uv_layers.new(do_init = False)
|
self.__mAssocMesh.uv_layers.new(do_init = False)
|
||||||
# split normals, it is IMPORTANT
|
|
||||||
self.__mAssocMesh.create_normals_split()
|
|
||||||
|
|
||||||
# add vertex position data
|
# add vertex position data
|
||||||
self.__mAssocMesh.vertices.foreach_set('co', self.__mVertexPos)
|
self.__mAssocMesh.vertices.foreach_set('co', self.__mVertexPos)
|
||||||
# add face vertex pos index data
|
# add face vertex pos index data
|
||||||
self.__mAssocMesh.loops.foreach_set('vertex_index', self.__mFacePosIndices)
|
self.__mAssocMesh.loops.foreach_set('vertex_index', self.__mFacePosIndices)
|
||||||
# add face vertex nml by function
|
# add face vertex nml by function via mesh custom attribute
|
||||||
self.__mAssocMesh.loops.foreach_set('normal',
|
# NOTE: Blender 4.0 / 4.1 changed. I copy these code from FBX Importer.
|
||||||
|
temp_normal_attribute: bpy.types.FloatVectorAttribute
|
||||||
|
temp_normal_attribute = typing.cast(
|
||||||
|
bpy.types.FloatVectorAttribute,
|
||||||
|
self.__mAssocMesh.attributes.new(MeshWriter.__cTempNormalAttrName, 'FLOAT_VECTOR', 'CORNER')
|
||||||
|
)
|
||||||
|
temp_normal_attribute.data.foreach_set('vector',
|
||||||
tuple(_flat_face_nml_index(self.__mFaceNmlIndices, self.__mVertexNormal))
|
tuple(_flat_face_nml_index(self.__mFaceNmlIndices, self.__mVertexNormal))
|
||||||
)
|
)
|
||||||
# add face vertex uv by function
|
# add face vertex uv by function
|
||||||
@ -494,14 +499,22 @@ class MeshWriter():
|
|||||||
# this should not happend in normal case, for testing, please load "Level_1.NMO" (Ballance Level 1).
|
# this should not happend in normal case, for testing, please load "Level_1.NMO" (Ballance Level 1).
|
||||||
|
|
||||||
# copy data from loops preserved in validate().
|
# copy data from loops preserved in validate().
|
||||||
|
# NOTE: Blender 4.0 / 4.1 changed. I copy these code from FBX Importer.
|
||||||
loops_normals = array.array('f', [0.0] * (len(self.__mAssocMesh.loops) * 3))
|
loops_normals = array.array('f', [0.0] * (len(self.__mAssocMesh.loops) * 3))
|
||||||
self.__mAssocMesh.loops.foreach_get('normal', loops_normals)
|
temp_normal_attribute = typing.cast(
|
||||||
|
bpy.types.FloatVectorAttribute,
|
||||||
|
self.__mAssocMesh.attributes[MeshWriter.__cTempNormalAttrName]
|
||||||
|
)
|
||||||
|
temp_normal_attribute.data.foreach_get("vector", loops_normals)
|
||||||
# apply data
|
# apply data
|
||||||
self.__mAssocMesh.normals_split_custom_set(
|
self.__mAssocMesh.normals_split_custom_set(
|
||||||
tuple(_nest_custom_split_normal(loops_normals))
|
tuple(_nest_custom_split_normal(loops_normals))
|
||||||
)
|
)
|
||||||
# enable auto smooth. it is IMPORTANT
|
self.__mAssocMesh.attributes.remove(
|
||||||
self.__mAssocMesh.use_auto_smooth = True
|
# MARK: idk why I need fucking get this attribute again.
|
||||||
|
# But if I were not, this function must raise bullshit exception!
|
||||||
|
self.__mAssocMesh.attributes[MeshWriter.__cTempNormalAttrName]
|
||||||
|
)
|
||||||
|
|
||||||
def __clear_mesh(self):
|
def __clear_mesh(self):
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
|
@ -344,6 +344,12 @@ def create_bme_struct(
|
|||||||
cache_bv = typing.cast(mathutils.Vector, transform @ cache_bv)
|
cache_bv = typing.cast(mathutils.Vector, transform @ cache_bv)
|
||||||
# get result
|
# get result
|
||||||
prebuild_vec_data.append((cache_bv.x, cache_bv.y, cache_bv.z))
|
prebuild_vec_data.append((cache_bv.x, cache_bv.y, cache_bv.z))
|
||||||
|
|
||||||
|
# Check whether given transform is mirror matrix
|
||||||
|
# because mirror matrix will reverse triangle indice order.
|
||||||
|
# If matrix is mirror matrix, we need reverse it again in following procession,
|
||||||
|
# including getting uv, calculating normal and providing face data.
|
||||||
|
mirror_matrix: bool = _is_mirror_matrix(transform)
|
||||||
|
|
||||||
# prepare mesh part data
|
# prepare mesh part data
|
||||||
mesh_part: UTIL_blender_mesh.MeshWriterIngredient = UTIL_blender_mesh.MeshWriterIngredient()
|
mesh_part: UTIL_blender_mesh.MeshWriterIngredient = UTIL_blender_mesh.MeshWriterIngredient()
|
||||||
@ -370,7 +376,12 @@ def create_bme_struct(
|
|||||||
if face_nml_data is None:
|
if face_nml_data is None:
|
||||||
# nml is null, we need compute by ourselves
|
# nml is null, we need compute by ourselves
|
||||||
# get first 3 entries in indices list as the compution ref
|
# get first 3 entries in indices list as the compution ref
|
||||||
face_indices_data: list[int] = face_data[TOKEN_FACES_INDICES]
|
# please note that we may need reverse it
|
||||||
|
face_indices_data: list[int]
|
||||||
|
if mirror_matrix:
|
||||||
|
face_indices_data = face_data[TOKEN_FACES_INDICES][::-1]
|
||||||
|
else:
|
||||||
|
face_indices_data = face_data[TOKEN_FACES_INDICES][:]
|
||||||
# compute it by getting vertices info from prebuild vertices data
|
# compute it by getting vertices info from prebuild vertices data
|
||||||
# because the normals is computed from transformed vertices
|
# because the normals is computed from transformed vertices
|
||||||
# so no need to correct its by normal transform.
|
# so no need to correct its by normal transform.
|
||||||
@ -399,7 +410,9 @@ def create_bme_struct(
|
|||||||
v: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2()
|
v: UTIL_virtools_types.VxVector2 = UTIL_virtools_types.VxVector2()
|
||||||
for face_idx in valid_face_idx:
|
for face_idx in valid_face_idx:
|
||||||
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx]
|
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx]
|
||||||
for i in range(len(face_data[TOKEN_FACES_INDICES])):
|
# iterate uv list considering mirror matrix
|
||||||
|
indices_count: int = len(face_data[TOKEN_FACES_INDICES])
|
||||||
|
for i in (range(indices_count)[::-1] if mirror_matrix else range(indices_count)):
|
||||||
# BME uv do not need any extra process
|
# BME uv do not need any extra process
|
||||||
v.x, v.y = _eval_others(face_data[TOKEN_FACES_UVS][i], params)
|
v.x, v.y = _eval_others(face_data[TOKEN_FACES_UVS][i], params)
|
||||||
yield v
|
yield v
|
||||||
@ -424,8 +437,13 @@ def create_bme_struct(
|
|||||||
# get face data
|
# get face data
|
||||||
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx]
|
face_data: dict[str, typing.Any] = proto[TOKEN_FACES][face_idx]
|
||||||
|
|
||||||
|
# get face indices considering the mirror matrix
|
||||||
|
face_indices: list[int]
|
||||||
|
if mirror_matrix:
|
||||||
|
face_indices = face_data[TOKEN_FACES_INDICES][::-1]
|
||||||
|
else:
|
||||||
|
face_indices = face_data[TOKEN_FACES_INDICES][:]
|
||||||
# calc indices count
|
# calc indices count
|
||||||
face_indices: list[int] = face_data[TOKEN_FACES_INDICES]
|
|
||||||
indices_count: int = len(face_indices)
|
indices_count: int = len(face_indices)
|
||||||
# resize face data to fulfill req
|
# resize face data to fulfill req
|
||||||
while len(f.mIndices) > indices_count:
|
while len(f.mIndices) > indices_count:
|
||||||
@ -499,5 +517,18 @@ def _compute_normals(
|
|||||||
corss_mul.normalize()
|
corss_mul.normalize()
|
||||||
return (corss_mul.x, corss_mul.y, corss_mul.z)
|
return (corss_mul.x, corss_mul.y, corss_mul.z)
|
||||||
|
|
||||||
|
def _is_mirror_matrix(mat: mathutils.Matrix) -> bool:
|
||||||
|
"""
|
||||||
|
Reflection matrix (aka. mirror matrix) is a special scaling matrix.
|
||||||
|
In this matrix, 1 or 3 scaling factor is minus number.
|
||||||
|
|
||||||
|
Mirror matrix will cause the inverse of triangle indice order.
|
||||||
|
So we need detect it and re-reverse when creating bm struct.
|
||||||
|
This function can detect whether given matrix is mirror matrix.
|
||||||
|
|
||||||
|
Reference: https://zhuanlan.zhihu.com/p/96717729
|
||||||
|
"""
|
||||||
|
return mat.is_negative
|
||||||
|
#return mat.to_3x3().determinant() < 0
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -80,6 +80,16 @@ def select_certain_objects(objs: tuple[bpy.types.Object, ...]) -> None:
|
|||||||
# select first object as active object
|
# select first object as active object
|
||||||
bpy.context.view_layer.objects.active = objs[0]
|
bpy.context.view_layer.objects.active = objs[0]
|
||||||
|
|
||||||
|
def is_in_object_mode() -> bool:
|
||||||
|
# get active object from context
|
||||||
|
obj = bpy.context.active_object
|
||||||
|
|
||||||
|
# if there is no active object, we think it is in object mode
|
||||||
|
if obj is None: return True
|
||||||
|
|
||||||
|
# simply check active object mode
|
||||||
|
return obj.mode == 'OBJECT'
|
||||||
|
|
||||||
class EnumPropHelper():
|
class EnumPropHelper():
|
||||||
"""
|
"""
|
||||||
These class contain all functions related to EnumProperty, including generating `items`,
|
These class contain all functions related to EnumProperty, including generating `items`,
|
||||||
|
@ -184,6 +184,42 @@ class BallanceObjectInfo():
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Sector Extractor
|
||||||
|
|
||||||
|
_g_RegexBlcSectorGroup: re.Pattern = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
|
||||||
|
|
||||||
|
def extract_sector_from_name(group_name: str) -> int | None:
|
||||||
|
"""
|
||||||
|
A convenient function to extract sector index from given group name.
|
||||||
|
This function also supports 999 sector plugin.
|
||||||
|
|
||||||
|
Not only in this module, but also in outside modules, this function is vary used to extract sector index info.
|
||||||
|
|
||||||
|
Function return the index extracted, or None if given group name is not a valid sector group.
|
||||||
|
The valid sector index is range from 1 to 999 (inclusive)
|
||||||
|
"""
|
||||||
|
regex_result = _g_RegexBlcSectorGroup.match(group_name)
|
||||||
|
if regex_result is not None:
|
||||||
|
return int(regex_result.group(1))
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def build_name_from_sector_index(sector_index: int) -> str:
|
||||||
|
"""
|
||||||
|
A convenient function to build Ballance recognizable sector group name.
|
||||||
|
This function also supports 999 sector plugin.
|
||||||
|
|
||||||
|
This function also is used in this module or other modules outside.
|
||||||
|
|
||||||
|
Function return a sector name string. It basically the reverse operation of `extract_sector_from_name`.
|
||||||
|
"""
|
||||||
|
if sector_index == 9:
|
||||||
|
return 'Sector_9'
|
||||||
|
else:
|
||||||
|
return f'Sector_{sector_index:0>2d}'
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
#region Naming Convention Declaration
|
#region Naming Convention Declaration
|
||||||
|
|
||||||
_g_BlcNormalComponents: set[str] = set((
|
_g_BlcNormalComponents: set[str] = set((
|
||||||
@ -227,7 +263,6 @@ _g_BlcWood: set[str] = set((
|
|||||||
))
|
))
|
||||||
|
|
||||||
class VirtoolsGroupConvention():
|
class VirtoolsGroupConvention():
|
||||||
cRegexGroupSector: typing.ClassVar[re.Pattern] = re.compile('^Sector_(0[1-8]|[1-9][0-9]{1,2}|9)$')
|
|
||||||
cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + ')_(0[1-9]|[1-9][0-9])_.*$')
|
cRegexComponent: typing.ClassVar[re.Pattern] = re.compile('^(' + '|'.join(_g_BlcNormalComponents) + ')_(0[1-9]|[1-9][0-9])_.*$')
|
||||||
cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_TwoFlames_(0[1-7])$')
|
cRegexPC: typing.ClassVar[re.Pattern] = re.compile('^PC_TwoFlames_(0[1-7])$')
|
||||||
cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_Resetpoint_(0[1-8])$')
|
cRegexPR: typing.ClassVar[re.Pattern] = re.compile('^PR_Resetpoint_(0[1-8])$')
|
||||||
@ -255,9 +290,9 @@ class VirtoolsGroupConvention():
|
|||||||
counter: int = 0
|
counter: int = 0
|
||||||
last_matched_sector: int = 0
|
last_matched_sector: int = 0
|
||||||
for i in gps:
|
for i in gps:
|
||||||
regex_result = VirtoolsGroupConvention.cRegexGroupSector.match(i)
|
regex_result: int | None = extract_sector_from_name(i)
|
||||||
if regex_result is not None:
|
if regex_result is not None:
|
||||||
last_matched_sector = int(regex_result.group(1))
|
last_matched_sector = regex_result
|
||||||
counter += 1
|
counter += 1
|
||||||
|
|
||||||
if counter != 1: return None
|
if counter != 1: return None
|
||||||
@ -364,12 +399,12 @@ class VirtoolsGroupConvention():
|
|||||||
gp.add_group('Shadow')
|
gp.add_group('Shadow')
|
||||||
case BallanceObjectType.RAIL:
|
case BallanceObjectType.RAIL:
|
||||||
gp.add_group('Phys_FloorRails')
|
gp.add_group('Phys_FloorRails')
|
||||||
gp.add_group('Sound_HitID_02')
|
|
||||||
gp.add_group('Sound_RollID_02')
|
|
||||||
case BallanceObjectType.WOOD:
|
|
||||||
gp.add_group('Phys_Floors')
|
|
||||||
gp.add_group('Sound_HitID_03')
|
gp.add_group('Sound_HitID_03')
|
||||||
gp.add_group('Sound_RollID_03')
|
gp.add_group('Sound_RollID_03')
|
||||||
|
case BallanceObjectType.WOOD:
|
||||||
|
gp.add_group('Phys_Floors')
|
||||||
|
gp.add_group('Sound_HitID_02')
|
||||||
|
gp.add_group('Sound_RollID_02')
|
||||||
case BallanceObjectType.STOPPER:
|
case BallanceObjectType.STOPPER:
|
||||||
gp.add_group('Phys_FloorStopper')
|
gp.add_group('Phys_FloorStopper')
|
||||||
|
|
||||||
@ -377,12 +412,8 @@ class VirtoolsGroupConvention():
|
|||||||
# group into component type
|
# group into component type
|
||||||
# use typing.cast() to force linter accept it because None is impossible
|
# use typing.cast() to force linter accept it because None is impossible
|
||||||
gp.add_group(typing.cast(str, info.mComponentType))
|
gp.add_group(typing.cast(str, info.mComponentType))
|
||||||
|
|
||||||
# group to sector
|
# group to sector
|
||||||
if info.mSector == 9:
|
gp.add_group(build_name_from_sector_index(typing.cast(int, info.mSector)))
|
||||||
gp.add_group('Sector_9')
|
|
||||||
else:
|
|
||||||
gp.add_group(f'Sector_{info.mSector:0>2d}')
|
|
||||||
|
|
||||||
case _:
|
case _:
|
||||||
if reporter is not None:
|
if reporter is not None:
|
||||||
@ -460,9 +491,9 @@ class YYCToolchainConvention():
|
|||||||
case BallanceObjectType.LEVEL_END:
|
case BallanceObjectType.LEVEL_END:
|
||||||
return 'PE_Balloon_01'
|
return 'PE_Balloon_01'
|
||||||
case BallanceObjectType.CHECKPOINT:
|
case BallanceObjectType.CHECKPOINT:
|
||||||
return f'PR_TwoFlames_{info.mSector:0>2d}'
|
return f'PC_TwoFlames_{info.mSector:0>2d}'
|
||||||
case BallanceObjectType.RESETPOINT:
|
case BallanceObjectType.RESETPOINT:
|
||||||
return f'PC_Resetpoint_{info.mSector:0>2d}'
|
return f'PR_Resetpoint_{info.mSector:0>2d}'
|
||||||
|
|
||||||
case BallanceObjectType.DEPTH_CUBE:
|
case BallanceObjectType.DEPTH_CUBE:
|
||||||
return 'DepthCubes_'
|
return 'DepthCubes_'
|
||||||
|
@ -1,16 +1,3 @@
|
|||||||
bl_info = {
|
|
||||||
"name": "Ballance Blender Plugin",
|
|
||||||
"description": "Ballance mapping tools for Blender",
|
|
||||||
"author": "yyc12345",
|
|
||||||
"version": (4, 0),
|
|
||||||
"blender": (3, 6, 0),
|
|
||||||
"category": "Object",
|
|
||||||
"support": "COMMUNITY",
|
|
||||||
"warning": "Please read document before using this plugin.",
|
|
||||||
"doc_url": "https://github.com/yyc12345/BallanceBlenderHelper",
|
|
||||||
"tracker_url": "https://github.com/yyc12345/BallanceBlenderHelper/issues"
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Reload and Import
|
#region Reload and Import
|
||||||
|
|
||||||
# import core lib
|
# import core lib
|
||||||
@ -29,7 +16,8 @@ from . import UTIL_icons_manager
|
|||||||
UTIL_icons_manager.register()
|
UTIL_icons_manager.register()
|
||||||
|
|
||||||
# then load other modules
|
# then load other modules
|
||||||
from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_group, PROP_ballance_element, PROP_bme_material
|
from . import PROP_preferences, PROP_ptrprop_resolver, PROP_virtools_material, PROP_virtools_texture, PROP_virtools_mesh, PROP_virtools_group
|
||||||
|
from . import PROP_ballance_element, PROP_bme_material, PROP_ballance_map_info
|
||||||
from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools
|
from . import OP_IMPORT_bmfile, OP_EXPORT_bmfile, OP_IMPORT_virtools, OP_EXPORT_virtools
|
||||||
from . import OP_UV_flatten_uv, OP_UV_rail_uv
|
from . import OP_UV_flatten_uv, OP_UV_rail_uv
|
||||||
from . import OP_MTL_fix_material
|
from . import OP_MTL_fix_material
|
||||||
@ -217,6 +205,7 @@ def register() -> None:
|
|||||||
PROP_virtools_group.register()
|
PROP_virtools_group.register()
|
||||||
PROP_ballance_element.register()
|
PROP_ballance_element.register()
|
||||||
PROP_bme_material.register()
|
PROP_bme_material.register()
|
||||||
|
PROP_ballance_map_info.register()
|
||||||
|
|
||||||
OP_IMPORT_bmfile.register()
|
OP_IMPORT_bmfile.register()
|
||||||
OP_EXPORT_bmfile.register()
|
OP_EXPORT_bmfile.register()
|
||||||
@ -275,6 +264,7 @@ def unregister() -> None:
|
|||||||
OP_EXPORT_bmfile.unregister()
|
OP_EXPORT_bmfile.unregister()
|
||||||
OP_IMPORT_bmfile.unregister()
|
OP_IMPORT_bmfile.unregister()
|
||||||
|
|
||||||
|
PROP_ballance_map_info.unregister()
|
||||||
PROP_bme_material.unregister()
|
PROP_bme_material.unregister()
|
||||||
PROP_ballance_element.unregister()
|
PROP_ballance_element.unregister()
|
||||||
PROP_virtools_group.unregister()
|
PROP_virtools_group.unregister()
|
||||||
|
81
bbp_ng/blender_manifest.toml
Normal file
81
bbp_ng/blender_manifest.toml
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
# Full context are copied from https://docs.blender.org/manual/en/dev/extensions/getting_started.html
|
||||||
|
# Please note any update of this manifest
|
||||||
|
|
||||||
|
schema_version = "1.0.0"
|
||||||
|
|
||||||
|
# Example of manifest file for a Blender extension
|
||||||
|
# Change the values according to your extension
|
||||||
|
id = "bbp_ng"
|
||||||
|
version = "4.0.0"
|
||||||
|
name = "Ballance Blender Plugin"
|
||||||
|
tagline = "The specialized add-on served for creating game map of Ballance"
|
||||||
|
maintainer = "yyc12345 <yyc12321@outlook.com>"
|
||||||
|
# Supported types: "add-on", "theme"
|
||||||
|
type = "add-on"
|
||||||
|
|
||||||
|
# Optional link to documentation, support, source files, etc
|
||||||
|
website = "https://github.com/yyc12345/BallanceBlenderHelper"
|
||||||
|
|
||||||
|
# Optional list defined by Blender and server, see:
|
||||||
|
# https://docs.blender.org/manual/en/dev/advanced/extensions/tags.html
|
||||||
|
tags = ["Object", "Mesh", "UV", "Import-Export"]
|
||||||
|
|
||||||
|
blender_version_min = "4.2.0"
|
||||||
|
# # Optional: Blender version that the extension does not support, earlier versions are supported.
|
||||||
|
# # This can be omitted and defined later on the extensions platform if an issue is found.
|
||||||
|
# blender_version_max = "5.1.0"
|
||||||
|
|
||||||
|
# License conforming to https://spdx.org/licenses/ (use "SPDX: prefix)
|
||||||
|
# https://docs.blender.org/manual/en/dev/advanced/extensions/licenses.html
|
||||||
|
license = [
|
||||||
|
"SPDX:GPL-3.0-or-later",
|
||||||
|
]
|
||||||
|
# Optional: required by some licenses.
|
||||||
|
# copyright = [
|
||||||
|
# "2002-2024 Developer Name",
|
||||||
|
# "1998 Company Name",
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# Optional list of supported platforms. If omitted, the extension will be available in all operating systems.
|
||||||
|
platforms = ["windows-x64", "linux-x64"]
|
||||||
|
# Supported platforms: "windows-x64", "macos-arm64", "linux-x64", "windows-arm64", "macos-x64"
|
||||||
|
|
||||||
|
# Optional: bundle 3rd party Python modules.
|
||||||
|
# https://docs.blender.org/manual/en/dev/advanced/extensions/python_wheels.html
|
||||||
|
# wheels = [
|
||||||
|
# "./wheels/hexdump-3.3-py3-none-any.whl",
|
||||||
|
# "./wheels/jsmin-3.0.1-py3-none-any.whl",
|
||||||
|
# ]
|
||||||
|
|
||||||
|
# Optional: add-ons can list which resources they will require:
|
||||||
|
# * files (for access of any filesystem operations)
|
||||||
|
# * network (for internet access)
|
||||||
|
# * clipboard (to read and/or write the system clipboard)
|
||||||
|
# * camera (to capture photos and videos)
|
||||||
|
# * microphone (to capture audio)
|
||||||
|
#
|
||||||
|
# If using network, remember to also check `bpy.app.online_access`
|
||||||
|
# https://docs.blender.org/manual/en/dev/advanced/extensions/addons.html#internet-access
|
||||||
|
#
|
||||||
|
# For each permission it is important to also specify the reason why it is required.
|
||||||
|
# Keep this a single short sentence without a period (.) at the end.
|
||||||
|
# For longer explanations use the documentation or detail page.
|
||||||
|
|
||||||
|
[permissions]
|
||||||
|
# network = "Need to sync motion-capture data to server"
|
||||||
|
files = "Import/export Virtools file from/to disk"
|
||||||
|
# clipboard = "Copy and paste bone transforms"
|
||||||
|
|
||||||
|
# Optional: build settings.
|
||||||
|
# https://docs.blender.org/manual/en/dev/advanced/extensions/command_line_arguments.html#command-line-args-extension-build
|
||||||
|
[build]
|
||||||
|
paths_exclude_pattern = [
|
||||||
|
"__pycache__/", # Python runtime cache
|
||||||
|
".style.yapf", # Python code style
|
||||||
|
"*.gitkeep", # Git directory keeper
|
||||||
|
".gitignore", # Git Ignore File
|
||||||
|
"*.md", # Useless document.
|
||||||
|
"/raw_jsons", # Raw JSONs.
|
||||||
|
"/raw_icons", # Raw Icons.
|
||||||
|
"/tools", # Assistant tools.
|
||||||
|
]
|
@ -43,11 +43,11 @@
|
|||||||
"params": {
|
"params": {
|
||||||
"length": "length",
|
"length": "length",
|
||||||
"height": "height",
|
"height": "height",
|
||||||
"face": "(face[0], face[1], face[3], face[2], face[5], False)",
|
"face": "(face[0], face[1], face[2], face[3], face[5], False)",
|
||||||
"is_sink": "is_sink",
|
"is_sink": "is_sink",
|
||||||
"is_ribbon": "False"
|
"is_ribbon": "False"
|
||||||
},
|
},
|
||||||
"transform": "move(length, 5, 0) @ rot(0, 0, 180)"
|
"transform": "move(0, 5, 0) @ scale(1, -1, 1)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -15,7 +15,7 @@ def resize_image(src_file: str, dst_file: str) -> None:
|
|||||||
|
|
||||||
def create_thumbnails() -> None:
|
def create_thumbnails() -> None:
|
||||||
# get folder path
|
# get folder path
|
||||||
root_folder: str = os.path.dirname(os.path.dirname(__file__))
|
root_folder: str = common.get_plugin_folder()
|
||||||
|
|
||||||
# prepare handler
|
# prepare handler
|
||||||
def folder_handler(src_folder: str, dst_folder: str) -> None:
|
def folder_handler(src_folder: str, dst_folder: str) -> None:
|
||||||
|
@ -14,7 +14,7 @@ def compress_json(src_file: str, dst_file: str) -> None:
|
|||||||
|
|
||||||
def create_compressed_jsons() -> None:
|
def create_compressed_jsons() -> None:
|
||||||
# get folder path
|
# get folder path
|
||||||
root_folder: str = os.path.dirname(os.path.dirname(__file__))
|
root_folder: str = common.get_plugin_folder()
|
||||||
|
|
||||||
# prepare handler
|
# prepare handler
|
||||||
def folder_handler(src_folder: str, dst_folder: str) -> None:
|
def folder_handler(src_folder: str, dst_folder: str) -> None:
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
import os, typing
|
import os, typing, fnmatch, shutil
|
||||||
|
|
||||||
|
def get_plugin_folder() -> str:
|
||||||
|
"""
|
||||||
|
Get the absolute path to plugin root folder.
|
||||||
|
|
||||||
|
@return The absolute path to plugin root folder.
|
||||||
|
"""
|
||||||
|
return os.path.dirname(os.path.dirname(__file__))
|
||||||
|
|
||||||
def relative_to_folder(abs_path: str, src_parent: str, dst_parent: str) -> str:
|
def relative_to_folder(abs_path: str, src_parent: str, dst_parent: str) -> str:
|
||||||
"""
|
"""
|
||||||
|
4
docs/docs/en/ballance-properties.md
Normal file
4
docs/docs/en/ballance-properties.md
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
# Ballance Properties
|
||||||
|
|
||||||
|
!!! info "Work in Progress"
|
||||||
|
This part of manual still work in progress.
|
@ -19,6 +19,7 @@ Therefore, choosing Blender with BBP for mapping is not only choosing freedom an
|
|||||||
## Features
|
## Features
|
||||||
|
|
||||||
* [Virtools Properties](./virtools-properties.md)
|
* [Virtools Properties](./virtools-properties.md)
|
||||||
|
* [Ballance Properties](./ballance-properties.md)
|
||||||
* [Import and Export Virtools Document](./import-export-virtools.md)
|
* [Import and Export Virtools Document](./import-export-virtools.md)
|
||||||
* [Group Operation](./group-operations.md)
|
* [Group Operation](./group-operations.md)
|
||||||
* [Legacy Alignment](./legacy-align.md)
|
* [Legacy Alignment](./legacy-align.md)
|
||||||
|
BIN
docs/docs/imgs/ballance-properties.png
Normal file
BIN
docs/docs/imgs/ballance-properties.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 33 KiB |
Binary file not shown.
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 66 KiB |
29
docs/docs/zh-cn/ballance-properties.md
Normal file
29
docs/docs/zh-cn/ballance-properties.md
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Ballance属性
|
||||||
|
|
||||||
|
Ballance属性有别于Virtools属性,它是专门为Ballance制图服务的一系列属性。这些属性寄宿于场景,在同一场景中(制图不会涉及Blender中的场景切换),这些属性不会改变。在`Scene`属性面板可以找到Ballance属性相关的面板,如下图所示,分别是:
|
||||||
|
|
||||||
|
* `Ballance Elements`面板(红色箭头),对应Ballance机关
|
||||||
|
* `BME Materials`面板(绿色箭头),对应BME材质
|
||||||
|
* `Ballance Map`面板(蓝色箭头),对应Ballance地图信息
|
||||||
|
|
||||||
|
其中,只有Ballance地图信息是你需要重点关注的,其它属性在通常情况下不需要关注,除非地图中的某些材质或网格出现错误后,才需要关注这些属性。
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Ballance地图信息
|
||||||
|
|
||||||
|
Ballance地图信息目前只有一个选项,Sector(地图小节数)。此属性指示了当前地图的最终期望小节数。这个属性主要是用于解决导出地图Bug的,具体Bug内容可参考导入导出Virtools文档章节。
|
||||||
|
|
||||||
|
你需要做的唯一一件事就是在导出最终地图前检查此字段是否是你期望的小节数。需要注意的是,尽管你可以在创建地图的一开始就设置此字段,然而BBP中的其它一些功能可能会修改此字段,比如:添加机关,导入Virtools文档等。例如当你的地图小节指定为3,你正在添加一个归属于第4小节的机关,那么此值会自动增加到4。同理,在导入一个总共4小节的Ballance地图时,此值也会增加到4(如果先前值小于4的话)。这主要是为了用户可以在没有感知到此值的情况下使用此插件,尤其是在对某些现有地图进行略微修改时。然而如此操作,可能会在某些情况下不能满足用户的需求,所以仍建议你在导出前检查此字段。
|
||||||
|
|
||||||
|
## Ballance机关
|
||||||
|
|
||||||
|
Ballance机关记录了你所有使用BBP添加机关功能添加的机关的网格。在3D文件中,通常而言网格占据了最大的数据量,因此减少网格的数量,即通过在各个相同形状的物体之间共享网格,可以大幅减少地图文件所占用的大小。当你使用BBP添加机关相关功能时,会首先尝试从这里获取机关的网格,如果没有对应网格,则加载并记录在这里。
|
||||||
|
|
||||||
|
这个面板只供查看,不可编辑。当你不小心修改了BBP添加的机关的网格(它们本不该被修改)后,想要恢复其原有形状时,只需点击`Reset Ballance Elements`即可将列表中的所有机关网格重置为正确状态。
|
||||||
|
|
||||||
|
## BME材质
|
||||||
|
|
||||||
|
与Ballance机关类似,记录了使用BME添加路面时使用的材质。这也是为了可以复用材质而设计的,这样就不需要每创建一个物体就创建一套与之相关的材质,大大减少了重复材质的数量。
|
||||||
|
|
||||||
|
这个面板只供查看,不可编辑。当你不小心修改了BME相关的材质(它们本不该被修改)后,想要恢复其原有材质时,只需点击`Reset BME Materials`即可将列表中的所有材质重置为正确状态。
|
@ -20,7 +20,11 @@ BBP内置了一系列自定义图标,以及其组件BME需要的用于描述
|
|||||||
|
|
||||||
## 打包
|
## 打包
|
||||||
|
|
||||||
将`bbp_ng`文件夹压缩成ZIP文件即可完成打包工作。需要注意的是下列文件或文件夹不应被打包:
|
从Blender 4.2 LTS开始,插件使用Blender自带的打包功能进行打包。
|
||||||
|
|
||||||
|
假定在项目根目录下执行命令,最终输出文件为`redist/bbp_ng.zip`,那么在命令行窗口中执行`blender --command extension build --source-dir bbp_ng --output-filepath redist/bbp_ng.zip`命令即可完成打包。其中`blender`为Blender的可执行程序。
|
||||||
|
|
||||||
|
Blender会根据`blender_manifest.toml`的指示,在排除下列文件的情况下将插件打包:
|
||||||
|
|
||||||
* `bbp_ng/raw_icons`:原始图片文件夹。
|
* `bbp_ng/raw_icons`:原始图片文件夹。
|
||||||
* `bbp_ng/raw_jsons`:原始JSON文件夹。
|
* `bbp_ng/raw_jsons`:原始JSON文件夹。
|
||||||
@ -30,10 +34,6 @@ BBP内置了一系列自定义图标,以及其组件BME需要的用于描述
|
|||||||
* `bbp_ng/icons/.gitkeep`:文件夹占位符
|
* `bbp_ng/icons/.gitkeep`:文件夹占位符
|
||||||
* `bbp_ng/jsons/.gitkeep`:文件夹占位符
|
* `bbp_ng/jsons/.gitkeep`:文件夹占位符
|
||||||
|
|
||||||
打包后的ZIP文件打开后如果有且只有`bbp_ng`一个文件夹,则代表打包成功。切勿直接将`bbp_ng` **内部的文件** 直接打包到ZIP文件中。
|
|
||||||
|
|
||||||
这样打包后的ZIP文件既可以直接通过Blender插件的安装功能直接安装,也可以解压在插件目录下完成安装。
|
|
||||||
|
|
||||||
## 生成帮助文档
|
## 生成帮助文档
|
||||||
|
|
||||||
虽然本项目会利用GitHub Page功能提供帮助文档,但有时你可能需要提供帮助文档的离线版本,本节将会介绍如何生成离线版本的帮助文档。
|
虽然本项目会利用GitHub Page功能提供帮助文档,但有时你可能需要提供帮助文档的离线版本,本节将会介绍如何生成离线版本的帮助文档。
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
## 打开配置面板
|
## 打开配置面板
|
||||||
|
|
||||||
开启Blender,选择`Edit - Preferences`,在打开的窗口中转到`Add-ons`选项卡,在`Community`分类下找到BBP插件,名称为`Object: Ballance Blender Plugin`。请确保其左侧的勾已被选中,代表插件已被启用。点击勾左侧的三角箭头展开插件详细信息,如图所示,进入配置面板。
|
开启Blender,选择`Edit - Preferences`,在打开的窗口中转到`Add-ons`选项卡,在列表中找到BBP插件,其名称为`Ballance Blender Plugin`。请确保其左侧的勾已被选中,代表插件已被启用。点击勾左侧的三角箭头展开插件详细信息,如图所示,进入配置面板。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -46,3 +46,10 @@ Global Texture Save Option(全局贴图保存选项)决定了那些设置了
|
|||||||
|
|
||||||
Use Compress(使用压缩)属性指定保存的文档是否压缩存储。压缩可以显著减少文档体积,且在现代计算机平台上,压缩所造成的性能损失几乎可以忽略不计。当选择使用压缩后,一个额外的Compress Level(压缩等级)属性将会显示,用于指定压缩的级别,数值越高,压缩率越大,文件越小。
|
Use Compress(使用压缩)属性指定保存的文档是否压缩存储。压缩可以显著减少文档体积,且在现代计算机平台上,压缩所造成的性能损失几乎可以忽略不计。当选择使用压缩后,一个额外的Compress Level(压缩等级)属性将会显示,用于指定压缩的级别,数值越高,压缩率越大,文件越小。
|
||||||
|
|
||||||
|
### Ballance参数
|
||||||
|
|
||||||
|
Ballance Params(Ballance参数)章节包含针对Ballance特有内容,对导出过程进行优化的参数。
|
||||||
|
|
||||||
|
Successive Sector(小节连续)是一个解决导出小节组时出现的Bug的选项。由于某些原因,如果一个小节中没有任何机关(实际上是某小节组中没有归入任何物体),导出插件会认为该小节组不存在,因而遗漏导出。且由于Ballance对最终小节,即飞船出现小节的判定是从1开始递增寻找最后一个存在的小节组,所以二者叠加,会导致Ballance错误地认定地图的小节数,从而在错误的小节显示飞船,这也就是导出Bug。当勾选此选项后,导出文档时会预先按照当前Blender文件中Ballance地图信息中指定的小节数预先创建所有小节组,然后再进行导出,这样就不会遗漏创建某些小节组,飞船也会在正确的小节显示。
|
||||||
|
|
||||||
|
这个选项通常在导出可游玩的地图时选中,如果你只是想导出一些模型,那么需要关闭此选项,否则会在最终文件中产生许多无用的小节组。
|
||||||
|
@ -16,6 +16,7 @@ Ballance Blender Plugin(后文简称BBP)是一款关注Ballance自制地图
|
|||||||
## 特性
|
## 特性
|
||||||
|
|
||||||
* [Virtools属性](./virtools-properties.md)
|
* [Virtools属性](./virtools-properties.md)
|
||||||
|
* [Ballance属性](./ballance-properties.md)
|
||||||
* [导入导出Virtools文档](./import-export-virtools.md)
|
* [导入导出Virtools文档](./import-export-virtools.md)
|
||||||
* [按组操作](./group-operations.md)
|
* [按组操作](./group-operations.md)
|
||||||
* [传统对齐](./legacy-align.md)
|
* [传统对齐](./legacy-align.md)
|
||||||
|
@ -2,19 +2,26 @@
|
|||||||
|
|
||||||
## 明确版本
|
## 明确版本
|
||||||
|
|
||||||
BBP对Blender支持的原则是支持当前最新的 **LTS** 版本,在最新的LTS版本释出之后会花一些时间迁移插件。当前插件版本 **4.0**,基于Blender **3.6.x** 版本。
|
BBP对Blender支持的原则是支持当前最新的 **LTS** 版本,在最新的LTS版本释出之后会花一些时间迁移插件。当前插件版本 **4.0**,基于Blender **4.2.x** 版本。
|
||||||
|
|
||||||
理论上而言,如果Blender没有做出重大改动,那么BBP可以在其它版本上正常运行。例如你可以尝试在Blender 4.0上运行基于Blender 3.6的BBP插件。但BBP的开发者不会处理仅在非LTS版本中才出现的Bug。在安装插件之前,请先选择适合的版本。
|
理论上而言,如果Blender没有做出重大改动,那么BBP可以在其它版本上正常运行。例如你可以尝试在Blender 4.0上运行基于Blender 3.6 LTS的BBP插件。但BBP的开发者不会处理仅在非LTS版本中才出现的Bug。在安装插件之前,请先选择适合的版本。
|
||||||
|
|
||||||
## 卸载旧插件
|
## 卸载旧插件
|
||||||
|
|
||||||
如果你之前使用过BBP,那么你需要首先卸载它。旧版的BBP通常被安装在下列的位置中:
|
如果你之前使用过BBP,那么你需要首先卸载它。旧版的BBP通常被安装在下列的位置中:
|
||||||
|
|
||||||
* `Blender/3.6/scripts/addons/ballance_blender_plugin`
|
* `<Blender>/3.6/scripts/addons/ballance_blender_plugin`:BBP 3.0或更低版本
|
||||||
* `Blender/3.6/scripts/addons_contrib/ballance_blender_plugin`
|
* `<Blender>/3.6/scripts/addons_contrib/ballance_blender_plugin`:BBP 3.0或更低版本
|
||||||
* `Blender/3.6/scripts/addons/bbp_ng`
|
* `<Blender>/3.6/scripts/addons/bbp_ng`:BBP 4.0内测版本
|
||||||
|
* `%APPDATA%/Blender Foundation/Blender/3.6/scripts/addons/bbp_ng`:BBP 4.0内测版本
|
||||||
|
* `%APPDATA%/Blender Foundation/Blender/4.2/extensions/user_default/bbp_ng`:BBP 4.0或更高版本
|
||||||
|
|
||||||
你只需要删除这些文件夹(如果它们存在的话)即可完全卸载插件。路径中的`Blender`指代你的Blender安装位置。路径中的`3.6`是你安装的Blender的版本号,需要根据你安装的版本进行调整,本手册均以`3.6`为例。
|
你只需要先在Blender中关闭插件(把插件名前面的勾取消),然后再删除这些文件夹(如果它们存在的话)即可完全卸载插件。路径中的`<Blender>`指代你的Blender安装位置。路径中的`3.6`和`4.2`是你安装的Blender的版本号,需要根据你安装的版本进行调整,后续出现的版本号也按此理解。
|
||||||
|
|
||||||
|
!!! warning "不能使用Blender的插件卸载功能"
|
||||||
|
不能使用Blender插件页面的插件卸载功能卸载BBP,因为BBP只要被Blender加载(无论是否启用),都会将Virtools文件读写库BMap加载进Blender。若在Blender运行期间删除,会出现拒绝访问错误。因此您必须在关闭Blender后手动删除插件目录。
|
||||||
|
|
||||||
|
如果您实在无法确定插件安装到了哪里,可以在Blender的偏好设置中的插件页面里找到`File`属性,其指向文件所在的文件夹就是要删除的文件夹。
|
||||||
|
|
||||||
!!! info "`ballance_blender_plugin`和`bbp_ng`"
|
!!! info "`ballance_blender_plugin`和`bbp_ng`"
|
||||||
`ballance_blender_plugin`是旧版BBP插件(4.0版本前)的模块名,`bbp_ng`是新版BBP插件(4.0版本后,包括4.0版本)的模块名。为了保证用户确实删除了旧版插件,所以同时提供了这两者。
|
`ballance_blender_plugin`是旧版BBP插件(4.0版本前)的模块名,`bbp_ng`是新版BBP插件(4.0版本后,包括4.0版本)的模块名。为了保证用户确实删除了旧版插件,所以同时提供了这两者。
|
||||||
@ -22,6 +29,9 @@ BBP对Blender支持的原则是支持当前最新的 **LTS** 版本,在最新
|
|||||||
!!! info "`addons`和`addons_contrib`"
|
!!! info "`addons`和`addons_contrib`"
|
||||||
在Blender 3.6 LTS版本,即BBP 3.3版本之后,Blender不再支持Testing类型插件。因而导致安装Testing插件专用的`addons_contrib`文件夹不再使用,插件需要被统一安装在`addons`中。为了保证用户确实删除了旧版插件,所以同时提供了这两者。
|
在Blender 3.6 LTS版本,即BBP 3.3版本之后,Blender不再支持Testing类型插件。因而导致安装Testing插件专用的`addons_contrib`文件夹不再使用,插件需要被统一安装在`addons`中。为了保证用户确实删除了旧版插件,所以同时提供了这两者。
|
||||||
|
|
||||||
|
!!! info "`addons`和`extensions`"
|
||||||
|
在Blender 4.2 LTS版本,Blender使用扩展(Extensions)而非插件(Addons)来描述插件。因而导致安装插件的位置也发生了变化。为了保证用户确实删除了旧版插件,所以同时提供了这两者。
|
||||||
|
|
||||||
## 下载插件
|
## 下载插件
|
||||||
|
|
||||||
你可以通过[本工程的GitHub代码库的Release页面](https://github.com/yyc12345/BallanceBlenderHelper/releases)下载最新的插件。插件是以ZIP压缩包形式提供的。
|
你可以通过[本工程的GitHub代码库的Release页面](https://github.com/yyc12345/BallanceBlenderHelper/releases)下载最新的插件。插件是以ZIP压缩包形式提供的。
|
||||||
@ -36,11 +46,11 @@ BBP对Blender支持的原则是支持当前最新的 **LTS** 版本,在最新
|
|||||||
|
|
||||||
## 安装插件
|
## 安装插件
|
||||||
|
|
||||||
开启Blender,选择`Edit - Preferences`,在打开的窗口中转到`Add-ons`选项卡,点击`Install...`按钮,选择刚刚下载完毕的ZIP压缩包,即可安装完成。若没有在列表中看到可选择刷新按钮或重启Blender。
|
开启Blender,选择`Edit - Preferences`,在打开的窗口中转到`Add-ons`选项卡,点击窗口右上方的箭头,然后点击`Install from Disk...`按钮,选择刚刚下载完毕的ZIP压缩包,即可安装完成。若没有在列表中看到可选择刷新按钮或重启Blender。
|
||||||
|
|
||||||
你也可以选择手动安装插件(如果上述安装方法失败了的话),转到`Blender/3.6/scripts/addons`,将下载好的ZIP压缩包内容解压到此文件夹下,启动Blender,即可在插件列表中找到BBP。
|
你也可以选择手动安装插件(如果上述安装方法失败了的话),转到`%APPDATA%/Blender Foundation/Blender/4.2/extensions/user_default`,创建一个名为`bbp_ng`的文件夹并进入,将下载好的ZIP压缩包内容解压到此文件夹下,启动Blender,即可在插件列表中找到BBP。
|
||||||
|
|
||||||
BBP插件位于`Community`类别下,名称为`Object: Ballance Blender Plugin`,找到后勾选名称左侧的勾即可启用插件。插件安装成功后的偏好设置页面如下图所示。
|
BBP插件在列表中的名称为`Ballance Blender Plugin`,找到后勾选名称左侧的勾即可启用插件。插件安装成功后的偏好设置页面如下图所示。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
## 使用方法
|
## 使用方法
|
||||||
|
|
||||||
传统对齐支持将多个物体对一个物体的对齐,操作方法是先依次选中需要被对齐的物体,然后在最后选中对齐参考对象(也就是使其称为活动物体),然后点击`Ballance - 3ds Max Align`,即可弹出传统对齐面板,之后便可开始对齐操作。
|
传统对齐支持将多个物体对一个物体的对齐,操作方法是先依次选中需要被对齐的物体,然后在最后选中对齐参考对象(也就是使其成为活动物体),然后点击`Ballance - 3ds Max Align`,即可弹出传统对齐面板,之后便可开始对齐操作。
|
||||||
|
|
||||||
## 面板介绍
|
## 面板介绍
|
||||||
|
|
||||||
|
@ -4,4 +4,9 @@
|
|||||||
* 制图工具链标准及`meshes`文件夹下的文件的格式:https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md
|
* 制图工具链标准及`meshes`文件夹下的文件的格式:https://github.com/yyc12345/gist/blob/master/BMFileSpec/YYCToolsChainSpec_ZH.md
|
||||||
* BMERevenge的JSON文件的格式:https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_v2.0_ZH.md
|
* BMERevenge的JSON文件的格式:https://github.com/yyc12345/gist/blob/master/BMERevenge/DevDocument_v2.0_ZH.md
|
||||||
|
|
||||||
本插件配合了`fake-bpy-module`模块来实现类型提示以加快开发速度。本插件目前基于Blender 3.6,因此使用`pip install fake-bpy-module-latest==20230627`来安装Blender的类型提示库。 这主要是因为`fake-bpy-module`没有发布官方的适用于Blender 3.6的包,因此我只能通过选择最接近Blender 3.6版本发布时间的每日编译版本来安装它。
|
本插件配合了`fake-bpy-module`模块来实现类型提示以加快开发速度。使用如下命令来安装Blender的类型提示库。
|
||||||
|
|
||||||
|
* Blender 3.6: `pip install fake-bpy-module-latest==20230627`
|
||||||
|
* Blender 4.2: `pip install fake-bpy-module-latest==20240716`
|
||||||
|
|
||||||
|
这么做主要是因为`fake-bpy-module`没有发布官方的适用于指定Blender版本的包,因此我只能通过选择最接近Blender对应版本发布时间的每日编译版本来安装它。
|
||||||
|
@ -52,7 +52,7 @@ BBP插件为所有Blender贴图(实际上是Image)添加了新的属性,
|
|||||||
|
|
||||||
## Virtools网格
|
## Virtools网格
|
||||||
|
|
||||||
BBP插件为所有Blender贴图(实际上是Image)添加了新的属性,称为Virtools Mesh。转到`Data`属性面板,即可以找到`Virtools Mesh`面板。
|
BBP插件为所有Blender网格添加了新的属性,称为Virtools Mesh。转到`Data`属性面板,即可以找到`Virtools Mesh`面板。
|
||||||
|
|
||||||

|

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