Does "copyTo" copy child-nodepaths?

Hi to all,

I have two nodepaths, the first is the parent of the second. They are within a certain display region that is parented to a different scene-graph. I know that

copyTo()

copies the geometry of the source nodepath to the destination nodepath (right?). So if I create a copy of the first-nodepath and then reparent that copy to the main scene-graph like this:

dum=render.attachNewNode("copied")
nodePath1.copyTo(dum)

It seems to copy both the geometry of the parent and of the child to the newly created nodepath. There’s no problem with that. However, when I do this:

print dum.getChildren()

The second nodepath is not listed as the child of the newly created nodepath [even though visibly, this new nodepath has the geometry of both the first and the second original nodepaths]. So it just copied the geometry of the parent and the child, but it didn’t create a new nodepath for the child it copied? Is there a way that I can copy the geometry of both, and then also maintain the new copy as consisting of the first nodepath as the parent, and the second nodepath the child? Kinda like this:

#First_node_path->parentOf->Second_node_path
dum=render.attachNewNode("copied")
First_node_path.copyTo(dum)
#then dum should have a copy of the Second_node_path as its child:
#print dum.getChildren()->Second_node_path_copy

I could just unparent the second from the first, create 2 separate copies, then reparent them at the destination, but is there a better way to do this?

Thanks.

When you copy the first NodePath to a newly created NodePath (“dum”), it becomes a child of dum.
You probably just want to copy the first NodePath directly to render instead.

Yes, that would work too, but the issue would persist nonetheless: I have two original nodes: n1 and n2. n1 is the parent of n2. I copy n1 to a copy c_n1. When I query c_n1 for its children, a copy of n2, c_n2 is not listed. However, visibly, my copy n1 has the geometries of both n1 and n2. Like if they were 2 cubes, and I copied n1, then we’d have four cubes on the screen instead of three, but when querying the duplicate of n1 for its children, expecting to see a copy of n2 listed there, it isn’t. So while duplicating the parent duplicates both its geometry and that of its children, it does not duplicate the nodepaths of its children. It’s as if it combines all of them into just one nodepath, whose geometry would be the geometry of all the originals, both parent and children. So the question is, can I access the copy’s visible geometry that belongs to n2 as a nodepath? How would I do that?

Example:

cube1=loader.loadModel("cube1.egg")
cube2=loader.loadModel("cube2.egg")
#reparent:
render_2=NodePath('render2')
cube1.reparentTo(render_2)#<- reparent to a different scene graph
cube2.reparentTo(cube1)
#positioning etc:
cube2.setPos(10,10,10)
cube2.setH(60)
#now the duplicating of the geometry:
cube1_copy=cube1.copyTo(render)#this will show, in the main scene graph, four cubes...
#ask for it's children:
print "KIDS:  ",cube1_copy.getChildren()
#the above output will not list "cube2" as a copy or anything like that as a child,
#but cube1_copy's visible geometry has it as part of its geometry, try it and see...
#how do I get a hold of the nodepath of "cube2" as a copy? 

Well I don’t have the cube models so I substituted them with smiley.egg and frowney.egg, but that shouldn’t make much difference.
The output I’m getting is the following:

KIDS:   render/smiley.egg/-GeomNode
render/smiley.egg/frowney.egg

As I expected, frowney.egg - which is parented to smiley.egg - is listed (in the second line, so it is the second child). The first child is the GeomNode of smiley.egg itself that is parented under a ModelRoot NodePath that Panda automatically creates for any model it loads.
You can get a better understanding of that hierarchy if you call cube1_copy.ls(), which yields the following output:

ModelRoot smiley.egg
  GeomNode  (1 geoms: S:(TextureAttrib))
  ModelRoot frowney.egg T:(pos 10 10 10 hpr 60 0 0)
    GeomNode  (1 geoms: S:(TextureAttrib))

See this topic for ways to get rid of those pesky ModelRoots.

Thanks, but will this be the case with procedural geometry too? Because for, me, whereas it does list the second nodepath as its child in this case with loaded geometry, in my app where procedural geometry is created, it does not list it as a child. It outputs this:

KIDS:  render1/104%figure.81.0.0_list_figure/prototype81
render1/104%figure.81.0.0_list_figure/81.0.0.ecollider
render1/104%figure.81.0.0_list_figure/104%figure.81.0.1_inv
render1/104%figure.81.0.0_list_figure/81.0.0.e$collider

Procedural geometry doesn’t have ModelRoots right? Anyways, the above is missing the second nodepath called “104%figure.81.0.1_list_figure” as a child. [I already implemented a workaround to this, where I just unparent, copy and reparent at the destination and when I do the .getChildren() query, it does list it as a child then…].

Here’s my own attempt at procedurally creating cubes (without collision geometry or whatever attached to them), parenting them together and then copying them:

from panda3d.core import *
from direct.showbase.ShowBase import ShowBase



class Cube(object):

  def __init__(self, parent_np, name):

    vertex_format = GeomVertexFormat.getV3n3cpt2()
    vertex_data = GeomVertexData("cube_data", vertex_format, Geom.UHStatic)
    tris_prim = GeomTriangles(Geom.UHStatic)

    pos_writer = GeomVertexWriter(vertex_data, "vertex")
    normal_writer = GeomVertexWriter(vertex_data, "normal")

    vertex_count = 0

    for direction in (-1, 1):

      for i in range(3):

        normal = VBase3()
        normal[i] = direction

        for a, b in ( (-1., -1.), (-1., 1.), (1., 1.), (1., -1.) ):

          pos = VBase3()
          pos[i] = direction
          pos[(i - direction) % 3] = a
          pos[(i - direction * 2) % 3] = b

          pos_writer.addData3f(pos)
          normal_writer.addData3f(normal)

        vertex_count += 4

        tris_prim.addVertices(vertex_count - 4, vertex_count - 3, vertex_count - 2)
        tris_prim.addVertices(vertex_count - 2, vertex_count - 1, vertex_count - 4)

    cube_geom = Geom(vertex_data)
    cube_geom.addPrimitive(tris_prim)
    cube_node = GeomNode(name)
    cube_node.addGeom(cube_geom)

    self._origin = parent_np.attachNewNode(cube_node)


  def getOrigin(self):

    return self._origin



class MyApp(ShowBase):

  def __init__(self):

    ShowBase.__init__(self)

    # set up a light source
    p_light = PointLight("point_light")
    p_light.setColor(VBase4(1., 1., 1., 1.))
    self.light = self.camera.attachNewNode(p_light)
    self.light.setPos(5., -10., 7.)
    self.render.setLight(self.light)

    # create cube1
    cube1 = Cube(self.render, "cube1").getOrigin()
    cube1.setColor(VBase4(1., 0., 0., 1.))

    # create cube2
    cube2 = Cube(self.render, "cube2").getOrigin()
    cube2.setColor(VBase4(0., 1., 0., 1.))

    render_2 = NodePath("render_2")
    #reparent:
    cube1.reparentTo(render_2)#<- reparent to a different scene graph
    cube2.reparentTo(cube1)
    #positioning etc:
    cube2.setPos(10,10,10)
    cube2.setH(60)

    #ask for the original parent's children:
    print "Original KIDS:  ", cube1.getChildren()
    ################################ output ##################################
    # KIDS:   render_2/cube1/cube2
    ##########################################################################
    # list the original cube hierarchy:
    print "Original cube hierarchy:\n"
    cube1.ls()
    ################################ output ##################################
    # GeomNode cube1 (1 geoms) S:(ColorAttrib)
    #   GeomNode cube2 (1 geoms) T:(pos 10 10 10 hpr 60 0 0) S:(ColorAttrib)
    ##########################################################################

    #now the duplicating of the geometry:
    cube1_copy=cube1.copyTo(render)
    #ask for it's children:
    print "\nCopied KIDS:  ", cube1_copy.getChildren()
    ################################ output ##################################
    # KIDS:   render/cube1/cube2
    ##########################################################################
    # list the copied cube hierarchy:
    print "Copied cube hierarchy:\n"
    cube1_copy.ls()
    ################################ output ##################################
    # GeomNode cube1 (1 geoms) S:(ColorAttrib)
    #   GeomNode cube2 (1 geoms) T:(pos 10 10 10 hpr 60 0 0) S:(ColorAttrib)
    ##########################################################################
    
    self.run()




MyApp()

The output clearly lists cube2 as a child of cube1, so I have no idea why it isn’t working for you.

Right, it doesn’t. What is the output when you call ls() on the parent NodePath? It might shed some light on how everything is parented together.
Also, if you call getChildren() and ls() on the original parent (see my code), do you get the same results? If so (which is what I suspect, since copyTo() is supposed to preserve the hierarchy), then it has nothing to do with copyTo().

Here is the output:

KIDS: PandaNode 104%figure.81.0.0_list_figure S:(ColorAttrib)
  GeomNode prototype81 (1 geoms) T:(scale 0.1) S:(ColorAttrib ColorScaleAttrib C
ullFaceAttrib TexMatrixAttrib TextureAttrib TransparencyAttrib)
  CollisionNode 81.0.0.ecollider (54 solids) T:(scale 0.1) (hidden)
  PandaNode 104%figure.81.0.1_inv T:(pos -3.47386 0.801076 0) S:(ColorAttrib)
    GeomNode prototype81 (1 geoms) T:(scale 0.1) S:(ColorAttrib ColorScaleAttrib
 CullFaceAttrib TexMatrixAttrib TextureAttrib TransparencyAttrib)
    CollisionNode 81.0.1.ecollider (54 solids) T:(scale 0.1) (hidden)
  CollisionNode 81.0.0.e$collider (1 solids) (hidden)
 None

As you can see, it’s still not listed as a child. But as I said I found a workaround and it works great: destroy the hierarchy, copyTo(), recreate hierarchy at the destination. Calling for copy_n1.getChildren() properly yields the expected hierarchy when I do it like that:

KIDDIES:  render1/104%figure.81.0.0_list_figure/prototype81
render1/104%figure.81.0.0_list_figure/81.0.0.ecollider
render1/104%figure.81.0.0_list_figure/81.0.0.e$collider
render1/104%figure.81.0.0_list_figure/104%figure.81.0.1_list_figure

As you can see, the last line shows that the second nodepath is a child of the first, so all is well. You did answer my question though, “copyTo()” is supposed to maintain the hierarchy, so thank you. The funny thing is, I hope to end up using files, so that I make the procedural geometry, store it as files (actor files) and then store their corresponding procedural animations as animation files too all within the app before letting the player use them; loading files would be easier, because for instance, “cube 1” would be the upper arm, “cube 2” the lower arm, so moving “cube 1” moves “cube 2”, hence the parent-child relationships; but defining this in a file would mean creating joints, and then animations and afterwards, loading it into the game would be far much easier as an actor with its corresponding animation file. I attempted it a few months ago, but there were some problems so I decided to stick with this for now, at least until I get a full working demo in a few weeks, then maybe someone here more competent with the structure of the “.egg file” will be willing to help me generate the files once they see what this project is all about [either that, or I’ll have enough time to create the files properly myself]. I appreciate the help, Epihaius [can’t believe you even created procedural geometry to try and recreate the problem, thanks!!].

Hey, glad to help, especially when I see a problem involving procedural geometry my interest is piqued, since I deal with that a lot in my own project.

Yeah I saw your posts about those issues, but I don’t yet know much about animation in .egg files either. Dealing with the .egg structure in general is already not that trivial, so I could use more insight too. (I’m using the EggData interface to process .egg files, by the way.)

Well, back on topic :stuck_out_tongue: .

If that’s the result of ls() on the original parent, it really should mean that - at least at the time you call it or getChildren() (and likely also copyTo) - the NodePath is simply not parented into the hierarchy at all. While I don’t doubt that you are parenting it at some point, it seems like the parenting happens somehow after the copying. Perhaps you’re using a task for this, and the task runs later in the frame than you expect, or something of the sort.

While I understand that you’re happy with the current workaround, I do hope you can figure this one out.

Thank you, for pointing out the speculation regarding the task, after examining a “spinning task” that rotates inventory items, I found out that the problem was that I was changing the names of some copied nodes and then ignoring those of others, so nodepath.ls() was showing the proper hierarchy, but because some names were changed and others ignored, to me it looked like the hierarchy was warped. So copyTo() works and copies the child nodepaths too, preserving the hierarchy.