import bpy, mathutils import typing from . import PROP_preferences from . import UTIL_functions, UTIL_translation, UTIL_bme #region BME Adder _g_EnumHelper_BmeStructType: UTIL_bme.EnumPropHelper = UTIL_bme.EnumPropHelper() class BBP_PG_bme_adder_cfgs(bpy.types.PropertyGroup): prop_int: bpy.props.IntProperty( name = 'Integral Value', description = 'The field representing a single integral value.', min = 0, max = 64, soft_min = 0, soft_max = 32, step = 1, default = 1, translation_context = 'BBP_PG_bme_adder_cfgs/property' ) # type: ignore prop_float: bpy.props.FloatProperty( name = 'Float Point Value', description = 'The field representing a single float point value.', min = 0.0, max = 1024.0, soft_min = 0.0, soft_max = 512.0, step = 50, # Step is in UI, in [1, 100] (WARNING: actual value is /100). So we choose 50, mean 0.5 default = 5.0, translation_context = 'BBP_PG_bme_adder_cfgs/property' ) # type: ignore prop_bool: bpy.props.BoolProperty( name = 'Boolean Value', description = 'The field representing a single boolean value.', default = True, translation_context = 'BBP_PG_bme_adder_cfgs/property' ) # type: ignore class BBP_OT_add_bme_struct(bpy.types.Operator): """Add BME structure""" bl_idname = "bbp.add_bme_struct" bl_label = "Add BME Structure" bl_options = {'REGISTER', 'UNDO'} bl_translation_context = 'BBP_OT_add_bme_struct' ## There is a compromise due to the shitty Blender design. # # The passed `self` of Blender Property update function is not the instance of operator, # but a simple OperatorProperties. # It mean that I can not visit the full operator, only what I can do is visit existing # Blender properties. # # 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. # The "outdated" flags is not showen and not saved. # 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. # Otherwise do nothing. # # Reference: https://docs.blender.org/api/current/bpy.props.html#update-example ## Compromise used "outdated" flag. outdated_flag: bpy.props.BoolProperty( # TR: Property not showen should not have name and desc. # name = "Outdated Type", # description = "Internal flag.", options = {'HIDDEN', 'SKIP_SAVE'}, default = False ) # type: ignore ## A BME struct cfgs descriptor cache list # Not only the descriptor self, also the cfg associated index in bme_struct_cfgs bme_struct_cfg_index_cache: list[tuple[UTIL_bme.PrototypeShowcaseCfgDescriptor, int]] def __internal_update_bme_struct_type(self) -> None: # if not outdated, skip if not self.outdated_flag: return # get available cfg entires cfgs: typing.Iterator[UTIL_bme.PrototypeShowcaseCfgDescriptor] cfgs = _g_EnumHelper_BmeStructType.get_bme_showcase_cfgs( _g_EnumHelper_BmeStructType.get_selection(self.bme_struct_type) ) # analyse cfgs. # create counter first counter_int: int = 0 counter_float: int = 0 counter_bool: int = 0 # create cache list self.bme_struct_cfg_index_cache.clear() # iterate cfgs and register them for cfg in cfgs: match(cfg.get_type()): case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer: self.bme_struct_cfg_index_cache.append((cfg, counter_int)) counter_int += 1 case UTIL_bme.PrototypeShowcaseCfgsTypes.Float: self.bme_struct_cfg_index_cache.append((cfg, counter_float)) counter_float += 1 case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean: self.bme_struct_cfg_index_cache.append((cfg, counter_bool)) counter_bool += 1 case UTIL_bme.PrototypeShowcaseCfgsTypes.Face: self.bme_struct_cfg_index_cache.append((cfg, counter_bool)) counter_bool += 6 # face will occupy 6 bool. # init data collection op_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs] op_cfgs_visitor = UTIL_functions.CollectionVisitor(self.bme_struct_cfgs) # clear first op_cfgs_visitor.clear() # create enough entries specified by gotten cfgs for _ in range(max(counter_int, counter_float, counter_bool)): op_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: op_cfgs_visitor[cfg_index].prop_int = cfg.get_default() case UTIL_bme.PrototypeShowcaseCfgsTypes.Float: op_cfgs_visitor[cfg_index].prop_float = cfg.get_default() case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean: op_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): op_cfgs_visitor[cfg_index + i].prop_bool = default_values[i] # reset outdated flag self.outdated_flag = False # the updator for default side value def bme_struct_type_updated(self, context): # update outdated flag self.outdated_flag = True # blender required return None bme_struct_type: bpy.props.EnumProperty( name = "Type", description = "The type of BME structure.", items = _g_EnumHelper_BmeStructType.generate_items(), update = bme_struct_type_updated, translation_context = 'BBP_OT_add_bme_struct/property' ) # type: ignore bme_struct_cfgs : bpy.props.CollectionProperty( name = "Configurations", description = "The collection holding BME structure configurations.", type = BBP_PG_bme_adder_cfgs, translation_context = 'BBP_OT_add_bme_struct/property' ) # 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), translation_context = 'BBP_OT_add_bme_struct/property' ) # 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), translation_context = 'BBP_OT_add_bme_struct/property' ) # type: ignore @classmethod def poll(cls, context): return PROP_preferences.get_raw_preferences().has_valid_blc_tex_folder() 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 self.bme_struct_cfg_index_cache = [] # trigger default bme struct type updator self.bme_struct_type_updated(context) # call internal updator self.__internal_update_bme_struct_type() # run execute() function return self.execute(context) def execute(self, context): # call internal updator self.__internal_update_bme_struct_type() # create cfg visitor op_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs] op_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()] = op_cfgs_visitor[cfg_index].prop_int case UTIL_bme.PrototypeShowcaseCfgsTypes.Float: cfgs[cfg.get_field()] = op_cfgs_visitor[cfg_index].prop_float case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean: cfgs[cfg.get_field()] = op_cfgs_visitor[cfg_index].prop_bool case UTIL_bme.PrototypeShowcaseCfgsTypes.Face: # face is just 6 bool tuple cfgs[cfg.get_field()] = tuple( op_cfgs_visitor[cfg_index + i].prop_bool for i in range(6) ) # call general creator obj: bpy.types.Object = UTIL_bme.create_bme_struct_wrapper( _g_EnumHelper_BmeStructType.get_selection(self.bme_struct_type), cfgs ) # add into scene and move to cursor 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 UTIL_functions.select_certain_objects((obj, )) return {'FINISHED'} def draw(self, context): # call internal updator self.__internal_update_bme_struct_type() # start drawing layout: bpy.types.UILayout = self.layout # show type layout.prop(self, 'bme_struct_type') # create cfg visitor op_cfgs_visitor: UTIL_functions.CollectionVisitor[BBP_PG_bme_adder_cfgs] op_cfgs_visitor = UTIL_functions.CollectionVisitor(self.bme_struct_cfgs) # visit cfgs cache list to show cfg layout.label(text="Prototype Configurations:", text_ctxt='BBP_OT_add_bme_struct/draw') for (cfg, cfg_index) in self.bme_struct_cfg_index_cache: # create box for cfgs box_layout: bpy.types.UILayout = layout.box() # draw title and description first box_layout.label(text=cfg.get_title()) # TODO: finish translation context box_layout.label(text=cfg.get_desc()) # show prop differently by cfg type match(cfg.get_type()): case UTIL_bme.PrototypeShowcaseCfgsTypes.Integer: box_layout.prop(op_cfgs_visitor[cfg_index], 'prop_int', text='') case UTIL_bme.PrototypeShowcaseCfgsTypes.Float: box_layout.prop(op_cfgs_visitor[cfg_index], 'prop_float', text='') case UTIL_bme.PrototypeShowcaseCfgsTypes.Boolean: box_layout.prop(op_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(op_cfgs_visitor[cfg_index + 0], 'prop_bool', text='Top', text_ctxt='BBP_OT_add_bme_struct/draw') # top grids.prop(op_cfgs_visitor[cfg_index + 2], 'prop_bool', text='Front', text_ctxt='BBP_OT_add_bme_struct/draw') # front grids.prop(op_cfgs_visitor[cfg_index + 4], 'prop_bool', text='Left', text_ctxt='BBP_OT_add_bme_struct/draw') # left grids.label(text='', icon='CUBE') # show a 3d cube as icon grids.prop(op_cfgs_visitor[cfg_index + 5], 'prop_bool', text='Right', text_ctxt='BBP_OT_add_bme_struct/draw') # right grids.prop(op_cfgs_visitor[cfg_index + 3], 'prop_bool', text='Back', text_ctxt='BBP_OT_add_bme_struct/draw') # back grids.prop(op_cfgs_visitor[cfg_index + 1], 'prop_bool', text='Bottom', text_ctxt='BBP_OT_add_bme_struct/draw') # bottom grids.separator() # show extra transform props # forcely order that each one are placed horizontally layout.label(text="Extra Transform", text_ctxt='BBP_OT_add_bme_struct/draw') # translation layout.label(text='Translation', text_ctxt='BBP_OT_add_bme_struct/draw') hbox_layout: bpy.types.UILayout = layout.row() hbox_layout.prop(self, 'extra_translation', text='') # rotation layout.label(text='Rotation', text_ctxt='BBP_OT_add_bme_struct/draw') hbox_layout = layout.row() hbox_layout.prop(self, 'extra_rotation', text='') @classmethod def draw_blc_menu(cls, layout: bpy.types.UILayout): for ident in _g_EnumHelper_BmeStructType.get_bme_identifiers(): # draw operator cop = layout.operator( cls.bl_idname, text = _g_EnumHelper_BmeStructType.get_bme_showcase_title(ident), icon_value = _g_EnumHelper_BmeStructType.get_bme_showcase_icon(ident), text_ctxt = UTIL_translation.build_prototype_showcase_context(ident) ) # and assign its init type value cop.bme_struct_type = _g_EnumHelper_BmeStructType.to_selection(ident) #endregion def register() -> None: bpy.utils.register_class(BBP_PG_bme_adder_cfgs) bpy.utils.register_class(BBP_OT_add_bme_struct) def unregister() -> None: bpy.utils.unregister_class(BBP_OT_add_bme_struct) bpy.utils.unregister_class(BBP_PG_bme_adder_cfgs)