Forum: Poser Python Scripting


Subject: Example program: Figure hierarchy and recursion for beginners

Bastep opened this issue on Nov 22, 2023 ยท 4 posts


Bastep posted Wed, 22 November 2023 at 3:58 PM

Example program: Figure hierarchy and recursion for beginners

I have been playing with the actor hierarchy of a figure for the last few days.

This hierarchy is a classic data tree. It allows you to work very effectively with recursive methods. A recursive method is a method that calls itself again and again. It is important that this method has a termination condition that can be fulfilled. Otherwise our method disappears into nowhere.

The structure of the hierarchy of a figure is quite simple. Each figure has a root actor method. This returns the first actor. This is always the body actor.
Each actor has a list of actor children. This list can be queried with actor.Children().

This list also provides us with the perfect termination condition. If the list is empty, our recursive method jumps back to the previous recursive method.

Each actor of a figure has the type ID_TYPE_BODY_ACTOR = 7. ID_TYPE_BODY_ACTOR is a separate definition that I use for the actor.GetIdType() method. There are no definitions of the values returned by GetIdType() in the Poser API. I use GetIdType() to filter the objects I want to see and not see.

The example program can be downloaded here: Example program: Figure hierarchy and recursion
The installation is done as usual via

Here is the source code:

#----------------------------------------------------------------------------------
# RecursiveFigureCrawler.py
# Definition of the class for browsing the figure Hierachy
# Date:             03.11.2023 23:41:36
# Last Change:      08.11.2023 22:03:28
# Version:          1.0
# Author:           Bastep
# Contact:          via Renderocity
#
# This is free to use.
#----------------------------------------------------------------------------------

import poser

# ID Types for actor.GetIdType().
# Missing in the Poser API
ID_TYPE_BODY_ACTOR              =    7
ID_TYPE_CAMERA                  =    8
ID_TYPE_LIGHT                   =    9
ID_TYPE_PROPS                   =   12
ID_TYPE_CURVE                   =   15 # from  an0malaus https://www.renderosity.com/forums/comments/4477830/permalink
ID_TYPE_MAGNET_BASE             =   16 # Magnet
ID_TYPE_MAGNET_DEFORMER         =   18 # Magnet
ID_TYPE_MAGNET_ZONE             =   21 # Magnet
ID_TYPE_HAIRPROP                =   23 # Only applies to hair done in the hair room.
ID_TYPE_UNIFORM_FORCE_FIELDPROP =   24 # from  an0malaus https://www.renderosity.com/forums/comments/4477830/permalink
ID_TYPE_CONE_FORCE_FIELD_PROP   =   25 # from  an0malaus https://www.renderosity.com/forums/comments/4477830/permalink
ID_TYPE_CONTROL_HANDLE_PROP     =   28
ID_TYPE_GROUPING                =   29
ID_TYPE_MEASUREMENT_LINE        =   34
ID_TYPE_MEASUREMENT_ANGLE       =   35 # from  an0malaus https://www.renderosity.com/forums/comments/4477830/permalink
ID_TYPE_MEASUREMENT_CIRCLE      =   36 # from  an0malaus https://www.renderosity.com/forums/comments/4477830/permalink
ID_TYPE_MEASUREMENT_POLYLINE    =   37 # from  an0malaus https://www.renderosity.com/forums/comments/4477830/permalink

# The following ID covers various scene objects:
# CenterOfMass
# GoalCenterOfMass
# BackgroundMaterialActor
# AtmosphereMaterialActor
# FocusDistanceControl
ID_TYPE_CONTROL_PROP        =   26

ID_WITH_PARENTED_FIGURE = True
ID_UNUSED_PARENTED_FIGURE = False

#----------------------------------------------------------------------------------
# Class for representing the object hierarchy of a figure.
#----------------------------------------------------------------------------------
class RecursiveFigureCrawlerClass:
    def __init__(self):
        # Counter to correctly indent an object/actor when displaying the hierarchy.
        self.__HierachyCounter  = 0

        self.__Figure   = poser.Scene().CurrentFigure()
        self.__Filter   = [ID_TYPE_BODY_ACTOR]

        # Figures that are connected to the target figure as non-conforming
        # child figures (parented) can be hidden(value = false).
        self.__UseParentedFigures = False # True = show the parented Figures

        # The hierarchy of figure actors begins with the root actor.
        # This does not necessarily have to be the body actor.
        # You can use the chest actor, for example.
        # You will then see everything that is attached to the chest actor.
        if self.__Figure:
            self.__RootActor = self.__Figure.RootActor()
        else:
            self.__RootActor = None

        #Clean layout of the print edition
        self.__fs_actor = '{0:02}{1:s}{2:s} - {3}'
        self.__fs_figure = '{0:02}{1:s}{2:s} - {3:s} - {4}'

    #----------------------------------------------------------------------------------
    # The recursive method
    #----------------------------------------------------------------------------------
    def Crawl(self, actor):
        if actor:
            # The cancel query. If there is no further actor in the child list,
            # we jump to the last line (100) and set the hierarchy counter back by 1 step.
            # Then we go back to the call of the crawl method. And so on ...
            for child in actor.Children():
                self.__HierachyCounter += 1
                if self.CrawlFilter(child): #The filter method. What we want to see ...
                    # Calculating the indent for displaying the hierarchy levels
                    hierachy = ' ' * (self.__HierachyCounter * 3)
                    # If the object is a parented figure, the name of the figure
                    # is placed in front of the bodyactor name.
                    if child.Name() == 'Body':
                        figure = child.ItsFigure()
                        print(self.__fs_figure.format(self.__HierachyCounter, hierachy, figure.Name(), child.Name(), child.GetIdType()))
                    else:
                        print(self.__fs_actor.format(self.__HierachyCounter, hierachy, child.Name(), child.GetIdType()))
                # Here the recursive method calls itself.
                # In Python, the maximum recursion depth is 1000.
                # The value can be changed with the function sys.setrecursionlimit(n).
                # This value can be queried with sys.getrecursionlimit().
                # But I don't think you will exceed this limit in Poser.
                self.Crawl(child)
            self.__HierachyCounter -= 1

    #----------------------------------------------------------------------------------
    # The filter method.
    #----------------------------------------------------------------------------------
    def CrawlFilter(self, actor):
        if not self.__UseParentedFigures:
            # The 'actor.ItsFigure() == self.__Figure' test prevents the insertion
            # of parented figures into the list
            if actor.ItsFigure() != self.__Figure:
                return False
        # Filter out the props you do not want
        if actor.GetIdType() not in self.__Filter:
            return False
        return True

    #----------------------------------------------------------------------------------
    # Initiates the recusion process
    #----------------------------------------------------------------------------------
    def ShowFigureActorTree(self):
        if self.__RootActor:
            print(self.__Figure.Name(), ' - ', self.__RootActor.Name())
            self.__HierachyCounter = 0
            # This is where the recursion process begins.
            self.Crawl(self.__RootActor)
        else:
            print('No figure is selected.')

    #----------------------------------------------------------------------------------
    # Set the class variables
    #----------------------------------------------------------------------------------
    def SetFigure(self, figure):
        self.__Figure = figure

    # The parameter withparentedfigures can either have the value
    # ID_WITH_PARENTED_FIGURE(True) or ID_UNUSED_PARENTED_FIGURE(False).
    # The list of idtypes is assigned to the idtypes parameter.
    # These can be specified in any number. They are each separated by a comma.
    def SetFilter(self, withparentedfigures, *idtypes):
        self.__Filter = []
        self.__UseParentedFigures = withparentedfigures
        for idtype in idtypes:
            self.__Filter.append(idtype)

    def SetRootActor(self, rootactor):
        self.__RootActor = rootactor

    def SetWithParentedFigures(self, withparentedfigures=True):
        self.__UseParentedFigures = withparentedfigures


#----------------------------------------------------------------------------------
# FigureCrawler.py
#
# Example program for using the FigureCrawlerClass
#
# Date:             08.11.2023 21:01:16
# Letzte Aenderung: 08.11.2023 21:01:19
# Version:          1.0
# Author:           Bastep
# Contact:          via Renderocity
#
# This is free to use.
#----------------------------------------------------------------------------------

import poser
import RecursiveFigureCrawler as myCrawler
import importlib
importlib.reload(myCrawler)

#----------------------------------------------------------------------------------
# Main
#----------------------------------------------------------------------------------
def main():
    example = 0 # 0 = Uses the values that are defined when the class is initialized
    fc = myCrawler.RecursiveFigureCrawlerClass()
    
    # Example using the body actor. Props such as earrings,
    # necklaces etc. are displayed. Parented figures are also displayed.
    if example == 1:
        fc.SetFilter(myCrawler.ID_WITH_PARENTED_FIGURE, myCrawler.ID_TYPE_BODY_ACTOR, myCrawler.ID_TYPE_PROPS)

    # Example using the neck actor. Only the body actors belonging
    # to the figure are displayed.
    elif example == 2:
        
        figure = poser.Scene().CurrentFigure()
        actor = figure.ActorByInternalName('neck')
        fc.SetFigure(figure)
        fc.SetRootActor(actor)
        fc.SetFilter(myCrawler.ID_UNUSED_PARENTED_FIGURE, myCrawler.ID_TYPE_BODY_ACTOR)

    # shows the tree
    fc.ShowFigureActorTree()

if __name__ == "__main__":
    main()