Skip to content

Commit

Permalink
refactor: add 2 classes to refactor old code which raise too much err…
Browse files Browse the repository at this point in the history
…os 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.
  • Loading branch information
yyc12345 committed Jan 6, 2025
1 parent 6ae8899 commit f4d3e48
Show file tree
Hide file tree
Showing 7 changed files with 182 additions and 101 deletions.
46 changes: 27 additions & 19 deletions bbp_ng/OP_ADDS_bme.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,27 +96,29 @@ def __internal_update_bme_struct_type(self) -> None:


# 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
self.bme_struct_cfgs.clear()
adder_cfgs_visitor.clear()
# create enough entries specified by gotten cfgs
for _ in range(max(counter_int, counter_float, counter_bool)):
self.bme_struct_cfgs.add()
adder_cfgs_visitor.add()

# assign default value
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
# show prop differently by cfg type
match(cfg.get_type()):
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:
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:
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:
# face is just 6 bool
default_values: tuple[bool, ...] = cfg.get_default()
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
self.outdated_flag = False
Expand Down Expand Up @@ -182,20 +184,23 @@ def execute(self, context):
# call internal updator
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
cfgs: dict[str, typing.Any] = {}
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
match(cfg.get_type()):
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:
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:
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:
# face is just 6 bool 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
Expand Down Expand Up @@ -225,6 +230,9 @@ def draw(self, context):
# show 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
layout.label(text = "Prototype Configurations:")
for (cfg, cfg_index) in self.bme_struct_cfg_index_cache:
Expand All @@ -238,24 +246,24 @@ def draw(self, context):
# show prop differently by cfg type
match(cfg.get_type()):
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:
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:
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:
# face will show a special layout (grid view)
grids = box_layout.grid_flow(
row_major=True, columns=3, even_columns=True, even_rows=True, align=True)
grids.alignment = 'CENTER'
grids.separator()
grids.prop(self.bme_struct_cfgs[cfg_index + 0], 'prop_bool', text = 'Top') # top
grids.prop(self.bme_struct_cfgs[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 + 0], 'prop_bool', text = 'Top') # top
grids.prop(adder_cfgs_visitor[cfg_index + 2], 'prop_bool', text = 'Front') # front
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.prop(self.bme_struct_cfgs[cfg_index + 5], 'prop_bool', text = 'Right') # right
grids.prop(self.bme_struct_cfgs[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 + 5], 'prop_bool', text = 'Right') # right
grids.prop(adder_cfgs_visitor[cfg_index + 3], 'prop_bool', text = 'Back') # back
grids.prop(adder_cfgs_visitor[cfg_index + 1], 'prop_bool', text = 'Bottom') # bottom
grids.separator()

# show extra transform props
Expand Down
23 changes: 15 additions & 8 deletions bbp_ng/OP_OBJECT_legacy_align.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,13 @@ def apply_flag_updated(self, context):

# check whether add new entry
# 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:
# valid one
# add a new entry in history
self.align_history.add()
histories.add()
else:
# invalid one
# reset all data to default
Expand Down Expand Up @@ -127,9 +129,11 @@ def poll(cls, context):
return _check_align_requirement()

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
self.align_history.clear()
self.align_history.add()
histories.clear()
histories.add()
# run execute() function
return self.execute(context)

Expand All @@ -145,8 +149,9 @@ def execute(self, context):
# If you place it at the end of this function, it doesn't work.
context.view_layer.update()
# iterate history to align objects
entry: BBP_PG_legacy_align_history
for entry in self.align_history:
histories: UTIL_functions.CollectionVisitor[BBP_PG_legacy_align_history]
histories = UTIL_functions.CollectionVisitor(self.align_history)
for entry in histories:
_align_objects(
current_obj, target_objs,
entry.align_x, entry.align_y, entry.align_z,
Expand All @@ -157,7 +162,9 @@ def execute(self, context):

def draw(self, context):
# 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
col = layout.column()
Expand All @@ -183,7 +190,7 @@ def draw(self, context):
conditional_disable_area.enabled = entry.align_x == True or entry.align_y == True or entry.align_z == True
# show apply and counter
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

Expand Down
9 changes: 2 additions & 7 deletions bbp_ng/OP_OBJECT_snoop_group_then_to_mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,10 @@ def execute(self, context):
if bevel_obj is None: continue

# 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:
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
# no matter the success of copying virtools group infos and whether selected object is curve
Expand Down
55 changes: 18 additions & 37 deletions bbp_ng/PROP_ballance_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,8 @@ class BBP_PG_ballance_element(bpy.types.PropertyGroup):
type = bpy.types.Mesh
) # type: ignore

def get_ballance_elements(scene: bpy.types.Scene) -> bpy.types.CollectionProperty:
return scene.ballance_elements
def get_ballance_elements(scene: bpy.types.Scene) -> UTIL_functions.CollectionVisitor[BBP_PG_ballance_element]:
return UTIL_functions.CollectionVisitor(scene.ballance_elements)

#endregion

Expand Down Expand Up @@ -224,22 +224,19 @@ class BallanceElementsHelper():
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.
"""
__mSingletonMutex: typing.ClassVar[bool] = False
__mSingletonMutex: typing.ClassVar[UTIL_functions.TinyMutex[bpy.types.Scene]] = UTIL_functions.TinyMutex()
__mIsValid: bool
__mAssocScene: bpy.types.Scene
__mElementMap: dict[BallanceElementType, bpy.types.Mesh]

def __init__(self, assoc: bpy.types.Scene):
self.__mElementMap = {}
self.__mAssocScene = assoc
self.__mIsValid = False

# check singleton
if BallanceElementsHelper.__mSingletonMutex:
self.__mIsValid = False
raise UTIL_functions.BBPException('BallanceElementsHelper is mutex.')

BallanceElementsHelper.__mSingletonMutex.lock(self.__mAssocScene)
# set validation and read ballance elements property
BallanceElementsHelper.__mSingletonMutex = True
self.__mIsValid = True
self.__read_from_ballance_element()

Expand All @@ -257,7 +254,7 @@ def dispose(self) -> None:
# write to ballance elements property and reset validation
self.__write_to_ballance_elements()
self.__mIsValid = False
BallanceElementsHelper.__mSingletonMutex = False
BallanceElementsHelper.__mSingletonMutex.unlock(self.__mAssocScene)

def get_element(self, element_type: BallanceElementType) -> bpy.types.Mesh:
if not self.is_valid():
Expand All @@ -276,8 +273,16 @@ def get_element(self, element_type: BallanceElementType) -> bpy.types.Mesh:
self.__mElementMap[element_type] = 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:
elements: bpy.types.CollectionProperty = get_ballance_elements(self.__mAssocScene)
elements = get_ballance_elements(self.__mAssocScene)
elements.clear()

for elety, elemesh in self.__mElementMap.items():
Expand All @@ -286,10 +291,9 @@ def __write_to_ballance_elements(self) -> None:
item.mesh_ptr = elemesh

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

item: BBP_PG_ballance_element
for item in elements:
# check requirements
if item.mesh_ptr is None: continue
Expand All @@ -299,30 +303,6 @@ def __read_from_ballance_element(self) -> None:
# add into map
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

#region Ballance Elements Representation
Expand All @@ -348,7 +328,8 @@ def poll(cls, context):
return context.scene is not None

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
UTIL_functions.message_box(
('Reset OK.', ),
Expand Down
22 changes: 9 additions & 13 deletions bbp_ng/PROP_bme_material.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@ class BBP_PG_bme_material(bpy.types.PropertyGroup):
type = bpy.types.Material
) # type: ignore

def get_bme_materials(scene: bpy.types.Scene) -> bpy.types.CollectionProperty:
return scene.bme_materials
def get_bme_materials(scene: bpy.types.Scene) -> UTIL_functions.CollectionVisitor[BBP_PG_bme_material]:
return UTIL_functions.CollectionVisitor(scene.bme_materials)

#endregion

Expand Down Expand Up @@ -126,22 +126,19 @@ class BMEMaterialsHelper():
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.
"""
__mSingletonMutex: typing.ClassVar[bool] = False
__mSingletonMutex: typing.ClassVar[UTIL_functions.TinyMutex[bpy.types.Scene]] = UTIL_functions.TinyMutex()
__mIsValid: bool
__mAssocScene: bpy.types.Scene
__mMaterialMap: dict[str, bpy.types.Material]

def __init__(self, assoc: bpy.types.Scene):
self.__mMaterialMap = {}
self.__mAssocScene = assoc
self.__mIsValid = False

# check singleton
if BMEMaterialsHelper.__mSingletonMutex:
self.__mIsValid = False
raise UTIL_functions.BBPException('BMEMaterialsHelper is mutex.')

BMEMaterialsHelper.__mSingletonMutex.lock(self.__mAssocScene)
# set validation and read ballance elements property
BMEMaterialsHelper.__mSingletonMutex = True
self.__mIsValid = True
self.__read_from_bme_materials()

Expand All @@ -159,7 +156,7 @@ def dispose(self) -> None:
# write to ballance elements property and reset validation
self.__write_to_bme_materials()
self.__mIsValid = False
BMEMaterialsHelper.__mSingletonMutex = False
BMEMaterialsHelper.__mSingletonMutex.unlock(self.__mAssocScene)

def get_material(self, preset_name: str) -> bpy.types.Material:
if not self.is_valid():
Expand All @@ -179,7 +176,7 @@ def get_material(self, preset_name: str) -> bpy.types.Material:
return new_mtl

def __write_to_bme_materials(self) -> None:
mtls: bpy.types.CollectionProperty = get_bme_materials(self.__mAssocScene)
mtls = get_bme_materials(self.__mAssocScene)
mtls.clear()

for preset_name, mtl in self.__mMaterialMap.items():
Expand All @@ -188,10 +185,9 @@ def __write_to_bme_materials(self) -> None:
item.material_ptr = mtl

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

item: BBP_PG_bme_material
for item in mtls:
# check requirements
if item.material_ptr is None: continue
Expand All @@ -200,7 +196,7 @@ def __read_from_bme_materials(self) -> None:

def reset_bme_materials(scene: bpy.types.Scene) -> None:
invalid_idx: list[int] = []
mtls: bpy.types.CollectionProperty = get_bme_materials(scene)
mtls = get_bme_materials(scene)

# re-load all elements
index: int = 0
Expand Down
Loading

0 comments on commit f4d3e48

Please sign in to comment.