import os, bpy from mpfb._classmanager import ClassManager from mpfb.services.logservice import LogService from mpfb.services.sceneconfigset import SceneConfigSet from mpfb.services.uiservice import UiService from mpfb.ui.righelpers import RigHelpersProperties from mpfb.ui.abstractpanel import Abstract_Panel _LOG = LogService.get_logger("righelpers.righelperspanel") _LOC = os.path.dirname(__file__) SETUP_HELPERS_PROPERTIES_DIR = os.path.join(_LOC, "properties") SETUP_HELPERS_PROPERTIES = SceneConfigSet.from_definitions_in_json_directory(SETUP_HELPERS_PROPERTIES_DIR, prefix="SIK_") class MPFB_PT_RigHelpersPanel(Abstract_Panel): bl_label = "Rig helpers" bl_category = UiService.get_value("RIGCATEGORY") bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "MPFB_PT_Rig_Panel" def _arm_helpers(self, scene, layout): box = self._create_box(layout, "Arm helpers", "BONE_DATA") props = [ "arm_helpers_type", "arm_parenting_strategy", "arm_target_rotates_hand", "arm_target_rotates_lower_arm" ] SETUP_HELPERS_PROPERTIES.draw_properties(scene, box, props)
"""This module provides and information holder for MHCLO files.""" import bpy, os, sys, json from mathutils import Vector from mpfb.services.objectservice import ObjectService from mpfb.services.logservice import LogService from mpfb.services.locationservice import LocationService _LOG = LogService.get_logger("entities.mhclo") _CONFIG_FILE = None class Mhclo: """A representation of the values of a MHCLO file.""" def __init__(self): """Create an empty MHCLO object with default values.""" self.obj_file = None self.x_scale = None self.y_scale = None self.z_scale = None self.author = "unknown" self.license = "CC0" self.name = "imported_cloth" self.description = "no description" self.material = None self.tags = "" self.zdepth = 50 self.first = 0 self.verts = {} self.delverts = []
from mpfb.services.logservice import LogService from mpfb._classmanager import ClassManager import bpy, json from bpy_extras.io_utils import ImportHelper _LOG = LogService.get_logger("developer.loadrigifylayers") class MPFB_OT_Load_Rigify_Layers_Operator(bpy.types.Operator, ImportHelper): """Load rigify layer definition as json""" bl_idname = "mpfb.load_rigify_layers" bl_label = "Load rigify layers" bl_options = {'REGISTER', 'UNDO'} filename_ext = '.json' @classmethod def poll(cls, context): _LOG.enter() if context.object is None or context.object.type != 'ARMATURE': return False # TODO: check that it is a rigify enabled rig return True def execute(self, context): _LOG.enter() if context.object is None or context.object.type != 'ARMATURE': self.report({'ERROR'}, "Must have armature as active object") return {'FINISHED'}
from mpfb._classmanager import ClassManager from mpfb.services.logservice import LogService from mpfb.services.uiservice import UiService from mpfb.ui.abstractpanel import Abstract_Panel _LOG = LogService.get_logger("ui.materialspanel") class MPFB_PT_Materials_Panel(Abstract_Panel): bl_label = "Materials" bl_category = UiService.get_value("MATERIALSCATEGORY") def draw(self, context): _LOG.enter() layout = self.layout scn = context.scene ClassManager.add_class(MPFB_PT_Materials_Panel)
from mpfb.services.logservice import LogService from mpfb.services.uiservice import UiService from mpfb.services.locationservice import LocationService from mpfb.services.objectservice import ObjectService from mpfb.services.nodeservice import NodeService from mpfb.services.humanservice import HumanService from mpfb.ui.humanpresets.humanpresetspanel import HUMAN_PRESETS_PROPERTIES from mpfb._classmanager import ClassManager import bpy, os, json _LOG = LogService.get_logger("humanpresets.overwritepresets") class MPFB_OT_Overwrite_Human_Presets_Operator(bpy.types.Operator): """This will overwrite the selected human presets, using values from the selected object""" bl_idname = "mpfb.overwrite_human_presets" bl_label = "Overwrite presets" bl_options = {'REGISTER'} def execute(self, context): _LOG.enter() if context.object is None: self.report({'ERROR'}, "Must have a selected object") return {'FINISHED'} name = HUMAN_PRESETS_PROPERTIES.get_value("available_presets", entity_reference=context) if not name is None: name = str(name).strip() if name == "" or name is None: self.report({'ERROR'}, "Presets must be chosen from the list")
"""File containing main UI for maketarget""" import bpy from mpfb import ClassManager from mpfb.services.logservice import LogService from mpfb.services.uiservice import UiService from mpfb.entities.objectproperties import GeneralObjectProperties from mpfb.ui.abstractpanel import Abstract_Panel from mpfb.ui.maketarget import MakeTargetObjectProperties _LOG = LogService.get_logger("maketarget.maketargetpanel") class MPFB_PT_MakeTarget_Panel(Abstract_Panel): """MakeTarget main panel.""" bl_label = "MakeTarget" bl_category = UiService.get_value("TARGETSCATEGORY") bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "MPFB_PT_Create_Panel" def _initialize_target(self, blender_object, layout): box = self._create_box(layout, "Initialize", "TOOL_SETTINGS") props = ["name"] MakeTargetObjectProperties.draw_properties(blender_object, box, props) box.operator('mpfb.create_maketarget_target') box.operator('mpfb.import_maketarget_target') def _save_target(self, scene, layout): box = self._create_box(layout, "Save target", "TOOL_SETTINGS") box.operator('mpfb.write_maketarget_target')
"""This file contains the dir resources panel.""" from mpfb._classmanager import ClassManager from mpfb.services.logservice import LogService from mpfb.services.locationservice import LocationService from mpfb.services.uiservice import UiService from mpfb.ui.abstractpanel import Abstract_Panel _LOG = LogService.get_logger("dirresources.dirresourcespanel") class MPFB_PT_Dir_Resources_Panel(Abstract_Panel): """UI for opening dir links.""" bl_label = "Directories" bl_category = UiService.get_value("DEVELOPERCATEGORY") bl_options = {'DEFAULT_CLOSED'} bl_parent_id = "MPFB_PT_System_Panel" def _path(self, layout, label, path): dirlink = layout.operator("mpfb.dir_resource", text=label) dirlink.path = path def draw(self, context): _LOG.enter() layout = self.layout user_files = LocationService.get_user_home() library_files = LocationService.get_user_data() system_data = LocationService.get_mpfb_data() log_files = LocationService.get_user_home("logs") self._path(layout, "User files", user_files)
"""This module provides functionality for adding helpers to hip/legs/feet.""" import bpy from mpfb.services.logservice import LogService _LOG = LogService.get_logger("leghelpers.leghelpers") from mpfb.services.rigservice import RigService from mpfb.ui.righelpers import RigHelpersProperties class LegHelpers(): """This is the abstract rig type independent base class for working with helpers for hips, legs and feet. You will want to call the static get_instance() method to get a concrete implementation for the specific rig you are working with.""" def __init__(self, which_leg, settings): """Get a new instance of LegHelpers. You should not call this directly. Use get_instance() instead.""" _LOG.debug("Constructing LegHelpers object") self.which_leg = which_leg self.settings = settings self._bone_info = dict() _LOG.dump("settings", self.settings) def apply_ik(self, armature_object): """Add rig helpers for hip, legs and feet based on the settings that were provided when constructing the class."""
from mpfb.services.logservice import LogService from mpfb.services.uiservice import UiService from mpfb.services.locationservice import LocationService from mpfb.ui.importerpresets.importerpresetspanel import IMPORTER_PRESETS_PROPERTIES from mpfb._classmanager import ClassManager import bpy, os _LOG = LogService.get_logger("importeroperators.savenewpresets") class MPFB_OT_SaveNewImporterPresetsOperator(bpy.types.Operator): """This will save new importer presets with a name from the text field above, using values from the fields below""" bl_idname = "mpfb.importerpresets_save_new_importer_presets" bl_label = "Save new importer presets" bl_options = {'REGISTER'} def execute(self, context): _LOG.enter() name = IMPORTER_PRESETS_PROPERTIES.get_value("name", entity_reference=context.scene) if not name is None: name = str(name).strip() if name == "" or name is None: self.report({'ERROR'}, "A valid name must be given") return {'FINISHED'} if " " in str(name): self.report({'ERROR'}, "Presets names should not contain spaces") return {'FINISHED'} confdir = LocationService.get_user_config() file_name = os.path.join(confdir, "importer_presets." + name + ".json")
from mpfb.services.logservice import LogService from mpfb.services.locationservice import LocationService from mpfb.ui.importerpresets.importerpresetspanel import IMPORTER_PRESETS_PROPERTIES from mpfb._classmanager import ClassManager import bpy _LOG = LogService.get_logger("importeroperators.overwritepresets") class MPFB_OT_OverwriteImporterPresetsOperator(bpy.types.Operator): """This will overwrite the importer presets selected in the dropdown above, using values from the fields below""" bl_idname = "mpfb.importerpresets_overwrite_importer_presets" bl_label = "Overwrite selected presets" bl_options = {'REGISTER'} def execute(self, context): _LOG.enter() name = IMPORTER_PRESETS_PROPERTIES.get_value( "available_presets", entity_reference=context.scene) file_name = LocationService.get_user_config("importer_presets." + name + ".json") excludes = ["available_presets", "name"] IMPORTER_PRESETS_PROPERTIES.serialize_to_json( file_name, entity_reference=context.scene, exclude_keys=excludes) self.report({'INFO'}, "Presets were written to " + file_name) return {'FINISHED'} ClassManager.add_class(MPFB_OT_OverwriteImporterPresetsOperator)
"""Operator for making left side a mirrored copy of right side.""" import bpy from mpfb.services.logservice import LogService from mpfb.services.objectservice import ObjectService from mpfb.services.targetservice import TargetService from mpfb import ClassManager _LOG = LogService.get_logger("maketarget.symmetrizeleft") class MPFB_OT_SymmetrizeLeftOperator(bpy.types.Operator): """Symmetrize by taking the right side of the model and copy it mirrored to the left side""" bl_idname = "mpfb.symmetrize_maketarget_left" bl_label = "Copy -x to +x" bl_options = {'REGISTER', 'UNDO'} @classmethod def poll(cls, context): if not ObjectService.object_is_basemesh(context.active_object): return False if context.active_object.data.shape_keys: return True return False def execute(self, context): blender_object = context.active_object TargetService.symmetrize_shape_key(blender_object, "PrimaryTarget", False) self.report({'INFO'}, "Target symmetrized") return {'FINISHED'}
"""This module only contains the SocketMeshObject. See the class docstring for more info.""" import gc, numpy from mpfb.services.logservice import LogService _LOG = LogService.get_logger("socketobject.socketmeshobject") class SocketMeshObject: """This is an abstract helper object for importing meshes from MakeHuman. We get the data as numpy arrays from MH, and the idea here is to sort and transform things as far as possible in numpy before applying the data as a blender mesh object.""" def __init__(self, importer_presets=None, object_type="GENERAL"): """Construct a SockeMeshObject. Normally this is only called by descendants. It is unlikely you want to directly instance a SocketMeshObject.""" # TODO: Make sure importer_presets is a dict, not a sceneconfigset _LOG.debug("Constructing new socket mesh object") self._importer_presets = importer_presets self._object_info = dict() self._object_type = object_type self._vertices = None self._faces = None self._texco = None self._uvmappings = None self._sorted_face_uv_and_texco = None self._vertex_groups_by_name = dict()
from mpfb.services.logservice import LogService _LOG = LogService.get_logger("rigifyoperators.init") _LOG.trace("initializing rigify operators") from .converttorigify import MPFB_OT_Convert_To_Rigify_Operator __all__ = ["MPFB_OT_Convert_To_Rigify_Operator"]
"""Operator for importing MHCLO clothes from asset library.""" import bpy from bpy.props import StringProperty from mpfb.services.logservice import LogService from mpfb.services.objectservice import ObjectService from mpfb.services.humanservice import HumanService from mpfb import ClassManager _LOG = LogService.get_logger("assetlibrary.loadlibraryclothes") class MPFB_OT_Load_Library_Clothes_Operator(bpy.types.Operator): """Load MHCLO from asset library.""" bl_idname = "mpfb.load_library_clothes" bl_label = "Load" bl_options = {'REGISTER', 'UNDO'} filepath: StringProperty(name="filepath", description="Full path to asset", default="") object_type: StringProperty(name="object_type", description="type of the object", default="Clothes") material_type: StringProperty(name="material_type", description="type of material", default="MAKESKIN") def execute(self, context): _LOG.debug("filepath", self.filepath)
"""File containing the base class for UI panels""" import bpy from mpfb.services.logservice import LogService from mpfb.services.uiservice import UiService _LOG = LogService.get_logger("ui.abstractpanel") class Abstract_Panel(bpy.types.Panel): """Human modeling panel.""" bl_label = "Abstract panel" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_category = UiService.get_value("DEVELOPERCATEGORY") bl_options = {'DEFAULT_CLOSED'} def create_box(self, layout, box_text, icon=None): _LOG.enter() box = layout.box() box.label(text=box_text) return box def _create_box(self, layout, box_text, icon=None): return self.create_box(layout, box_text, icon)
from mpfb.services.logservice import LogService from mpfb.services.uiservice import UiService from mpfb.services.locationservice import LocationService from mpfb.services.objectservice import ObjectService from mpfb.services.nodeservice import NodeService from mpfb.services.materialservice import MaterialService from mpfb.services.humanservice import HumanService from mpfb.ui.humanpresets.humanpresetspanel import HUMAN_PRESETS_PROPERTIES from mpfb._classmanager import ClassManager import bpy, os, json _LOG = LogService.get_logger("humanpresets.savenewpresets") class MPFB_OT_Save_New_Presets_Operator(bpy.types.Operator): """This will save new human preset""" bl_idname = "mpfb.save_new_human_presets" bl_label = "Save new presets" bl_options = {'REGISTER'} def execute(self, context): _LOG.enter() if context.object is None: self.report({'ERROR'}, "Must have a selected object") return {'FINISHED'} name = HUMAN_PRESETS_PROPERTIES.get_value("name", entity_reference=context) if not name is None: name = str(name).strip()
from mpfb.services.logservice import LogService from mpfb.services.locationservice import LocationService from mpfb.services.objectservice import ObjectService from mpfb.services.nodeservice import NodeService from mpfb.services.materialservice import MaterialService from mpfb.ui.enhancedsettings.enhancedsettingspanel import ENHANCED_SETTINGS_PROPERTIES from mpfb._classmanager import ClassManager import bpy, os, json _LOG = LogService.get_logger("enhancedsettings.applysettings") class MPFB_OT_ApplyEnhancedSettingsOperator(bpy.types.Operator): """This will load the enhanced material setting selected in the dropdown above, and use these to update the materials on the selected object""" bl_idname = "mpfb.enhancedsettings_apply_settings" bl_label = "Apply selected presets" bl_options = {'REGISTER'} def execute(self, context): _LOG.enter() if context.object is None: self.report({'ERROR'}, "Must have a selected object") return {'FINISHED'} name = ENHANCED_SETTINGS_PROPERTIES.get_value("available_settings", entity_reference=context) if not name: self.report({'ERROR'}, "Must select settings to load") return {'FINISHED'}
from mpfb.services.logservice import LogService from mpfb.services.materialservice import MaterialService from mpfb.services.nodeservice import NodeService from mpfb._classmanager import ClassManager import bpy, json from bpy.types import StringProperty from bpy_extras.io_utils import ImportHelper _LOG = LogService.get_logger("loadnodes.operators.loadnodes") class MPFB_OT_Load_Nodes_Operator(bpy.types.Operator, ImportHelper): """Load node tree from json""" bl_idname = "mpfb.load_nodes" bl_label = "Load nodes" bl_options = {'REGISTER'} filename_ext = '.json' @classmethod def poll(self, context): if context.active_object is not None: return not MaterialService.has_materials(context.active_object) return False def execute(self, context): _LOG.enter() _LOG.debug("click") blender_object = context.active_object if len(blender_object.material_slots) > 0:
from mpfb.services.logservice import LogService _LOG = LogService.get_logger("importeroperators.init") _LOG.trace("initializing importer operators") from .importhuman import MPFB_OT_ImportHumanOperator __all__ = ["MPFB_OT_ImportHumanOperator"]
"""This module sets up and provide global custom properties for blender objects, for example of what type a certain object is. See JSON data under "generalproperties" for information about the actual properties""" import bpy, os from mpfb.services.logservice import LogService _LOG = LogService.get_logger("objectproperties.init") _LOG.trace("initializing object properties module") from mpfb.services.blenderconfigset import BlenderConfigSet _ROOT = os.path.dirname(__file__) _GENERAL_PROPERTIES_DIR = os.path.join(_ROOT, "generalproperties") _GENERAL_PROPERTIES = BlenderConfigSet.get_definitions_in_json_directory( _GENERAL_PROPERTIES_DIR) # This is the general properties object that can be imported GeneralObjectProperties = BlenderConfigSet(_GENERAL_PROPERTIES, bpy.types.Object, prefix="GEN_") # pylint: disable=C0103 _HUMAN_PROPERTIES_DIR = os.path.join(_ROOT, "humanproperties") _HUMAN_PROPERTIES = BlenderConfigSet.get_definitions_in_json_directory( _HUMAN_PROPERTIES_DIR) # This is the human properties object that can be imported HumanObjectProperties = BlenderConfigSet(_HUMAN_PROPERTIES, bpy.types.Object,
from mpfb._classmanager import ClassManager from mpfb.services.logservice import LogService from mpfb.services.uiservice import UiService from mpfb.ui.abstractpanel import Abstract_Panel _LOG = LogService.get_logger("ui.assetspanel") class MPFB_PT_Assets_Panel(Abstract_Panel): bl_label = "Apply assets" bl_category = UiService.get_value("MATERIALSCATEGORY") def draw(self, context): _LOG.enter() layout = self.layout scn = context.scene ClassManager.add_class(MPFB_PT_Assets_Panel)
from mpfb.services.locationservice import LocationService from mpfb.services.socketservice import SocketService from mpfb.services.objectservice import ObjectService from mpfb.services.materialservice import MaterialService from mpfb.services.nodeservice import NodeService from mpfb.services.uiservice import UiService from mpfb.ui.importer.importerpanel import IMPORTER_PROPERTIES from mpfb.ui.importerpresets.importerpresetspanel import IMPORTER_PRESETS_PROPERTIES from mpfb._classmanager import ClassManager from mpfb.entities.socketobject.socketbodyobject import SocketBodyObject from mpfb.entities.socketobject.socketproxyobject import SocketProxyObject from mpfb.entities.material.makeskinmaterial import MakeSkinMaterial from mpfb.entities.material.enhancedskinmaterial import EnhancedSkinMaterial import bpy, os, json _LOG = LogService.get_logger("importer.operators.importhuman") class MPFB_OT_ImportHumanOperator(bpy.types.Operator): """Import human from MakeHuman""" bl_idname = "mpfb.importer_import_body" bl_label = "Import human" bl_options = {'REGISTER', 'UNDO'} def _get_settings_from_ui(self, context): _LOG.enter() selected_presets = IMPORTER_PROPERTIES.get_value("presets_for_import", entity_reference=context, default_value="FROM_UI") _LOG.debug("import with presets:", selected_presets) json_with_overrides = None
from mpfb.services.logservice import LogService _LOG = LogService.get_logger("humanpresets.operators.init") _LOG.trace("initializing human presets operators") from .savenewpresets import MPFB_OT_Save_New_Presets_Operator from .overwritepresets import MPFB_OT_Overwrite_Human_Presets_Operator __all__ = [ "MPFB_OT_Overwrite_Human_Presets_Operator", "MPFB_OT_Save_New_Presets_Operator" ]
"""Asset library subpanels""" import bpy from mpfb import ClassManager from mpfb.services.logservice import LogService from mpfb.services.assetservice import AssetService, ASSET_LIBRARY_SECTIONS from mpfb.ui.assetlibrary.assetsettingspanel import ASSET_SETTINGS_PROPERTIES _LOG = LogService.get_logger("assetlibrary.assetlibrarypanel") _NOASSETS = [ "No assets in this section.", "Maybe set MH user data preference", "or install assets in MPFB user data" ] class _Abstract_Asset_Library_Panel(bpy.types.Panel): """Asset library panel.""" bl_label = "SHOULD BE OVERRIDDEN" bl_space_type = "VIEW_3D" bl_region_type = "UI" bl_parent_id = "MPFB_PT_Assets_Panel" bl_options = {'DEFAULT_CLOSED'} asset_subdir = "-" asset_type = "mhclo" skin_overrides = False eye_overrides = False object_type = "Clothes"
"""This module contains the default rig's implementation of the arm helpers class""" import bpy from mpfb.services.logservice import LogService _LOG = LogService.get_logger("armhelpers.defaultarmhelpers") from mpfb.services.rigservice import RigService from mpfb.services.righelpers.armhelpers.armhelpers import ArmHelpers _ROTATION_LIMITS = { "lowerarm01": { "X": [-40, 125], "Y": [-45, 45] }, "lowerarm02": { "Y": [-45, 45] }, "upperarm01": { "X": [-45, 90], "Y": [-45, 45] }, "upperarm02": { "Y": [-45, 45] }, "shoulder01": { "X": [-30, 60], "Y": [-60, 60], "Z": [-20, 40] }, "clavicle": {
from mpfb.services.logservice import LogService from mpfb.services.materialservice import MaterialService from mpfb.services.nodeservice import NodeService from mpfb._classmanager import ClassManager import bpy, json from bpy.types import StringProperty from bpy_extras.io_utils import ExportHelper _LOG = LogService.get_logger("savenodes.operators.savenodes") class MPFB_OT_Save_Nodes_Operator(bpy.types.Operator, ExportHelper): """Save node tree as json""" bl_idname = "mpfb.save_nodes" bl_label = "Save nodes" bl_options = {'REGISTER'} filename_ext = '.json' @classmethod def poll(self, context): if context.active_object is not None: return MaterialService.has_materials(context.active_object) return False def execute(self, context): _LOG.enter() _LOG.debug("click") blender_object = context.active_object
"""Operator for loading and applying eye material settings.""" from mpfb.services.logservice import LogService from mpfb.services.locationservice import LocationService from mpfb.services.objectservice import ObjectService from mpfb.services.nodeservice import NodeService from mpfb.services.materialservice import MaterialService from mpfb.ui.eyesettings.eyesettingspanel import EYE_SETTINGS_PROPERTIES from mpfb._classmanager import ClassManager import bpy, os, json _LOG = LogService.get_logger("eyesettings.applysettings") class MPFB_OT_ApplyEyeSettingsOperator(bpy.types.Operator): """This will load the eye material setting selected in the dropdown above, and use these to update the materials on the selected object""" bl_idname = "mpfb.eyesettings_apply_settings" bl_label = "Apply selected presets" bl_options = {'REGISTER'} def execute(self, context): _LOG.enter() if context.object is None: self.report({'ERROR'}, "Must have a selected object") return {'FINISHED'} name = EYE_SETTINGS_PROPERTIES.get_value("available_settings", entity_reference=context) if not name:
"""Operators for loading clothes""" from mpfb.services.logservice import LogService _LOG = LogService.get_logger("loadclothes.operators.init") _LOG.trace("initializing load clothes operators") from .loadclothes import MPFB_OT_Load_Clothes_Operator __all__ = ["MPFB_OT_Load_Clothes_Operator"]
from mpfb.services.logservice import LogService from mpfb.services.locationservice import LocationService from mpfb.ui.importerpresets.importerpresetspanel import IMPORTER_PRESETS_PROPERTIES from mpfb._classmanager import ClassManager import bpy _LOG = LogService.get_logger("importeroperators.loadpresets") class MPFB_OT_LoadImporterPresetsOperator(bpy.types.Operator): """This will load the importer presets selected in the dropdown above, and use these presets to populate the fields below""" bl_idname = "mpfb.importerpresets_load_importer_presets" bl_label = "Load selected presets" bl_options = {'REGISTER'} def execute(self, context): _LOG.enter() name = IMPORTER_PRESETS_PROPERTIES.get_value( "available_presets", entity_reference=context.scene) file_name = LocationService.get_user_config("importer_presets." + name + ".json") IMPORTER_PRESETS_PROPERTIES.deserialize_from_json( file_name, entity_reference=context.scene) self.report({'INFO'}, "Presets were loaded from " + file_name) return {'FINISHED'} ClassManager.add_class(MPFB_OT_LoadImporterPresetsOperator)
"""Operator for importing an asset pack.""" import bpy, os from bpy_extras.io_utils import ImportHelper from bpy.props import StringProperty from mpfb.services.logservice import LogService from mpfb.services.locationservice import LocationService from mpfb.services.assetservice import AssetService import zipfile from mpfb import ClassManager _LOG = LogService.get_logger("assetlibrary.loadpack") class MPFB_OT_Load_Pack_Operator(bpy.types.Operator, ImportHelper): """Install an asset pack from ZIP file. You can find more asset packs to download by clicking the asset packs button under "system and resources" -> "web resources" """ bl_idname = "mpfb.load_pack" bl_label = "Load pack from zip file" bl_options = {'REGISTER'} filter_glob: StringProperty(default='*.zip', options={'HIDDEN'}) def execute(self, context): if not self.filepath: self.report({'ERROR'}, "Must select a file") return {'FINISHED'} if not os.path.exists(self.filepath):