Sat, Nov 9, 11:43 AM 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 Sep 18 2:50 am)

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: Modifying Materials


JimX ( ) posted Tue, 06 June 2023 at 2:57 PM · edited Sun, 03 November 2024 at 5:39 PM

I did some Python scripting for another application a few years ago. The Python knowledge is coming back just fine, but I am slowly slogging up the learning curve for the Poser classes and methods. I've studied the pydoc, and I've looked at a lot of sample scripts. I am using Poser 12 on a Mac.

My project involves changing the Texture Map (and maybe later, some other maps) on an object. I wrote a quick script to test my understanding of the process:

import poser

scene = poser.Scene()

actor = scene.CurrentActor()

mtl = actor.Material('hair')

mtl.SetDiffuseColor(0.5, 0.0, 0.0)

print ('Diffuse Color should have been set')

print ('Texture before =', mtl.TextureMapFileName() )

mtl.SetTextureMapFileName('/Users/Shared/Poser 12 Content/Downloads/Runtime/textures/KozHair/KyokoHair_Mk2/KyokoHr2TxBlondGold.jpg')

print ('Texture after =', mtl.TextureMapFileName() )


I have a basic scene with on Victoria 4 figure and one prop-style hair object parented to it. I have the hair selected, and in the Material Room I have the "hair" material selected. There is the Poser_Surface node, and three image nodes (one each for texture map, specular map, and transparency map). When I run the script, I get

Diffuse Color should have been set

Texture before = /Users/Shared/Poser 12 Content/Downloads/Runtime/textures/KozHair/KyokoHair_Mk2/KyokoHr2TxBlack.jpg

Texture after = /Users/Shared/Poser 12 Content/Downloads/Runtime/textures/KozHair/KyokoHair_Mk2/KyokoHr2TxBlondGold.jpg


So some texture map path somewhere seems to have been changed.

However, the Diffuse_Color on the Poser_Surface node has not changed at all. Nor have any of the texture maps in either of the image_map nodes changed (they are still exactly what was printed on the "Texture before = " line).

Clearly, my understanding of the process still needs some help. Does anyone have any pointers?

Many thanks for any suggestions.


adp001 ( ) posted Wed, 07 June 2023 at 5:42 AM · edited Wed, 07 June 2023 at 5:44 AM

The part of the material you addressed (via Python) is only the informative part. It serves for example to create an OBJ file or the corresponding ".mat" file in a simple way. This includes the texture names and some rudimentary information, which is (or should be) a summary of the used material shaders.

If you want to make a visible change to a material, you have to change the associated shader. Roughly, this requires:

* The corresponding root node (Firefly, Superfly).
* The shader tree based on it.
* The nodeset that represents the property to be changed (can also be a property of the root shader).

(A bit more complicated if the material uses material layers).


To change the base color of the preview material in the currently selected Poser Actor to red, the following is necessary:

material = poser.Scene().CurrentActor()
tree = material.ShaderTree()
# we know the first node in this shadertree is the root node, and we want to change the first entry of this node.
node = tree.Nodes()[0]
inp = node.Inputs()[0]
inp.SetColor(1.0, 0.0, 0.0) # RGB in floating point notation

or short:

material.ShaderTree().Nodes()[0].Inputs()[0].SetColor(1.0, 0.0, 0.0)






adp001 ( ) posted Wed, 07 June 2023 at 6:44 AM

Perhaps the following script (used as lib) may be helpful when working with materials:


def node_exist(stree, ntype, name):
"""
Check if a node of a certain type and name exist.
:param stree: Shadertree
:param ntype: Type of node
:param name: Name of node
:return: Node or None if nothing was fond

:type stree: poser.ShaderTree()
:type ntype: poser.ShaderNodeType()
:type name: basestring | str
"""
for node in stree.Nodes(): # type: poser.ShaderNodeType
if node.Type() == ntype and node.Name() == name:
return node
return None


def get_or_create_node(stree, ntype, name):
"""
Find a node in a shadertree. Create the node if it not yet exists.

:param stree: ShaderTree
:param ntype: Type of node
:param name: Name of node
:return: Node

:type stree: poser.ShaderTree()
:type ntype: poser.ShaderNodeType()
:type name: basestring | str
"""
node = node_exist(stree, ntype, name)
if not node:
node = stree.CreateNode(ntype)
node.SetName(name)
return node


def copy_input(source_input, target_input):
"""
Copy one Input-port of a shadernode to another.
:type source_input: poser.ShaderNodeInputType()
:type target_input: poser.ShaderNodeInputType()
:return: None
"""
assert isinstance(source_input, poser.ShaderNodeInputType)
assert isinstance(target_input, poser.ShaderNodeInputType)

target_input.SetName(source_input.Name())
if source_input.CanBeAnimated():
target_input.SetAnimated(source_input.Animated())

v = source_input.Value()
t = source_input.Type()
if t == poser.kNodeInputCodeCOLOR:
target_input.SetColor(*v)
elif t == poser.kNodeInputCodeSTRING:
target_input.SetString(v)
elif t == poser.kNodeInputCodeFLOAT:
target_input.SetFloat(v)

return target_input


def copy_node(source_node, target_node, target_tree):
"""
Copy a complete node to shadertree |target_tree|.
Nodes referencing this node are also copied if they do not yet
exist in the target shader tree.
:type source_node: poser.ShaderNodeType()
:type target_node: poser.ShaderNodeType()
:type target_tree: poser.ShaderTree()
:return: None
"""
assert isinstance(source_node, poser.ShaderNodeType)
assert isinstance(target_node, poser.ShaderNodeType)
assert isinstance(target_tree, poser.ShaderTreeType)

for inp_idx, node_input in enumerate(source_node.Inputs()):
copy_input(node_input, target_node.Input(inp_idx))
in_node = node_input.InNode()
if isinstance(in_node, poser.ShaderNodeType):
new_node = get_or_create_node(target_tree, in_node.Type(), in_node.Name())
copy_node(in_node, new_node, target_tree)
target_inp = target_node.Input(inp_idx)
target_tree.AttachTreeNodes(target_node, target_inp.Name(), new_node)

target_node.SetName(source_node.Name())
target_node.SetInputsCollapsed(source_node.InputsCollapsed())
target_node.SetLocation(*source_node.Location())
target_node.SetPreviewCollapsed(source_node.PreviewCollapsed())


def copy_shadertree(source_tree, target_tree):
"""
Copy all nodes of a shadertree into another shadertree.
:type source_tree: poser.ShaderTreeType()
:type target_tree: poser.ShaderTreeType()
:return: None
"""
assert isinstance(source_tree, poser.ShaderTreeType)
assert isinstance(target_tree, poser.ShaderTreeType)
for source_node in source_tree.Nodes(): # type: poser.ShaderNodeType
target_node = get_or_create_node(target_tree, source_node.Type(), source_node.Name())
copy_node(source_node, target_node, target_tree)
target_tree.UpdatePreview()


def copy_material(source_material, target_material, clean=True):
"""
Copy the contents of one material completely into another material.
If flag |clean| is set to True, all nodes in |target_material| are
deleted first.

:type source_material: poser.MaterialType
:type target_material: poser.MaterialType
:type clean: bool
:return: None
"""
assert isinstance(source_material, poser.MaterialType)
assert isinstance(target_material, poser.MaterialType)

target_layer_names = [l.Name() for l in target_material.Layers()]

for source_idx, source_layer in enumerate(source_material.Layers()):
if source_layer.Name() not in target_layer_names:
target_material.CreateLayer(source_layer.Name())
target_layer_names = [l.Name() for l in target_material.Layers()]
target_idx = target_layer_names.index(source_layer.Name())
source_tree = source_material.LayerShaderTree(source_idx)
target_tree = target_material.LayerShaderTree(target_idx)
if clean:
for node in target_tree.Nodes():
if not node.Type() in "PoserSurface CyclesSurface":
node.Delete()

copy_shadertree(source_tree, target_tree)

for renderengine in (poser.kRenderEngineCodeFIREFLY,
poser.kRenderEngineCodeSUPERFLY,
poser.kRenderEngineCodePOSER4):
root_node = source_tree.RendererRootNode(renderengine)
if root_node:
try:
# !!!!! Needs some work !!!!
troot = target_tree.NodeByInternalName(root_node.InternalName())
target_tree.SetRendererRootNode(renderengine, troot)
except poser.error:
pass


def copy_material_to_actor(source_material, target_actor, materialname=None):
"""
Copy a complete material into a Poser actor.
If |materialname| is not None, it is used as name for the target material.

:type source_material: poser.MaterialType()
:type target_actor: poser.ActorType()
:type materialname: basestring | str | None
:return: None
"""
assert isinstance(source_material, poser.MaterialType)
assert isinstance(target_actor, poser.ActorType)
source_matname = source_material.Name()
target_matname = materialname or source_matname

if target_matname not in [m.Name() for m in target_actor.Materials()]:
target_actor.Geometry().AddMaterialName(source_material.Name())

target_material = target_actor.Material(target_matname)
copy_material(source_material, target_material)


def copy_materials(source_poserobj, target_poserobj):
"""
Copy materials from one Poser Actor/Figure to another Actor/Figure.

:type source_poserobj: poser.ActorType() | poser.FigureType()
:param target_poserobj: poser.ActorType() | poser.FigureType()
:return: None
"""
if isinstance(source_poserobj, poser.FigureType):
source_poserobj = source_poserobj.RootActor()
if isinstance(target_poserobj, poser.FigureType):
target_poserobj = source_poserobj.RootActor() # type: poser.ActorType

for material in source_poserobj.Materials():
copy_material_to_actor(material, target_poserobj)





adp001 ( ) posted Wed, 07 June 2023 at 6:51 AM

As usual the forum editor messed with my posted code.

And also as usual one may download the source from my server:

http://adp.spdns.org/material_support.py




adp001 ( ) posted Wed, 07 June 2023 at 6:58 AM

Note: In the editor, the source code is displayed completely correctly. It is only messed up in the forum view.




JimX ( ) posted Wed, 07 June 2023 at 8:26 AM

That makes some sense. The nodes I had found probably had to do with some much earlier version of Poser, before shader tress were introduced.

I'll go forward based on your suggestions.

Many thanks!


adp001 ( ) posted Thu, 08 June 2023 at 5:39 AM

JimX posted at 8:26 AM Wed, 7 June 2023 - #4467373

That makes some sense. The nodes I had found probably had to do with some much earlier version of Poser, before shader tress were introduced.

Yes, I remember the old way to do it.

Shaders really do have massive advantages, though. And once you've got it right, you can do it easily and quickly with Python.
Unlike much else in Poser, where there is more chaos than structure, the structure of materials, shader trees and shader nodes is logical and effective - so they are very easy to deal with in Python.

I'll go forward based on your suggestions.

Many thanks!

Good luck!

Don't be shy to ask if you get stuck somewhere. Sometimes a little external hint loosens thick knots :)





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.