Getting unique identifiers for PDF nodes

PDF 3D scene graph

In a 3D PDF, all entities in the 3D model are structured in a node tree representation called a 'scene graph'. The scene graph is visible in Adobe Reader in a dedicated navigation panel called "Model Tree". It can also be accessed programmatically using JavaScript. Thus, the scene graph is the accessible part of the 3D content from the PDF part of the document, and a key point is to understand the relationship between 3D entities and nodes in the scene graph.

Nodes identifier

Scene graph nodes are identified by a unique name, which may differ from the name displayed to the user. The situation is different depending on the format of the 3D stream inside the PDF:

  • U3D content: the internal unique name is the same as the visible name. This is because U3D format already specifies unique node names for the tree.
  • PRC content: the internal unique name is generated by Adobe Reader during PRC reading, it is the visible name suffixed by a unique identifier.

PRC entities

Not all PRC entities are represented in the scene graph. Regarding the model structure, only two kind of entities have corresponding nodes:

  • Product Occurrences
  • Representation Items

Warning: Product Occurrence structure may define instances of a shared sub-product structure (see the documentation on product prototypes). This kind of structure defines a lot of product occurrences that won't lead to a node in the scene graph. It is important to have a good understanding of the prototype architecture to predict the scene graph representation of the structure.

Getting nodes' unique identifiers

HOOPS Publish contains functions to get the unique names defined by Adobe Reader from PRC entities. They are the function A3DPDF3DStreamCreateFromModelFileAsPRC, which takes a pointer to an important helper object A3DRWParamsPrcWriteHelper as its last parameter, and the two functions A3DEntityGetPDFNodeIdFromWrite and A3DRWParamsPrcWriteHelperFree.

First, the model file needs to be created and loaded into a stream via A3DPDF3DStreamCreateFromModelFileAsPRC with the A3DRWParamsPrcWriteHelper object. Then, the model file will be traversed and entities will be interrogated with the data from this helper object. Finally, the helper object will need to be deleted with A3DRWParamsPrcWriteHelperFree.

Here's a step-by-step rundown of the process:

  • First, create the model file.
  • Create a PRC stream from the model file by calling A3DPDF3DStreamCreateFromModelFileAsPRC with a pointer to a A3DRWParamsPrcWriteHelper object as its final argument. The function creates the object and fills the pointer.
  • Store the A3DRWParamsPrcWriteHelper object.
  • Traverse the original model file by recursively parsing the Product Occurrences structure:

    • first traverse the part definition (from the product if it owns one, or traverse the prototypes)
    • then traverse the Product Occurrences' children. During the traversals, use the function A3DEntityGetPDFNodeIdFromWrite with the A3DRWParamsPrcWriteHelper object to get the PDF unique id on entities. Entities which get a unique id are Product Occurrence and Representation Items.
    • Finally, delete the A3DRWParamsPrcWriteHelper object with the A3DRWParamsPrcWriteHelperFree function.

    The steps required to get unique names from PRC entities are illustrated with the sample PublishPRCCube. This sample also implements product occurrence traversal for a use case that includes shared occurrences and prototypes.

    PublishPRCCube example

    The PublishPRCCube example demonstrates using the HOOPS Publish API to create geometry directly rather than load it in from file. You can also use this method if you wish to link HOOPS Publish directly to your application's internal data structures, rather than transferring geometry via file.

    The work to create the cube geometry is done in the file CreatePRCAsmAndGetPDFIds.cpp, whilst PublishPRCCube.cpp creates the PDF file.

    Reviewing the PRC creation code is beyond the scope of this document but the important concept of traversing the model to obtain unique ids for each node needs discussion.

    The function TraverseModelForPDFIds traverses the in-memory PRC assembly structure and retrieves a PDFNodeId from each representation item using A3DEntityGetPDFNodeIdFromWrite. For this example the name of each node is checked and only the PDF Ids for each cube are returned.

    The node ids that has been retrieved can be used in JavaScript to allow buttons to control the visibility of each item: the retrieved value for pcIdNode1 is used in the scene.nodes.getByName function to retrieve the node object itself:

    iRet = A3DPDFPageFieldButtonSetLabel(pPage, "Button1", "Cube1");
    CHECK_RET;
    charjscmd[1024];
    sprintf(jscmd, "c3d = this.getAnnots3D( 0 )[0].context3D; "
    "for (vari = 0; i< c3d.scene.nodes.count; i++) {"
    "var node = c3d.scene.nodes.getByIndex(i); node.visible=false; } ; "
    "var node = c3d.scene.nodes.getByName(\"%s\"); node.visible=true;",
    pcIdNode1);
    iRet = A3DPDFPageFieldSetActionJavaScriptFromString(pPage, "Button1", jscmd);
    CHECK_RET;
    top_level:1 prog_guide:2