Sat, Jan 4, 5:17 PM CST

Renderosity Forums / Poser Python Scripting



Welcome to the Poser Python Scripting Forum

Forum Moderators: Staff

Poser Python Scripting F.A.Q (Last Updated: 2024 Dec 02 3:16 pm)

We now have a ProPack Section in the Poser FreeStuff.
Check out the new Poser Python Wish List thread. If you have an idea for a script, jot it down and maybe someone can write it. If you're looking to write a script, check out this thread for useful suggestions.

Also, check out the official Python site for interpreters, sample code, applications, cool links and debuggers. This is THE central site for Python.

You can now attach text files to your posts to pass around scripts. Just attach the script as a txt file like you would a jpg or gif. Since the forum will use a random name for the file in the link, you should give instructions on what the file name should be and where to install it. Its a good idea to usually put that info right in the script file as well.

Checkout the Renderosity MarketPlace - Your source for digital art content!



Subject: List of bitmaps used by a figure/prop


Y-Phil ( ) posted Mon, 01 January 2024 at 6:46 PM · edited Sat, 04 January 2025 at 5:15 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.

UBc90eupaVbiz3hAKfAP0Wjh9dtt6O84XPCAWLqW.png

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 · edited Tue, 02 January 2024 at 10:05 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) 6VCQn5JJTYXkip0JkIXEtBiGJd0M0xx2gZCbc5Ia.gif

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 · edited Sun, 14 January 2024 at 2:22 PM

Here you are... yGFacP6WYQEdNJ7QcIoLAyVPDtyXH3WKKQG2lJY9.gif
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! xdqUSaPt3rGRkW5ODikEUWvxKqOxdCI7PPecjSxS.gif

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:

qaP5FHtUDHeXAcpNEliRpcEuT0D10HCuV1V3bfIo.png

SOdnCgS2DD16nvMGwI58UyXnEJOym1VaeOBxwhuC.png

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.
5EqOnWCyXFYcnA83r50wARguqbLJVMrluvuCd2sn.png


𝒫𝒽𝓎𝓁


(っ◔◡◔)っ

👿 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 · edited Sun, 14 January 2024 at 2:31 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 · edited Sun, 14 January 2024 at 4:18 PM

Content Advisory! This message contains nudity

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

89pZr1lXEJ5dvFvo8ImSvDbxQeM6AUN5JPsNyn3y.png

"Base layer": only typical bitmaps:

qz1S3h318sq8zeMARukzrvij3tkDk8drErhsq5Fp.png

Whereas the "second layer" is using its own bitmap

nuqnhrVeXnyqvubHhOc0sN1dfyWbFpu2Igj5LL2W.png

𝒫𝒽𝓎𝓁


(っ◔◡◔)っ

👿 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 · edited Mon, 15 January 2024 at 12:22 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):

toSpBV4o7JcZUl8Mnm5zAj4XVm9eKSI9zrK7v6Uy.png

And the exported file looks like this:

sM0rRam9eikxkliOTxly5rl5CTmlMLMl7BsJWmwk.png

𝒫𝒽𝓎𝓁


(っ◔◡◔)っ

👿 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 · edited 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 H4CwBOWUSCAAo5umIrA0XRfrcRC3nJhFSoqU7Rn1.png

𝒫𝒽𝓎𝓁


(っ◔◡◔)っ

👿 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 ❤️


Privacy Notice

This site uses cookies to deliver the best experience. Our own cookies make user accounts and other features possible. Third-party cookies are used to display relevant ads and to analyze how Renderosity is used. By using our site, you acknowledge that you have read and understood our Terms of Service, including our Cookie Policy and our Privacy Policy.