refactor: add 2 classes to refactor old code which raise too much erros by linter.
- add TinyMutex to resolve the issue that we can not operate the virtools group infos of 2 individual objects. * use this new class for all class need resource mutex, such as ballance element, bme materials and etc. - add CollectionVisitor to resolve that blender have bad document and type hint for runtime bpy.types.CollectionProperty. * now all visit to CollectionProperty are delegated by this class.
This commit is contained in:
parent
6ae8899912
commit
f4d3e48be2
@ -96,27 +96,29 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
|||||||
|
|
||||||
|
|
||||||
# init data collection
|
# init data collection
|
||||||
|
adder_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs]
|
||||||
|
adder_cfgs_visitor = UTIL_functions.CollectionVisitor(self.bme_struct_cfgs)
|
||||||
# clear first
|
# clear first
|
||||||
self.bme_struct_cfgs.clear()
|
adder_cfgs_visitor.clear()
|
||||||
# create enough entries specified by gotten cfgs
|
# create enough entries specified by gotten cfgs
|
||||||
for _ in range(max(counter_int, counter_float, counter_bool)):
|
for _ in range(max(counter_int, counter_float, counter_bool)):
|
||||||
self.bme_struct_cfgs.add()
|
adder_cfgs_visitor.add()
|
||||||
|
|
||||||
# assign default value
|
# assign default value
|
||||||
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
|
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
|
||||||
# show prop differently by cfg type
|
# show prop differently by cfg type
|
||||||
match(cfg.get_type()):
|
match(cfg.get_type()):
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
||||||
self.bme_struct_cfgs[cfg_index].prop_int = cfg.get_default()
|
adder_cfgs_visitor[cfg_index].prop_int = cfg.get_default()
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
||||||
self.bme_struct_cfgs[cfg_index].prop_float = cfg.get_default()
|
adder_cfgs_visitor[cfg_index].prop_float = cfg.get_default()
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
||||||
self.bme_struct_cfgs[cfg_index].prop_bool = cfg.get_default()
|
adder_cfgs_visitor[cfg_index].prop_bool = cfg.get_default()
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
||||||
# face is just 6 bool
|
# face is just 6 bool
|
||||||
default_values: tuple[bool, ...] = cfg.get_default()
|
default_values: tuple[bool, ...] = cfg.get_default()
|
||||||
for i in range(6):
|
for i in range(6):
|
||||||
self.bme_struct_cfgs[cfg_index + i].prop_bool = default_values[i]
|
adder_cfgs_visitor[cfg_index + i].prop_bool = default_values[i]
|
||||||
|
|
||||||
# reset outdated flag
|
# reset outdated flag
|
||||||
self.outdated_flag = False
|
self.outdated_flag = False
|
||||||
@ -182,20 +184,23 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
|||||||
# call internal updator
|
# call internal updator
|
||||||
self.__internal_update_bme_struct_type()
|
self.__internal_update_bme_struct_type()
|
||||||
|
|
||||||
|
# create cfg visitor
|
||||||
|
adder_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs]
|
||||||
|
adder_cfgs_visitor = UTIL_functions.CollectionVisitor(self.bme_struct_cfgs)
|
||||||
# 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:
|
||||||
match(cfg.get_type()):
|
match(cfg.get_type()):
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
||||||
cfgs[cfg.get_field()] = self.bme_struct_cfgs[cfg_index].prop_int
|
cfgs[cfg.get_field()] = adder_cfgs_visitor[cfg_index].prop_int
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
||||||
cfgs[cfg.get_field()] = self.bme_struct_cfgs[cfg_index].prop_float
|
cfgs[cfg.get_field()] = adder_cfgs_visitor[cfg_index].prop_float
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
||||||
cfgs[cfg.get_field()] = self.bme_struct_cfgs[cfg_index].prop_bool
|
cfgs[cfg.get_field()] = adder_cfgs_visitor[cfg_index].prop_bool
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
||||||
# face is just 6 bool tuple
|
# face is just 6 bool tuple
|
||||||
cfgs[cfg.get_field()] = tuple(
|
cfgs[cfg.get_field()] = tuple(
|
||||||
self.bme_struct_cfgs[cfg_index + i].prop_bool for i in range(6)
|
adder_cfgs_visitor[cfg_index + i].prop_bool for i in range(6)
|
||||||
)
|
)
|
||||||
|
|
||||||
# call general creator
|
# call general creator
|
||||||
@ -225,6 +230,9 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
|||||||
# show type
|
# show type
|
||||||
layout.prop(self, 'bme_struct_type')
|
layout.prop(self, 'bme_struct_type')
|
||||||
|
|
||||||
|
# create cfg visitor
|
||||||
|
adder_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs]
|
||||||
|
adder_cfgs_visitor = UTIL_functions.CollectionVisitor(self.bme_struct_cfgs)
|
||||||
# visit cfgs cache list to show cfg
|
# visit cfgs cache list to show cfg
|
||||||
layout.label(text = "Prototype Configurations:")
|
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:
|
||||||
@ -238,24 +246,24 @@ class BBP_OT_add_bme_struct(bpy.types.Operator):
|
|||||||
# show prop differently by cfg type
|
# show prop differently by cfg type
|
||||||
match(cfg.get_type()):
|
match(cfg.get_type()):
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer:
|
||||||
box_layout.prop(self.bme_struct_cfgs[cfg_index], 'prop_int', text = '')
|
box_layout.prop(adder_cfgs_visitor[cfg_index], 'prop_int', text = '')
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Float:
|
||||||
box_layout.prop(self.bme_struct_cfgs[cfg_index], 'prop_float', text = '')
|
box_layout.prop(adder_cfgs_visitor[cfg_index], 'prop_float', text = '')
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean:
|
||||||
box_layout.prop(self.bme_struct_cfgs[cfg_index], 'prop_bool', text = '')
|
box_layout.prop(adder_cfgs_visitor[cfg_index], 'prop_bool', text = '')
|
||||||
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
case UTIL_bme.PrototypeShowcaseCfgsTypes.Face:
|
||||||
# face will show a special layout (grid view)
|
# face will show a special layout (grid view)
|
||||||
grids = box_layout.grid_flow(
|
grids = box_layout.grid_flow(
|
||||||
row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
|
row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
|
||||||
grids.alignment = 'CENTER'
|
grids.alignment = 'CENTER'
|
||||||
grids.separator()
|
grids.separator()
|
||||||
grids.prop(self.bme_struct_cfgs[cfg_index + 0], 'prop_bool', text = 'Top') # top
|
grids.prop(adder_cfgs_visitor[cfg_index + 0], 'prop_bool', text = 'Top') # top
|
||||||
grids.prop(self.bme_struct_cfgs[cfg_index + 2], 'prop_bool', text = 'Front') # front
|
grids.prop(adder_cfgs_visitor[cfg_index + 2], 'prop_bool', text = 'Front') # front
|
||||||
grids.prop(self.bme_struct_cfgs[cfg_index + 4], 'prop_bool', text = 'Left') # left
|
grids.prop(adder_cfgs_visitor[cfg_index + 4], 'prop_bool', text = 'Left') # left
|
||||||
grids.label(text = '', icon = 'CUBE') # show a 3d cube as icon
|
grids.label(text = '', icon = 'CUBE') # show a 3d cube as icon
|
||||||
grids.prop(self.bme_struct_cfgs[cfg_index + 5], 'prop_bool', text = 'Right') # right
|
grids.prop(adder_cfgs_visitor[cfg_index + 5], 'prop_bool', text = 'Right') # right
|
||||||
grids.prop(self.bme_struct_cfgs[cfg_index + 3], 'prop_bool', text = 'Back') # back
|
grids.prop(adder_cfgs_visitor[cfg_index + 3], 'prop_bool', text = 'Back') # back
|
||||||
grids.prop(self.bme_struct_cfgs[cfg_index + 1], 'prop_bool', text = 'Bottom') # bottom
|
grids.prop(adder_cfgs_visitor[cfg_index + 1], 'prop_bool', text = 'Bottom') # bottom
|
||||||
grids.separator()
|
grids.separator()
|
||||||
|
|
||||||
# show extra transform props
|
# show extra transform props
|
||||||
|
@ -84,11 +84,13 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
|
|
||||||
# check whether add new entry
|
# check whether add new entry
|
||||||
# if no selected axis, this alignment is invalid
|
# if no selected axis, this alignment is invalid
|
||||||
entry: BBP_PG_legacy_align_history = self.align_history[-1]
|
histories: UTIL_functions.CollectionVisitor[BBP_PG_legacy_align_history]
|
||||||
|
histories = UTIL_functions.CollectionVisitor(self.align_history)
|
||||||
|
entry: BBP_PG_legacy_align_history = histories[-1]
|
||||||
if entry.align_x == True or entry.align_y == True or entry.align_z == True:
|
if entry.align_x == True or entry.align_y == True or entry.align_z == True:
|
||||||
# valid one
|
# valid one
|
||||||
# add a new entry in history
|
# add a new entry in history
|
||||||
self.align_history.add()
|
histories.add()
|
||||||
else:
|
else:
|
||||||
# invalid one
|
# invalid one
|
||||||
# reset all data to default
|
# reset all data to default
|
||||||
@ -127,9 +129,11 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
return _check_align_requirement()
|
return _check_align_requirement()
|
||||||
|
|
||||||
def invoke(self, context, event):
|
def invoke(self, context, event):
|
||||||
|
histories: UTIL_functions.CollectionVisitor[BBP_PG_legacy_align_history]
|
||||||
|
histories = UTIL_functions.CollectionVisitor(self.align_history)
|
||||||
# clear history and add 1 entry for following functions
|
# clear history and add 1 entry for following functions
|
||||||
self.align_history.clear()
|
histories.clear()
|
||||||
self.align_history.add()
|
histories.add()
|
||||||
# run execute() function
|
# run execute() function
|
||||||
return self.execute(context)
|
return self.execute(context)
|
||||||
|
|
||||||
@ -145,8 +149,9 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
# If you place it at the end of this function, it doesn't work.
|
# If you place it at the end of this function, it doesn't work.
|
||||||
context.view_layer.update()
|
context.view_layer.update()
|
||||||
# iterate history to align objects
|
# iterate history to align objects
|
||||||
entry: BBP_PG_legacy_align_history
|
histories: UTIL_functions.CollectionVisitor[BBP_PG_legacy_align_history]
|
||||||
for entry in self.align_history:
|
histories = UTIL_functions.CollectionVisitor(self.align_history)
|
||||||
|
for entry in histories:
|
||||||
_align_objects(
|
_align_objects(
|
||||||
current_obj, target_objs,
|
current_obj, target_objs,
|
||||||
entry.align_x, entry.align_y, entry.align_z,
|
entry.align_x, entry.align_y, entry.align_z,
|
||||||
@ -157,7 +162,9 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
|
|
||||||
def draw(self, context):
|
def draw(self, context):
|
||||||
# get last entry in history to show
|
# get last entry in history to show
|
||||||
entry: BBP_PG_legacy_align_history = self.align_history[-1]
|
histories: UTIL_functions.CollectionVisitor[BBP_PG_legacy_align_history]
|
||||||
|
histories = UTIL_functions.CollectionVisitor(self.align_history)
|
||||||
|
entry: BBP_PG_legacy_align_history = histories[-1]
|
||||||
|
|
||||||
layout = self.layout
|
layout = self.layout
|
||||||
col = layout.column()
|
col = layout.column()
|
||||||
@ -183,7 +190,7 @@ class BBP_OT_legacy_align(bpy.types.Operator):
|
|||||||
conditional_disable_area.enabled = entry.align_x == True or entry.align_y == True or entry.align_z == True
|
conditional_disable_area.enabled = entry.align_x == True or entry.align_y == True or entry.align_z == True
|
||||||
# show apply and counter
|
# show apply and counter
|
||||||
conditional_disable_area.prop(self, 'apply_flag', text = 'Apply', icon = 'CHECKMARK', toggle = 1)
|
conditional_disable_area.prop(self, 'apply_flag', text = 'Apply', icon = 'CHECKMARK', toggle = 1)
|
||||||
conditional_disable_area.label(text = f'Total {len(self.align_history) - 1} applied alignments')
|
conditional_disable_area.label(text = f'Total {len(histories) - 1} applied alignments')
|
||||||
|
|
||||||
#region Core Functions
|
#region Core Functions
|
||||||
|
|
||||||
|
@ -27,15 +27,10 @@ class BBP_OT_snoop_group_then_to_mesh(bpy.types.Operator):
|
|||||||
if bevel_obj is None: continue
|
if bevel_obj is None: continue
|
||||||
|
|
||||||
# copy bevel object group info into current object
|
# copy bevel object group info into current object
|
||||||
# MARK: VirtoolsGroupsHelper is self-mutex.
|
|
||||||
# we only can operate one VirtoolsGroupsHelper at the same time.
|
|
||||||
# so we extract bevel object group infos then apply to target later.
|
|
||||||
group_infos: tuple[str, ...]
|
|
||||||
with PROP_virtools_group.VirtoolsGroupsHelper(bevel_obj) as bevel_gp:
|
|
||||||
group_infos = tuple(bevel_gp.iterate_groups())
|
|
||||||
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as this_gp:
|
with PROP_virtools_group.VirtoolsGroupsHelper(obj) as this_gp:
|
||||||
this_gp.clear_groups()
|
this_gp.clear_groups()
|
||||||
this_gp.add_groups(group_infos)
|
with PROP_virtools_group.VirtoolsGroupsHelper(bevel_obj) as bevel_gp:
|
||||||
|
this_gp.add_groups(bevel_gp.iterate_groups())
|
||||||
|
|
||||||
# convert all selected object to mesh
|
# convert all selected object to mesh
|
||||||
# no matter the success of copying virtools group infos and whether selected object is curve
|
# no matter the success of copying virtools group infos and whether selected object is curve
|
||||||
|
@ -104,8 +104,8 @@ class BBP_PG_ballance_element(bpy.types.PropertyGroup):
|
|||||||
type = bpy.types.Mesh
|
type = bpy.types.Mesh
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
def get_ballance_elements(scene: bpy.types.Scene) -> bpy.types.CollectionProperty:
|
def get_ballance_elements(scene: bpy.types.Scene) -> UTIL_functions.CollectionVisitor[BBP_PG_ballance_element]:
|
||||||
return scene.ballance_elements
|
return UTIL_functions.CollectionVisitor(scene.ballance_elements)
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -224,7 +224,7 @@ class BallanceElementsHelper():
|
|||||||
This class should only have 1 instance at the same time. This class support `with` syntax to achieve this.
|
This class should only have 1 instance at the same time. This class support `with` syntax to achieve this.
|
||||||
This class frequently used in importing stage to create element placeholder.
|
This class frequently used in importing stage to create element placeholder.
|
||||||
"""
|
"""
|
||||||
__mSingletonMutex: typing.ClassVar[bool] = False
|
__mSingletonMutex: typing.ClassVar[UTIL_functions.TinyMutex[bpy.types.Scene]] = UTIL_functions.TinyMutex()
|
||||||
__mIsValid: bool
|
__mIsValid: bool
|
||||||
__mAssocScene: bpy.types.Scene
|
__mAssocScene: bpy.types.Scene
|
||||||
__mElementMap: dict[BallanceElementType, bpy.types.Mesh]
|
__mElementMap: dict[BallanceElementType, bpy.types.Mesh]
|
||||||
@ -232,14 +232,11 @@ class BallanceElementsHelper():
|
|||||||
def __init__(self, assoc: bpy.types.Scene):
|
def __init__(self, assoc: bpy.types.Scene):
|
||||||
self.__mElementMap = {}
|
self.__mElementMap = {}
|
||||||
self.__mAssocScene = assoc
|
self.__mAssocScene = assoc
|
||||||
|
self.__mIsValid = False
|
||||||
|
|
||||||
# check singleton
|
# check singleton
|
||||||
if BallanceElementsHelper.__mSingletonMutex:
|
BallanceElementsHelper.__mSingletonMutex.lock(self.__mAssocScene)
|
||||||
self.__mIsValid = False
|
|
||||||
raise UTIL_functions.BBPException('BallanceElementsHelper is mutex.')
|
|
||||||
|
|
||||||
# set validation and read ballance elements property
|
# set validation and read ballance elements property
|
||||||
BallanceElementsHelper.__mSingletonMutex = True
|
|
||||||
self.__mIsValid = True
|
self.__mIsValid = True
|
||||||
self.__read_from_ballance_element()
|
self.__read_from_ballance_element()
|
||||||
|
|
||||||
@ -257,7 +254,7 @@ class BallanceElementsHelper():
|
|||||||
# write to ballance elements property and reset validation
|
# write to ballance elements property and reset validation
|
||||||
self.__write_to_ballance_elements()
|
self.__write_to_ballance_elements()
|
||||||
self.__mIsValid = False
|
self.__mIsValid = False
|
||||||
BallanceElementsHelper.__mSingletonMutex = False
|
BallanceElementsHelper.__mSingletonMutex.unlock(self.__mAssocScene)
|
||||||
|
|
||||||
def get_element(self, element_type: BallanceElementType) -> bpy.types.Mesh:
|
def get_element(self, element_type: BallanceElementType) -> bpy.types.Mesh:
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@ -276,8 +273,16 @@ class BallanceElementsHelper():
|
|||||||
self.__mElementMap[element_type] = new_mesh
|
self.__mElementMap[element_type] = new_mesh
|
||||||
return new_mesh
|
return new_mesh
|
||||||
|
|
||||||
|
def reset_elements(self) -> None:
|
||||||
|
if not self.is_valid():
|
||||||
|
raise UTIL_functions.BBPException('calling invalid BallanceElementsHelper')
|
||||||
|
|
||||||
|
# reload all items
|
||||||
|
for elety, elemesh in self.__mElementMap.items():
|
||||||
|
_load_element(elemesh, elety)
|
||||||
|
|
||||||
def __write_to_ballance_elements(self) -> None:
|
def __write_to_ballance_elements(self) -> None:
|
||||||
elements: bpy.types.CollectionProperty = get_ballance_elements(self.__mAssocScene)
|
elements = get_ballance_elements(self.__mAssocScene)
|
||||||
elements.clear()
|
elements.clear()
|
||||||
|
|
||||||
for elety, elemesh in self.__mElementMap.items():
|
for elety, elemesh in self.__mElementMap.items():
|
||||||
@ -286,10 +291,9 @@ class BallanceElementsHelper():
|
|||||||
item.mesh_ptr = elemesh
|
item.mesh_ptr = elemesh
|
||||||
|
|
||||||
def __read_from_ballance_element(self) -> None:
|
def __read_from_ballance_element(self) -> None:
|
||||||
elements: bpy.types.CollectionProperty = get_ballance_elements(self.__mAssocScene)
|
elements = get_ballance_elements(self.__mAssocScene)
|
||||||
self.__mElementMap.clear()
|
self.__mElementMap.clear()
|
||||||
|
|
||||||
item: BBP_PG_ballance_element
|
|
||||||
for item in elements:
|
for item in elements:
|
||||||
# check requirements
|
# check requirements
|
||||||
if item.mesh_ptr is None: continue
|
if item.mesh_ptr is None: continue
|
||||||
@ -299,30 +303,6 @@ class BallanceElementsHelper():
|
|||||||
# add into map
|
# add into map
|
||||||
self.__mElementMap[element_type] = item.mesh_ptr
|
self.__mElementMap[element_type] = item.mesh_ptr
|
||||||
|
|
||||||
def reset_ballance_elements(scene: bpy.types.Scene) -> None:
|
|
||||||
invalid_idx: list[int] = []
|
|
||||||
elements: bpy.types.CollectionProperty = get_ballance_elements(scene)
|
|
||||||
|
|
||||||
# re-load all elements
|
|
||||||
index: int = 0
|
|
||||||
item: BBP_PG_ballance_element
|
|
||||||
for item in elements:
|
|
||||||
elety: BallanceElementType | None = get_ballance_element_type_from_id(item.element_id)
|
|
||||||
|
|
||||||
# load or record invalid entry
|
|
||||||
if elety is None or item.mesh_ptr is None:
|
|
||||||
invalid_idx.append(index)
|
|
||||||
else:
|
|
||||||
_load_element(item.mesh_ptr, elety)
|
|
||||||
|
|
||||||
# inc counter
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
# remove invalid one with reversed order
|
|
||||||
invalid_idx.reverse()
|
|
||||||
for idx in invalid_idx:
|
|
||||||
elements.remove(idx)
|
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Ballance Elements Representation
|
#region Ballance Elements Representation
|
||||||
@ -348,7 +328,8 @@ class BBP_OT_reset_ballance_elements(bpy.types.Operator):
|
|||||||
return context.scene is not None
|
return context.scene is not None
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
reset_ballance_elements(context.scene)
|
with BallanceElementsHelper(context.scene) as helper:
|
||||||
|
helper.reset_elements()
|
||||||
# show a window to let user know, not silence
|
# show a window to let user know, not silence
|
||||||
UTIL_functions.message_box(
|
UTIL_functions.message_box(
|
||||||
('Reset OK.', ),
|
('Reset OK.', ),
|
||||||
|
@ -85,8 +85,8 @@ class BBP_PG_bme_material(bpy.types.PropertyGroup):
|
|||||||
type = bpy.types.Material
|
type = bpy.types.Material
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
def get_bme_materials(scene: bpy.types.Scene) -> bpy.types.CollectionProperty:
|
def get_bme_materials(scene: bpy.types.Scene) -> UTIL_functions.CollectionVisitor[BBP_PG_bme_material]:
|
||||||
return scene.bme_materials
|
return UTIL_functions.CollectionVisitor(scene.bme_materials)
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ class BMEMaterialsHelper():
|
|||||||
This class should only have 1 instance at the same time. This class support `with` syntax to achieve this.
|
This class should only have 1 instance at the same time. This class support `with` syntax to achieve this.
|
||||||
This class frequently used in creating BME meshes.
|
This class frequently used in creating BME meshes.
|
||||||
"""
|
"""
|
||||||
__mSingletonMutex: typing.ClassVar[bool] = False
|
__mSingletonMutex: typing.ClassVar[UTIL_functions.TinyMutex[bpy.types.Scene]] = UTIL_functions.TinyMutex()
|
||||||
__mIsValid: bool
|
__mIsValid: bool
|
||||||
__mAssocScene: bpy.types.Scene
|
__mAssocScene: bpy.types.Scene
|
||||||
__mMaterialMap: dict[str, bpy.types.Material]
|
__mMaterialMap: dict[str, bpy.types.Material]
|
||||||
@ -134,14 +134,11 @@ class BMEMaterialsHelper():
|
|||||||
def __init__(self, assoc: bpy.types.Scene):
|
def __init__(self, assoc: bpy.types.Scene):
|
||||||
self.__mMaterialMap = {}
|
self.__mMaterialMap = {}
|
||||||
self.__mAssocScene = assoc
|
self.__mAssocScene = assoc
|
||||||
|
self.__mIsValid = False
|
||||||
|
|
||||||
# check singleton
|
# check singleton
|
||||||
if BMEMaterialsHelper.__mSingletonMutex:
|
BMEMaterialsHelper.__mSingletonMutex.lock(self.__mAssocScene)
|
||||||
self.__mIsValid = False
|
|
||||||
raise UTIL_functions.BBPException('BMEMaterialsHelper is mutex.')
|
|
||||||
|
|
||||||
# set validation and read ballance elements property
|
# set validation and read ballance elements property
|
||||||
BMEMaterialsHelper.__mSingletonMutex = True
|
|
||||||
self.__mIsValid = True
|
self.__mIsValid = True
|
||||||
self.__read_from_bme_materials()
|
self.__read_from_bme_materials()
|
||||||
|
|
||||||
@ -159,7 +156,7 @@ class BMEMaterialsHelper():
|
|||||||
# write to ballance elements property and reset validation
|
# write to ballance elements property and reset validation
|
||||||
self.__write_to_bme_materials()
|
self.__write_to_bme_materials()
|
||||||
self.__mIsValid = False
|
self.__mIsValid = False
|
||||||
BMEMaterialsHelper.__mSingletonMutex = False
|
BMEMaterialsHelper.__mSingletonMutex.unlock(self.__mAssocScene)
|
||||||
|
|
||||||
def get_material(self, preset_name: str) -> bpy.types.Material:
|
def get_material(self, preset_name: str) -> bpy.types.Material:
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@ -179,7 +176,7 @@ class BMEMaterialsHelper():
|
|||||||
return new_mtl
|
return new_mtl
|
||||||
|
|
||||||
def __write_to_bme_materials(self) -> None:
|
def __write_to_bme_materials(self) -> None:
|
||||||
mtls: bpy.types.CollectionProperty = get_bme_materials(self.__mAssocScene)
|
mtls = get_bme_materials(self.__mAssocScene)
|
||||||
mtls.clear()
|
mtls.clear()
|
||||||
|
|
||||||
for preset_name, mtl in self.__mMaterialMap.items():
|
for preset_name, mtl in self.__mMaterialMap.items():
|
||||||
@ -188,10 +185,9 @@ class BMEMaterialsHelper():
|
|||||||
item.material_ptr = mtl
|
item.material_ptr = mtl
|
||||||
|
|
||||||
def __read_from_bme_materials(self) -> None:
|
def __read_from_bme_materials(self) -> None:
|
||||||
mtls: bpy.types.CollectionProperty = get_bme_materials(self.__mAssocScene)
|
mtls = get_bme_materials(self.__mAssocScene)
|
||||||
self.__mMaterialMap.clear()
|
self.__mMaterialMap.clear()
|
||||||
|
|
||||||
item: BBP_PG_bme_material
|
|
||||||
for item in mtls:
|
for item in mtls:
|
||||||
# check requirements
|
# check requirements
|
||||||
if item.material_ptr is None: continue
|
if item.material_ptr is None: continue
|
||||||
@ -200,7 +196,7 @@ class BMEMaterialsHelper():
|
|||||||
|
|
||||||
def reset_bme_materials(scene: bpy.types.Scene) -> None:
|
def reset_bme_materials(scene: bpy.types.Scene) -> None:
|
||||||
invalid_idx: list[int] = []
|
invalid_idx: list[int] = []
|
||||||
mtls: bpy.types.CollectionProperty = get_bme_materials(scene)
|
mtls = get_bme_materials(scene)
|
||||||
|
|
||||||
# re-load all elements
|
# re-load all elements
|
||||||
index: int = 0
|
index: int = 0
|
||||||
|
@ -10,8 +10,8 @@ class BBP_PG_virtools_group(bpy.types.PropertyGroup):
|
|||||||
default = ""
|
default = ""
|
||||||
) # type: ignore
|
) # type: ignore
|
||||||
|
|
||||||
def get_virtools_groups(obj: bpy.types.Object) -> bpy.types.CollectionProperty:
|
def get_virtools_groups(obj: bpy.types.Object) -> UTIL_functions.CollectionVisitor[BBP_PG_virtools_group]:
|
||||||
return obj.virtools_groups
|
return UTIL_functions.CollectionVisitor(obj.virtools_groups)
|
||||||
|
|
||||||
def get_active_virtools_groups(obj: bpy.types.Object) -> int:
|
def get_active_virtools_groups(obj: bpy.types.Object) -> int:
|
||||||
return obj.active_virtools_groups
|
return obj.active_virtools_groups
|
||||||
@ -26,7 +26,7 @@ class VirtoolsGroupsHelper():
|
|||||||
All Virtools group operations should be done by this class.
|
All Virtools group operations should be done by this class.
|
||||||
Do NOT manipulate object's Virtools group properties directly.
|
Do NOT manipulate object's Virtools group properties directly.
|
||||||
"""
|
"""
|
||||||
__mSingletonMutex: typing.ClassVar[bool] = False
|
__mSingletonMutex: typing.ClassVar[UTIL_functions.TinyMutex[bpy.types.Object]] = UTIL_functions.TinyMutex()
|
||||||
__mIsValid: bool
|
__mIsValid: bool
|
||||||
__mNoChange: bool ##< A bool indicate whether any change happended during lifetime. If no change, skip the writing when exiting.
|
__mNoChange: bool ##< A bool indicate whether any change happended during lifetime. If no change, skip the writing when exiting.
|
||||||
__mAssocObj: bpy.types.Object
|
__mAssocObj: bpy.types.Object
|
||||||
@ -36,14 +36,11 @@ class VirtoolsGroupsHelper():
|
|||||||
self.__mGroupsSet = set()
|
self.__mGroupsSet = set()
|
||||||
self.__mAssocObj = assoc
|
self.__mAssocObj = assoc
|
||||||
self.__mNoChange = True
|
self.__mNoChange = True
|
||||||
|
self.__mIsValid = False
|
||||||
|
|
||||||
# check singleton
|
# check singleton
|
||||||
if VirtoolsGroupsHelper.__mSingletonMutex:
|
VirtoolsGroupsHelper.__mSingletonMutex.lock(self.__mAssocObj)
|
||||||
self.__mIsValid = False
|
|
||||||
raise UTIL_functions.BBPException('VirtoolsGroupsHelper is mutex.')
|
|
||||||
|
|
||||||
# set validation and read ballance elements property
|
# set validation and read ballance elements property
|
||||||
VirtoolsGroupsHelper.__mSingletonMutex = True
|
|
||||||
self.__mIsValid = True
|
self.__mIsValid = True
|
||||||
self.__read_from_virtools_groups()
|
self.__read_from_virtools_groups()
|
||||||
|
|
||||||
@ -63,7 +60,7 @@ class VirtoolsGroupsHelper():
|
|||||||
if not self.__mNoChange:
|
if not self.__mNoChange:
|
||||||
self.__write_to_virtools_groups()
|
self.__write_to_virtools_groups()
|
||||||
self.__mIsValid = False
|
self.__mIsValid = False
|
||||||
VirtoolsGroupsHelper.__mSingletonMutex = False
|
VirtoolsGroupsHelper.__mSingletonMutex.unlock(self.__mAssocObj)
|
||||||
|
|
||||||
def __check_valid(self) -> None:
|
def __check_valid(self) -> None:
|
||||||
if not self.is_valid():
|
if not self.is_valid():
|
||||||
@ -127,7 +124,7 @@ class VirtoolsGroupsHelper():
|
|||||||
return len(self.__mGroupsSet)
|
return len(self.__mGroupsSet)
|
||||||
|
|
||||||
def __write_to_virtools_groups(self) -> None:
|
def __write_to_virtools_groups(self) -> None:
|
||||||
groups: bpy.types.CollectionProperty = get_virtools_groups(self.__mAssocObj)
|
groups = get_virtools_groups(self.__mAssocObj)
|
||||||
sel: int = get_active_virtools_groups(self.__mAssocObj)
|
sel: int = get_active_virtools_groups(self.__mAssocObj)
|
||||||
groups.clear()
|
groups.clear()
|
||||||
|
|
||||||
@ -143,10 +140,9 @@ class VirtoolsGroupsHelper():
|
|||||||
set_active_virtools_groups(self.__mAssocObj, sel)
|
set_active_virtools_groups(self.__mAssocObj, sel)
|
||||||
|
|
||||||
def __read_from_virtools_groups(self) -> None:
|
def __read_from_virtools_groups(self) -> None:
|
||||||
groups: bpy.types.CollectionProperty = get_virtools_groups(self.__mAssocObj)
|
groups = get_virtools_groups(self.__mAssocObj)
|
||||||
self.__mGroupsSet.clear()
|
self.__mGroupsSet.clear()
|
||||||
|
|
||||||
item: BBP_PG_virtools_group
|
|
||||||
for item in groups:
|
for item in groups:
|
||||||
self.__mGroupsSet.add(item.group_name)
|
self.__mGroupsSet.add(item.group_name)
|
||||||
|
|
||||||
@ -306,7 +302,8 @@ class BBP_OT_add_virtools_group(bpy.types.Operator, SharedGroupNameInputProperti
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
# add group
|
# add group
|
||||||
with VirtoolsGroupsHelper(context.object) as hlp:
|
obj = typing.cast(bpy.types.Object, context.object)
|
||||||
|
with VirtoolsGroupsHelper(obj) as hlp:
|
||||||
hlp.add_group(self.general_get_group_name())
|
hlp.add_group(self.general_get_group_name())
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -335,8 +332,9 @@ class BBP_OT_rm_virtools_group(bpy.types.Operator):
|
|||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
# get selected group name first
|
# get selected group name first
|
||||||
obj = context.object
|
obj = typing.cast(bpy.types.Object, context.object)
|
||||||
item: BBP_PG_virtools_group = get_virtools_groups(obj)[get_active_virtools_groups(obj)]
|
groups = get_virtools_groups(obj)
|
||||||
|
item = groups[get_active_virtools_groups(obj)]
|
||||||
gname: str = item.group_name
|
gname: str = item.group_name
|
||||||
# then delete it
|
# then delete it
|
||||||
with VirtoolsGroupsHelper(obj) as hlp:
|
with VirtoolsGroupsHelper(obj) as hlp:
|
||||||
@ -359,7 +357,8 @@ class BBP_OT_clear_virtools_groups(bpy.types.Operator):
|
|||||||
return wm.invoke_confirm(self, event)
|
return wm.invoke_confirm(self, event)
|
||||||
|
|
||||||
def execute(self, context):
|
def execute(self, context):
|
||||||
with VirtoolsGroupsHelper(context.object) as hlp:
|
obj = typing.cast(bpy.types.Object, context.object)
|
||||||
|
with VirtoolsGroupsHelper(obj) as hlp:
|
||||||
hlp.clear_groups()
|
hlp.clear_groups()
|
||||||
return {'FINISHED'}
|
return {'FINISHED'}
|
||||||
|
|
||||||
@ -384,7 +383,6 @@ class BBP_PT_virtools_groups(bpy.types.Panel):
|
|||||||
layout.label(text = 'Virtools Group is invalid on non-mesh object!', icon = 'ERROR')
|
layout.label(text = 'Virtools Group is invalid on non-mesh object!', icon = 'ERROR')
|
||||||
|
|
||||||
# draw main body
|
# draw main body
|
||||||
|
|
||||||
row = layout.row()
|
row = layout.row()
|
||||||
row.template_list(
|
row.template_list(
|
||||||
"BBP_UL_virtools_groups", "",
|
"BBP_UL_virtools_groups", "",
|
||||||
|
@ -173,3 +173,99 @@ class EnumPropHelper():
|
|||||||
# call to_str fct ptr
|
# call to_str fct ptr
|
||||||
return self.__mFctToStr(val)
|
return self.__mFctToStr(val)
|
||||||
|
|
||||||
|
#region Blender Collection Visitor
|
||||||
|
|
||||||
|
_TPropertyGroup = typing.TypeVar('_TPropertyGroup', bound = bpy.types.PropertyGroup)
|
||||||
|
|
||||||
|
class CollectionVisitor(typing.Generic[_TPropertyGroup]):
|
||||||
|
"""
|
||||||
|
This is a patch class for Blender collection property.
|
||||||
|
Blender collcetion property lack essential type hint and document.
|
||||||
|
So I create a wrapper for my personal use to reduce type hint errors raised by my linter.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__mSrcProp: bpy.types.CollectionProperty
|
||||||
|
|
||||||
|
def __init__(self, src_prop: bpy.types.CollectionProperty):
|
||||||
|
self.__mSrcProp = src_prop
|
||||||
|
|
||||||
|
def add(self) -> _TPropertyGroup:
|
||||||
|
"""!
|
||||||
|
@brief Adds a new item to the collection.
|
||||||
|
@return The instance of newly created item.
|
||||||
|
"""
|
||||||
|
return self.__mSrcProp.add()
|
||||||
|
|
||||||
|
def remove(self, index: int) -> None:
|
||||||
|
"""!
|
||||||
|
@brief Removes the item at the specified index from the collection.
|
||||||
|
@param[in] index The index of the item to remove.
|
||||||
|
"""
|
||||||
|
self.__mSrcProp.remove(index)
|
||||||
|
|
||||||
|
def move(self, from_index: int, to_index: int) -> None:
|
||||||
|
"""!
|
||||||
|
@brief Moves an item from one index to another within the collection.
|
||||||
|
@param[in] from_index The current index of the item to move.
|
||||||
|
@param[in] to_index The target index where the item should be moved.
|
||||||
|
"""
|
||||||
|
self.__mSrcProp.move(from_index, to_index)
|
||||||
|
|
||||||
|
def clear(self) -> None:
|
||||||
|
"""!
|
||||||
|
@brief Clears all items from the collection.
|
||||||
|
"""
|
||||||
|
self.__mSrcProp.clear()
|
||||||
|
|
||||||
|
def __len__(self) -> int:
|
||||||
|
return self.__mSrcProp.__len__()
|
||||||
|
def __getitem__(self, index: int | str) -> _TPropertyGroup:
|
||||||
|
return self.__mSrcProp.__getitem__(index)
|
||||||
|
def __setitem__(self, index: int | str, value: _TPropertyGroup) -> None:
|
||||||
|
self.__mSrcProp.__setitem__(index, value)
|
||||||
|
def __delitem__(self, index: int | str) -> None:
|
||||||
|
self.__mSrcProp.__delitem__(index)
|
||||||
|
def __iter__(self) -> typing.Iterator[_TPropertyGroup]:
|
||||||
|
return self.__mSrcProp.__iter__()
|
||||||
|
def __contains__(self, item: _TPropertyGroup) -> bool:
|
||||||
|
return self.__mSrcProp.__contains__(item)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Tiny Mutex for With Context
|
||||||
|
|
||||||
|
_TMutexObject = typing.TypeVar('_TMutexObject')
|
||||||
|
|
||||||
|
class TinyMutex(typing.Generic[_TMutexObject]):
|
||||||
|
"""
|
||||||
|
In this plugin, some class have "with" context feature.
|
||||||
|
However, it is essential to block any futher visiting if some "with" context are operating on some object.
|
||||||
|
This is the reason why this tiny mutex is designed.
|
||||||
|
|
||||||
|
Please note this class is not a real MUTEX.
|
||||||
|
We just want to make sure the resources only can be visited by one "with" context.
|
||||||
|
So it doesn't matter that we do not use lock before operating something.
|
||||||
|
"""
|
||||||
|
|
||||||
|
__mProtectedObjects: set[_TMutexObject]
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.__mProtectedObjects = set()
|
||||||
|
|
||||||
|
def lock(self, obj: _TMutexObject) -> None:
|
||||||
|
if obj in self.__mProtectedObjects:
|
||||||
|
raise BBPException('It is not allowed that operate multiple "with" contexts on a single object.')
|
||||||
|
self.__mProtectedObjects.add(obj)
|
||||||
|
|
||||||
|
def try_lock(self, obj: _TMutexObject) -> bool:
|
||||||
|
if obj in self.__mProtectedObjects:
|
||||||
|
return False
|
||||||
|
self.__mProtectedObjects.add(obj)
|
||||||
|
return True
|
||||||
|
|
||||||
|
def unlock(self, obj: _TMutexObject) -> None:
|
||||||
|
if obj not in self.__mProtectedObjects:
|
||||||
|
raise BBPException('It is not allowed that unlock an non-existent object.')
|
||||||
|
self.__mProtectedObjects.remove(obj)
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
Loading…
Reference in New Issue
Block a user