13 Commits

Author SHA1 Message Date
427bad4f6b fix: fix various trivial issues
- update blender_manifest.toml to the latest version and fix the issues raised by blender when packaging.
- use blender do packaging work. remove redist.py because blender_manifest.toml has gitignore like filter feature when packaging.
- update document about installing, configurating, building plugin for blender 4.2.
- update a document image for blender 4.2.
2024-07-19 15:25:38 +08:00
209d212287 fix: fix blender mesh writer issue.
- fix blender mesh writer with the reference of FBX Importer.
- ballance map importer now may works as expected (still need more review).
2024-07-18 20:23:21 +08:00
2271b0a621 fix: remove bl_info.
- remove bl_info in __init__.py because blender_manifest.toml has replace it.
2024-07-17 21:20:40 +08:00
6940428b88 fix: fix blender 4.2 mesh issue.
- fix issue that blender 4.2 lost use_auto_smooth and calc_split_normals. (it almost works but not perfect. still need more debugging)
- remove depracted material blend_mode. use modern one instead. (the modern setting make me consufes but it works)
2024-07-17 19:45:35 +08:00
aa602a7bb8 fix: update to blender 4.2 LTS
- update to Blender 4.2 LTS
- fix issue that can not find specular property in priniciple BSDF when applying virtools material.
- update blender manifest toml to let plugin works.
- there are various mesh works need to be fixed in later commits. this commit can not works normally on blender 4.2
2024-07-17 09:23:16 +08:00
8588f097a2 fix: change default screw steps to 28.
- change default screw steps to 28 (7 steps for 90 degree)
- update blender mesh annotation.
2024-05-25 20:32:30 +08:00
5d8ffb7e48 feat: add redist script
- add redist script for the convenience of building plugin package, especially for Blender 4.2
2024-05-21 16:19:13 +08:00
270fddff52 feat: do some trivial adaptions for blender 4.2 LTS
- add blender_manifest.toml
- add a removal note for bl_info in __init__.py.
- it doesn't mean that this plugin now can be used in blender 4.2. I just add some trivial changes which do not break current compatibility to reduce migration work in future.
2024-05-17 09:15:12 +08:00
084e7fbe61 feat: add extra transform for rail creation
- add extra transform for rail creation.
- remove scale from extra transform because it is rarely used. because Ballance can not handle the physicalization of scaled object.
2024-05-13 13:39:42 +08:00
190be6ec61 fix: restore extra transform to identy when entering bme operator. 2024-05-10 15:33:28 +08:00
36e925101e feat: extra transform properties for bme creation.
- add extra transform properties (translation, rotation and scale) for bme creation operator. this is good for "what you seen is what you gotten" and can increase user experience when add bme protorypes. please note these extra transform is applied to object after moving to cursor, not the mesh self.
- fix a weired bug that bme creation execute() may get outdated configurations when switch bme type in pop up window. the solution is simply adding an extra outdated flag checker at the head of execute(). but i have no spare time to research whether this patch is correct for this bug.
2024-05-10 14:44:37 +08:00
84e7e8380f feat: hide some operators in edit mode and add some notify for grouping oper.
- now grouping, ungrouping clear groups operators will show a report at the bottom of blender to indicate their work have done.
- disable fix material, 3ds max align, select by virtools group operators in edit mode to prevent any possible undefined behavior.
2024-04-22 21:06:41 +08:00
c58af8ce48 fix: fix face generation error of BME streets prototype. 2024-04-22 21:06:35 +08:00
20 changed files with 335 additions and 91 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
# disable distribution build folder # disable distribution build folder
redist/ redist/
!redist/.gitkeep

View File

@ -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)
) )

View File

@ -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, ))

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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():

View File

@ -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`,

View File

@ -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

View 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.
]

View File

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

View File

@ -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:

View File

@ -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:

View File

@ -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:
""" """

Binary file not shown.

Before

Width:  |  Height:  |  Size: 82 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -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功能提供帮助文档但有时你可能需要提供帮助文档的离线版本本节将会介绍如何生成离线版本的帮助文档。

View File

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

View File

@ -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`,找到后勾选名称左侧的勾即可启用插件。插件安装成功后的偏好设置页面如下图所示。
![](../imgs/config-plugin.png) ![](../imgs/config-plugin.png)

View File

@ -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对应版本发布时间的每日编译版本来安装它。