Compare commits
13 Commits
v4.0-alpha
...
v4.0-alpha
Author | SHA1 | Date | |
---|---|---|---|
427bad4f6b | |||
209d212287 | |||
2271b0a621 | |||
6940428b88 | |||
aa602a7bb8 | |||
8588f097a2 | |||
5d8ffb7e48 | |||
270fddff52 | |||
084e7fbe61 | |||
190be6ec61 | |||
36e925101e | |||
84e7e8380f | |||
c58af8ce48 |
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)
|
||||||
)
|
)
|
||||||
|
@ -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,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
|
||||||
|
@ -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():
|
||||||
|
@ -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`,
|
||||||
|
@ -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
|
||||||
|
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,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"
|
||||||
},
|
},
|
||||||
|
@ -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:
|
||||||
"""
|
"""
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 66 KiB |
@ -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`。请确保其左侧的勾已被选中,代表插件已被启用。点击勾左侧的三角箭头展开插件详细信息,如图所示,进入配置面板。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -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`,找到后勾选名称左侧的勾即可启用插件。插件安装成功后的偏好设置页面如下图所示。
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
|
@ -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对应版本发布时间的每日编译版本来安装它。
|
||||||
|
Reference in New Issue
Block a user