OverviewFilesCommitsIssuesDocsReleasesPeers

hardops / operators / third_party / dm_edit_mode.py

276 lines · python ref:
import bpy, os, bmesh
from pathlib import Path
from bpy_extras.image_utils import load_image
from ... utility import addon
from ...ui_framework.master import Master
from ...utils.addons import addon_exists
from ... icons import icons_directory


class HOPS_OT_DM2_Window(bpy.types.Operator):

    """DM2 Window"""
    bl_idname = "hops.dm2_window"
    bl_label = "DM2 Window"
    bl_options = {"REGISTER", "UNDO"}
    bl_description = """DECALmachine 2 Window
    
    Displays decal machine decal list for edit mode usage. 
    Supports DecalMachine2 only.

    Ctrl + click decal to add blank material + decal
        
    """


    def invoke(self, context, event):

        # Validate
        if context.active_object == None:
            return {'CANCELLED'}
        if context.active_object.type != 'MESH':
            return {'CANCELLED'}
        if context.mode != 'EDIT_MESH':
            return {'CANCELLED'}
        if addon_exists("DECALmachine") == False:
            return {'CANCELLED'}

        from DECALmachine.utils.registration import get_prefs
        from DECALmachine import bl_info

        # Validate Version
        if bl_info['version'][0] < 2:
            return {'CANCELLED'}

        decallibsCOL = get_prefs().decallibsCOL
        self.trimlibs = [lib for lib in decallibsCOL if lib.isvisible and lib.istrimsheet]
        self.dm_prefs = get_prefs()
        self.dm_assets_path = self.dm_prefs.assetspath
        self.collections = {}
        self.active_pack = ""
        self.build_collection = True
        self.exit = False
        self.window_name = f'DECALmachine {bl_info["version"][0]}.{bl_info["version"][1]}.{bl_info["version"][2]}'
        self.obj = context.active_object

        # Run modal
        self.master = Master(context=context, custom_preset="preset_kit_ops", show_fast_ui=False)
        context.window_manager.modal_handler_add(self)
        return {'RUNNING_MODAL'}


    def modal(self, context, event):

        # UI Update
        self.master.receive_event(event=event)

        # Navigation
        if event.type in {'MIDDLEMOUSE', 'WHEELUPMOUSE', 'WHEELDOWNMOUSE'} and event.ctrl != True:
            if not self.master.is_mouse_over_ui():
                return {'PASS_THROUGH'}

        # Confirm
        elif event.type == 'LEFTMOUSE':
            if not self.master.is_mouse_over_ui():
                self.master.run_fade()
                self.remove_images()
                return {'FINISHED'}

        # Cancel
        elif event.type in {'RIGHTMOUSE', 'ESC'}:
            if not self.master.is_mouse_over_ui():
                self.master.run_fade()
                self.remove_images()
                return {'CANCELLED'}

        self.draw_window(context, event)

        # Decal was loaded now exit
        if self.exit == True:
            if event.value == 'RELEASE':
                self.master.run_fade()
                self.remove_images()
                context.area.tag_redraw()
                bpy.ops.machin3.trim_unwrap('INVOKE_DEFAULT', library_name=self.library_name, trim_name=self.decal_name, rotate=0)
                return {'FINISHED'}

        context.area.tag_redraw()
        return {'RUNNING_MODAL'}


    def draw_window(self, context, event):

        self.master.setup()
        prefs = addon.preference()
        main_window = {
            "folders" : [],
            "files"   : [],
            "images"  : []}

        # Build collections data
        if self.build_collection:
            self.build_collection = False

            for col in self.trimlibs:

                paths = self.dm_get_image_data(self.dm_assets_path, col.name)

                if paths != None:
                    self.collections[col.name] = paths
                    for item in reversed(self.collections[col.name]):
                        main_window["files"].append( [item[1]] )

        # Add collection
        for col_name, paths in self.collections.items():

            # Set initial pack
            if self.active_pack == "":
                self.active_pack = col_name

            # Highlight the active pack
            highlight = False
            if col_name == self.active_pack:
                highlight = True

            main_window["folders"].append([col_name, self.dm_set_active_pack, (col_name,), highlight])

            if col_name == self.active_pack:
                for items in reversed(paths):
                    image = items[1]
                    if image != None:

                        path = Path(image.filepath)
                        decal_dir = path.parts[-2]

                        if event.ctrl == True:
                            main_window["images"].append([image, self.dm_call_decal_insert, (col_name, decal_dir, True)])
                        else:
                            main_window["images"].append([image, self.dm_call_decal_insert, (col_name, decal_dir)])
                        main_window["files"].append( [decal_dir] )

            main_window["folders"].sort()
            main_window["folders"].reverse()

        self.master.receive_main(win_dict=main_window, window_name=self.window_name)
        self.master.finished()

    ##################################
    #   DECAL MACHINE
    ##################################

    def dm_get_image_data(self, assets_path="", col_name=""):

        trim_folder = os.path.join(assets_path, "Trims")
        directory = os.path.join(trim_folder, col_name)
        paths = []
        
        if os.path.exists(directory) == False:
            return None

        with os.scandir(directory) as it:
            for entry in it:
                if not entry.name.startswith('.'):

                    image_folder =  os.path.join(directory, entry.name)
                    image = self.dm_get_image(image_folder, image_name="decal")
                    if image == None:
                        continue

                    image_directory = os.path.join(directory, entry.name, "decal")
                    paths.append((image_directory, image))

        return paths


    def dm_get_image(self, image_folder, image_name):

        image = load_image_file(filename=image_name, directory=image_folder)

        if image != None:

            if bpy.app.version < (2, 83, 0):
                image.colorspace_settings.name = 'Linear'
            else:
                image.colorspace_settings.name = 'sRGB'

        if image == None:
            return None

        return image


    def dm_set_active_pack(self, pack):

        if pack in self.collections:
            self.active_pack = pack


    def dm_call_decal_insert(self, library_name, decal_name, with_mats=False):

        if with_mats == True:
            self.add_blank_materials()

        self.library_name = library_name
        self.decal_name = decal_name
        self.exit = True

    ##################################
    #   UTILS
    ##################################

    def add_blank_materials(self):
        '''Add blank materials to object so that DM trim sheets looks more controlled.'''

        mesh = self.obj.data
        bm = bmesh.from_edit_mesh(mesh)
        bm.faces.ensure_lookup_table()

        sel_faces = [f for f in bm.faces if f.select == True]


        # Add material to entire obect
        bpy.ops.mesh.select_all(action='DESELECT')

        if len(self.obj.material_slots) > 0:
            for face in bm.faces:
                slot = self.obj.material_slots[face.material_index]
                mat = slot.material
                if mat == None:
                    face.select = True
        else:
            bpy.ops.mesh.select_all(action='SELECT')

        bpy.ops.material.hops_new('INVOKE_DEFAULT')
        bpy.ops.mesh.select_all(action='DESELECT')

        # Add material to selected faces
        for f in sel_faces:
            f.select = True
        bpy.ops.material.hops_new('INVOKE_DEFAULT')

        bm.free()
        bmesh.update_edit_mesh(mesh)

        bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
        bpy.ops.object.mode_set(mode='EDIT', toggle=False)


    def remove_images(self):

        # Unload images : DM
        if self.collections != {}:
            for key, val in self.collections.items():
                for items in val:
                    image = items[1]
                    if image != None:
                        try:
                            bpy.data.images.remove(image)
                        except:
                            pass


def load_image_file(filename="", directory=""):
    '''Return the loaded image.'''

    return load_image(filename + ".png", dirname=directory)