Forum: Poser Python Scripting


Subject: List of bitmaps used by a figure/prop

Y-Phil opened this issue on Jan 01, 2024 ยท 11 posts


Y-Phil posted Mon, 15 January 2024 at 12:21 PM

Here is a version that exports the bitmaps used, by layer. The layers that exists but without using any bitmap are simply ignored.
The export is done accordingly.

try:
    import poser
except ImportError:
    import _POSER_FAKE as poser

import wx
import wx.lib.agw.flatnotebook as fnb
import wx.lib.agw.hypertreelist as HTL
import os
import os.path
import configparser

HTL_ITEM_NORMAL=0
HTL_ITEM_CHECKBOX=1
HTL_ITEM_RADIO=2

class CypMaterialsList(wx.Frame):

    # ---------- Dialog part

    def __init__(self, dlg_name):
        self.parent = poser.WxAuiManager().GetManagedWindow().GetParent()
        self.title_font = wx.Font(9, wx.DECORATIVE, wx.NORMAL, wx.BOLD)
        self.dlg_name = dlg_name

        self.load_config()
        self.load_environment()

        wx.Frame.__init__(
            self,
            self.parent,
            title=self.get_dialog_title(),
            pos=(self.dlg_x, self.dlg_y),
            size=(self.dlg_w, self.dlg_h),
            style=wx.DEFAULT_FRAME_STYLE|wx.FRAME_TOOL_WINDOW|wx.FRAME_FLOAT_ON_PARENT|wx.FRAME_NO_TASKBAR|wx.FRAME_DRAWER,
            name=dlg_name
        )

        self.Bind(wx.EVT_CLOSE, self.on_close)
        self.Bind(wx.EVT_ACTIVATE, self.on_activate)
        self.Bind(wx.EVT_SIZING, self.on_sizing)

        self.main_panel = wx.Panel(self, size=(5,150))
        self.notebook = fnb.FlatNotebook(
            self.main_panel,
            #        fnb.FNB_HIDE_TABS
            agwStyle=fnb.FNB_FANCY_TABS|fnb.FNB_NO_X_BUTTON|fnb.FNB_NO_NAV_BUTTONS|fnb.FNB_NO_TAB_FOCUS
        )
        self.notebook.SetBackgroundColour(wx.Colour(192,192,192))
        self.moving_controls = {}

        self.create_materiels_page()
        self.create_tools_page()

        main_sizer = wx.BoxSizer()
        main_sizer.Add(self.notebook, 1, wx.ALL | wx.EXPAND, 5)
        self.main_panel.SetSizer(main_sizer)

        self.Show()

    def create_tools_page(self):
        self.cmd_panel = wx.Panel(self.notebook)

        # Action button
        x = 120
        y = 100
        self.button_export = wx.Button(
                self.cmd_panel,
                label='Export',
                pos=(self.dlg_w-x, self.dlg_h-y),
                name='button_export'
            )
        self.moving_controls[self.button_export] = (x, y)
        self.button_export.Bind(wx.EVT_BUTTON, self.action_export)

        self.notebook.AddPage(self.cmd_panel, "Tools")

    def create_materiels_page(self):
        self.HTL_mat_panel = wx.Panel(self.notebook)
        self.mats_tree_list = HTL.HyperTreeList(
            self.HTL_mat_panel,
            agwStyle=wx.TR_DEFAULT_STYLE
        )

        self.mats_tree_list.AddColumn("Materials")
        self.mats_tree_list.AddColumn("Gamma")
        self.mats_tree_list.AddColumn("Filtering")
        self.mats_tree_root = self.mats_tree_list.AddRoot("Root")
        self.mats_tree_root.Expand()
        self.tree_root_materials = self.mats_tree_list.AppendItem(self.mats_tree_root, "Object", ct_type=HTL_ITEM_NORMAL)
        self.tree_root_materials.Expand()
        self.mats_tree_list.SetColumnWidth(0, int(self.dlg_w*0.70))

        self.HTL_Prepare_materials_list()

        sizer = wx.BoxSizer()
        sizer.Add(self.mats_tree_list, 1, wx.ALL | wx.EXPAND, 5)
        self.HTL_mat_panel.SetSizer(sizer)

        # Drop all this in a notebook page
        self.notebook.AddPage(self.HTL_mat_panel, "Material List" )

    # ---------- HyperTreeList mamagenemt

    def _deep_scan_for_bitmaps(self, from_node=None):
        if not from_node: return {}

        current_list = {}
        detected = {}
        if from_node.Type() == 'image_map':
            sni = from_node.InputByInternalName('Image_Source')
            file_name = sni.Value()
            texture = sni.Texture()
            gamma = 2.2 if texture.UseSceneGamma() else texture.Gamma()
            filtering_i = int(from_node.InputByInternalName('Filtering').Value())
            filtering = "Filtering: " + { 1:'none', 2:'fast', 3:'quality', 4:'crisp'}.get(filtering_i, 'unknown')
            detected = {file_name: (str(gamma), filtering)}

        elif from_node.Type() == 'ccl_ImageTexture':
            sni = from_node.InputByInternalName('Image')
            file_name = sni.Value()
            texture = sni.Texture()
            gamma = 2.2 if texture.UseSceneGamma() else texture.Gamma()
            detected = {file_name: (str(gamma), "-")}

        if not detected:
            for sni in from_node.Inputs():
                in_node = sni.InNode()
                if in_node:
                    detected = self._deep_scan_for_bitmaps(from_node=in_node)
                    if detected:
                        current_list.update(detected)

        else:
            current_list.update(detected)

        return current_list

    def OnCompareItems(self, item1, item2):
        a = item1.Name()
        b = item2.Name()
        return (a > b) - (a < b)  # equivalent to old cmp function

    def HTL_mat_scan_4_bitmaps(self, material, start_place):
        mat_ok = False
        lay_count = 0
        for layer in material.Layers():
            if not mat_ok:
                mat_ok = True
                self.txt_material_list.append(material.Name())

            tree = layer.ShaderTree()
            root_node = tree.RendererRootNode(self.scene.CurrentRenderEngine())
            full_list = self._deep_scan_for_bitmaps(from_node=root_node)
            layer_name = layer.ExtName()
            layer_place = self.mats_tree_list.AppendItem(start_place, layer_name)
            self.txt_material_list.append(f"    {layer_name}")

            check = False
            for file_name, values in dict(sorted(full_list.items())).items():
                gamma, filtering = values #full_list[file_name]
                check = True
                self.txt_material_list.append(f"        {file_name}")
                img_info = self.mats_tree_list.AppendItem(layer_place, file_name)
                img_info.SetText(1, gamma)
                img_info.SetText(2, filtering)

            if check:
                lay_count += 1
                layer_place.Expand()
            else:
                layer_place.Hide(True)
                self.txt_material_list.pop()

        return lay_count

    def HTL_mats_scan(self, place, item):
        if not item.IsProp() and not item.IsBodyPart(): return

        sorted_materials = [
            material
            for material in item.Materials()
        ]
        from functools import cmp_to_key
        sorted_materials.sort(key=cmp_to_key(self.OnCompareItems))
        for material in sorted_materials:
            this = self.mats_tree_list.AppendItem(place, material.Name())
            if not self.HTL_mat_scan_4_bitmaps(material, this):
                this.Hide(True)
            else:
                this.Expand()

    def HTL_Prepare_materials_list(self):
        self.tree_root_materials.DeleteChildren(self.mats_tree_list.GetMainWindow())
        self.txt_material_list = []

        title = "Object"
        if self.figure:
            if self.figure.IsFigure():
                actor = self.figure.RootActor()
                title = "Figure: {}".format(self.figure.Name())
            else:
                actor = self.figure
                title = "{}: {}".format(title, actor.Name())

            self.txt_material_list = [title]
            self.HTL_mats_scan(self.tree_root_materials, actor)

        self.tree_root_materials.SetText(0, title)

    # ---------- Events management

    def on_close(self, event=None):
        try:
            if self.IsIconized(): self.Iconize(False)
            if self.IsMaximized(): self.Maximize(False)
            if not self.IsIconized():
                self.save_config()
        except: pass

        self.Destroy()

    def on_sizing(self, event=None):
        if event:
            new_w, new_h = event.GetSize()
            for ctrl, rel_pos in self.moving_controls.items():
                ctrl.Move(new_w-1-rel_pos[0], new_h-1-rel_pos[1])

        event.ResumePropagation(wx.EVENT_PROPAGATE_MAX)

    def on_activate(self, event=None):
        if not event: return
        if event.GetActive():
            self.load_environment()
            self.SetTitle(self.get_dialog_title())

            # ---------- Hypertreelists Management

            check = getattr(self, 'mats_tree_root', None)
            if check:
                self.HTL_Prepare_materials_list()

    def action_export(self, event=None):
        if not event: return

        if not self.txt_material_list:
            poser.DialogSimple.MessageBox("Nothing...")
            return

        get_open_file = poser.DialogFileChooser(
            poser.kDialogFileChooserSave,
            self.figure.Name(),
            "Select output file",
            wildcard="Text files (*.txt)|*.txt"
        )
        if (get_open_file.Show()):
            output = open(get_open_file.Path(), 'w')
            for line in self.txt_material_list:
                print(line, file=output)
            output.close()

    # ---------- Gen Tools

    def get_render_engine(self):
        try:
            return {
                4: " - Superfly",
                1: " - Firefly",
                3: " - Sketch",
                0: " - Preview"
            }.get(self.scene.CurrentRenderEngine(), "")
        except:
            return ''

    def load_config(self, def_x=100, def_y=200, def_w=750, def_h=300):
        '''
            Load the dialog's position from the configuration file, if any
        '''
        self.section_name = "Materials List"

        self.configFile = os.path.join(poser.PrefsLocation(), "Cyp" + os.extsep + "cfg")
        self.config = configparser.RawConfigParser()
        self.config.read(self.configFile)
        if not self.config.has_section(self.section_name): self.config.add_section(self.section_name)

        try: self.dlg_x = self.config.getint(self.section_name, "x")
        except: self.dlg_x = def_x
        try: self.dlg_y = self.config.getint(self.section_name, "y")
        except: self.dlg_y = def_y
        try: self.dlg_w = self.config.getint(self.section_name, "w")
        except: self.dlg_w = def_w
        try: self.dlg_h = self.config.getint(self.section_name, "h")
        except: self.dlg_h = def_h

    def save_config(self):
        r = self.GetScreenRect()

        self.config.set(self.section_name, "x", r.left)
        self.config.set(self.section_name, "y", r.top)
        self.config.set(self.section_name, "w", r.width)
        self.config.set(self.section_name, "h", r.height)
        self.config.write(open(self.configFile, "w"))

    def load_environment(self):
        '''
            Load all values for the dialog,
        '''
        self.scene = poser.Scene()
        try:
            figure = self.scene.CurrentActor()
            if figure.IsBodyPart():
                figure = self.scene.CurrentFigure()
        except:
            figure = None

        self.figure = figure

    def get_dialog_title(self):
        title = "List of materials {}".format(self.get_render_engine())
        if self.figure:
            title = "{}: {}".format(title, self.figure.Name())

        return title


dlg_name = "Cyp's Materials List"
# Kill any stalled previous instances
[w.Destroy() for w in poser.WxAuiManager().GetManagedWindow().GetParent().Children if w.GetName()==dlg_name]
CypMaterialsList(dlg_name)

In the case of the previous example (#4480373):



And the exported file looks like this:



๐’ซ๐’ฝ๐“Ž๐“


(ใฃโ—”โ—กโ—”)ใฃ

๐Ÿ‘ฟ Win11 on i9-13900K@5GHz, 64GB, RoG Strix B760F Gamng, Asus Tuf Gaming RTX 4070 OC Edition, 1 TB SSD, 6+4+8TB HD
๐Ÿ‘ฟ Mac Mini M2, Sequoia 15.2, 16GB, 500GB SSD
๐Ÿ‘ฟ Nas 10TB
๐Ÿ‘ฟ Poser 13 and soon 14 โค๏ธ