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, 01 January 2024 at 6:46 PM

I don't know it this could be useful for someone, but for my needs, I've written a tool that lists all bitmaps used by a figure, sorted by material list.
Actually, I don't check supplementary layer but I could add this.

It scans the currently selected object (prop or figure), check for the actual renderer and starts investigating  from there.
It's currently in my toolbox but if someone finds this useful, I could easily create an independent tool.
Currently tested on Poser 13, but I think that it should be easily doable to make it Poser11 compatible.

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


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

๐Ÿ‘ฟ 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 โค๏ธ


hborre posted Mon, 01 January 2024 at 11:21 PM

That might be practical.  It sure saves time hunting and picking for each material zone map.


Y-Phil posted Tue, 02 January 2024 at 10:02 AM

Ok, here is a version that runs on Poser12 and Poser13.

Even though I have avoided any Python3-specific instructions, I have not been able to instantiate the HyperTreeList control, something's making the original code crash on Poser11.
I could have used a simple list but the fact that it's hierarchically presented is one of the key point.
The window may remain opened, and pushed on a side of the screen. Each time the selection is done on another object and the window is once again activated, it is refreshed. Once the window is closed using its upper-right cross, it maintains its position and size for the next time.

Furthermore, that are probably a few useless instructions but consider that it's a small part (260 lines) among a rather bigger Python script (more than 3130 lines) 

try:
    import poser
except ImportError:
    import _POSER_FAKE as poser

import wx
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.HTL_mat_panel = wx.Panel(self)

        self.create_materiels_list()

        self.Show()

    def create_materiels_list(self):
        htl_width = self.dlg_w-500
        self.mats_tree_list = HTL.HyperTreeList(
            self.HTL_mat_panel,
            pos=(5,5),
            size=(htl_width, self.dlg_h-45),
            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)

    # ---------- 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, place):
        tree = material.ShaderTree()
        root_node = tree.RendererRootNode(self.scene.CurrentRenderEngine())
        full_list = self._deep_scan_for_bitmaps(from_node=root_node)

        check = False
        for file_name, values in dict(sorted(full_list.items())).items():
            gamma, filtering = values #full_list[file_name]
            check = True
            img_info = self.mats_tree_list.AppendItem(place, file_name)
            img_info.SetText(1, gamma)
            img_info.SetText(2, filtering)

        return check

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

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

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

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


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

๐Ÿ‘ฟ 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 โค๏ธ


jancory posted Sun, 14 January 2024 at 8:14 AM

script works great!  this is super useful.  is there a way to copy the list to text?


lost in the wilderness

Poser 13, Poser11,  Win7Pro 64, now with 24GB ram

ooh! i guess i can add my new render(only) machine!  Win11, I7, RTX 3060 12GB

 My Freebies



Y-Phil posted Sun, 14 January 2024 at 2:22 PM

Here you are... 
The insane part that the HyperTreeList control,  that allows the hierarchical presentation, is hard-coded to occupy the whole area of the dialog's windows. That's impossible to add a control up, or down, or on a side. Same for the Notebook control.
So that I had to add a notebook with two tabs, the second displaying the "Export" button, and more controls if you have other ideas! 

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, place):
        tree = material.ShaderTree()
        root_node = tree.RendererRootNode(self.scene.CurrentRenderEngine())
        full_list = self._deep_scan_for_bitmaps(from_node=root_node)

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

        return check

    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)

The result is this:



I know that the poor lonely button seems somewhat lost in its low right corner but I'm used to this kind of dialog setup...
Change the size of the window and it will follow your mouse.


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


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

๐Ÿ‘ฟ 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 โค๏ธ


jancory posted Sun, 14 January 2024 at 2:28 PM

thank you!  


also: what does "supplementary layer" mean? just curious


lost in the wilderness

Poser 13, Poser11,  Win7Pro 64, now with 24GB ram

ooh! i guess i can add my new render(only) machine!  Win11, I7, RTX 3060 12GB

 My Freebies



Y-Phil posted Sun, 14 January 2024 at 4:17 PM

jancory posted at 2:28 PM Sun, 14 January 2024 - #4480366

thank you!  


also: what does "supplementary layer" mean? just curious

This is because Poser can manage more than one layer, and actually my script only checks the layer named "Base".
For example, in this picture: the cloth is a second layer, using its own bitmap, and it appears as a second skin, That way I can handle the skin's glossiness and the cloth part's own glossiness separately

"Base layer": only typical bitmaps:


Whereas the "second layer" is using its own bitmap


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


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

๐Ÿ‘ฟ 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 โค๏ธ


jancory posted Sun, 14 January 2024 at 4:35 PM

oh yes PLEASE add layers to the options if you can.  i make & use them constantly. new script version works great.


lost in the wilderness

Poser 13, Poser11,  Win7Pro 64, now with 24GB ram

ooh! i guess i can add my new render(only) machine!  Win11, I7, RTX 3060 12GB

 My Freebies



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 โค๏ธ


jancory posted Mon, 15 January 2024 at 12:42 PM

perfect. thanks again.


lost in the wilderness

Poser 13, Poser11,  Win7Pro 64, now with 24GB ram

ooh! i guess i can add my new render(only) machine!  Win11, I7, RTX 3060 12GB

 My Freebies



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

jancory posted at 12:42 PM Mon, 15 January 2024 - #4480403

perfect. thanks again.

You're welcome 

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


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

๐Ÿ‘ฟ 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 โค๏ธ