Start a new topic
Solved

Is there a script to create a switch under every LOD node, or place a group node on top of every LOD?

Hi all,


I am a 3D artist who has been tasked to process a database of hundreds of flight files for CDB. Most of them contains multiple LOD, switches and groups.


I need to batch-process these files but I am not trained in programming, and we can't afford to hire any programmers so I need your help please.


Could anyone show me a script to place a switch below every LOD node, or another script to place a group node above every LOD node, or preferably a script that does both functions together?


I am on Openflight 4.2 API.


Thank you very much!


Could you please provide some  more information so we know how to structure the script?


  1. Will you be running this script inside of Creator or in a stand-alone environment?
  2. if you're going to be running this script outside of Creator, I presume you will be passing the name of the OpenFlight file you want to process to the script as an argument, correct?
  3. For the switch node below the LOD, what do you want to be done with the current child/children of the LOD? Do you want those children to become children of the new switch or do you want those children to be siblings of the new switch
The meat of the script you want is very simple. Once we get answers to these questions, we'll finiish it up and pass it along.

Hi Steve, thank you for your reply.


1. I will be running the script inside of Creator 4.2

2. This doesn't matter now because I am running it in Creator right?

3. I want those current children of the LOD to become children of the new switch

Here is a script that should do what you want. Let me know if you have any questions. I did not have a chance to test this in Creator 4.2 (that's quite old) but I believe all the script functions I used will be compatible. If not, let me know,

 

class NodeList:
   def __init__(self,nodeCode):
      self.nodes = list()
      self.nodeCode = nodeCode

def ReportError(message):
   # prints twice, comment out whichever you don't want
   # this prints in the script editor log window
   print message
   # this prints to the Creator Status Log
   mgSendMessage(MMSG_ERROR, message)
   
def GatherChildren(node):
   """return a simple list containing the child nodes of 'node'"""
   childList = list()
   child = mgGetChild(node)
   while child:
      childList.append(child)
      child = mgGetNext(child)
   return childList

def InsertNodeAbove(nodes, nodeCode):
   """insert a node of type 'nodeCode' above each node in nodes list"""
   nodeCodeString = ddGetName(nodeCode)
   for node in nodes:
      parent = mgGetParent(node)
      # make sure we can attach a 'nodeCode' node to this nodes parent
      if mgValidAttach(mgGetCode(parent), nodeCode):
         # and that this node can be attached to new 'nodeCode'
         if mgValidAttach(nodeCode, mgGetCode(node)):
            newNode = mgNewRec(nodeCode)
            mgDetach(node)
            mgAppend(parent, newNode)
            mgAttach(newNode, node)
         else:
            ReportError("Error attaching {} to {}".format(mgGetName(node), nodeCodeString))
      else:
         ReportError("Error attaching {} to {}".format(nodeCodeString, mgGetName(parent)))
            
def InsertNodeBelow(nodes, nodeCode):
   """insert a node of type 'nodeCode' below each node in nodes list"""
   nodeCodeString = ddGetName(nodeCode)
   for node in nodes:
      # make sure we can attach a 'nodeCode' node to this node
      if mgValidAttach(mgGetCode(node), nodeCode):
         # grab current childen into a list
         children = GatherChildren(node)
         newNode = mgNewRec(nodeCode)
         mgAttach(node, newNode)
         # now move old children to be children of the new node
         for child in children:
            # move the child only if it can be a child of the new node
            if mgValidAttach(nodeCode, mgGetCode(child)):
               mgDetach(child)
               mgAppend(newNode, child)
            else:
               ReportError("Error moving child {} to {}".format(mgGetName(child), nodeCodeString))
      else:
         ReportError("Error attaching {} to {}".format(nodeCodeString, mgGetName(node)))
            
def PreWalkFunc (db, parent, rec, nodeList):
   if (mgGetCode(rec) == nodeList.nodeCode):
      nodeList.nodes.append(rec)
   return MG_TRUE

def PostWalkFunc (db, parent, rec, nodeList):
   return MG_TRUE

def AddGroupsAboveLods(db, lodList):
   InsertNodeAbove(lodList.nodes, fltGroup)

def AddSwitchesBelowLods(db, lodList):
   InsertNodeBelow(lodList.nodes, fltSwitch)

def GatherLODs(db):
   """walk database, collect and return a list of LOD nodes"""
   lodList = NodeList(fltLod)
   mgWalk (db, PreWalkFunc, PostWalkFunc, lodList, 0)
   return lodList

db = mgGetCurrentDb ()
lodList = GatherLODs(db)
AddGroupsAboveLods(db, lodList)
AddSwitchesBelowLods(db, lodList)



 


1 person likes this

Hi Steve,


The script is working like a charm on the first few files I've tested. I will batch it on 100 files on Monday and let you know how it goes.


Thank you so much!

Hi, after testing the script on 100 files, many are working well but I realized that my lightpoints are being deleted as they fall under LOD nodes


Is it possible to modify the script to exclude nodes of a specific name? I tried using: if mgGetRecByName (db, "headlights") but that broke the script instead.


Right now, I need to exclude all nodes (and their children) that has the name "headlights" or "blackout" or "bad". "headlights" and "blackout" are switches while "bad" is a group node

Hmmm... I'm puzzled why any nodes are getting deleted. The script only adds and moves nodes around, it does not delete nodes. Any child of the LOD before the script runs should be "moved" to be under the switch. But the script only does this if the child of the LOD "can be" attached to a switch. If it cannot be moved from the LOD to the switch, it should simply remain on the LOD. Perhaps the check for mgValideAttach is returning a FALSE positive in which case the child would get detached but then the mgAppend would fail. This would look like the child got "deleted" but not purposefully Do you see any messages in the Status Log to suggest this might be happening? I think this should be fixed instead of coming up with any sort of "exclude this node" mechanism which should NOT be needed if the script was working correctly.

I tried this script with a light point child of the LOD and see the light point getting moved properly from the LOD to the switch. Granted I am not using 4.2 but I can't imagine it's too different for you. Can you clarify what you're seeing, perhaps attach a file that is showing this? In the meantime, I can make some changes in the script to try to detect and correct this. But I'd really like to undertand what is really happening before making guesses.

I just installed Creator 4.2 and see what you're talking about. As I suspected mgValidAttach is returning a FALSE positive which leads the script to believe that attaching Light Points to Switches is OK, which in 4.2 it is not. Also in 4.2, attaching light points directly to LODs is forbidden as well. So when the script tries to move the light point from the LOD to the switch (even though mgValidAttach says its ok to do so) it fails. This results in the Light Point being detached (not deleted really). So if you're inside of Creator when you run the script, you'll see the Light Points detached in the hierarchy view, you can grab them and reattach them. If you run this in batch mode, howver, the detached nodes will be lost when the file is written.


So you're in a bit of a pickle. For one, you have a file that in Creator 4.2 is illegal in that you have light points attached directly to LODs. Not sure how you got such a file. I don't think you created it in CR 4.2, as it would not have allowed you. Either way you have a couple of options:

1) Go through all your files and insert object nodes between the light point and the LOD. Object nodes can be attached to both LOD and Switch nodes. In the long run this might be your best option unless for some reason your runtime would be negative impacted.

2) Use the updated script here. In this script I added my own version of mgValidAttach (called myValidAttach) which kinda fixes the problem. If you find other nodes that "go missing" you can add more cases to myValidAttach to fix them too.

 

class NodeList:
   def __init__(self,nodeCode):
      self.nodes = list()
      self.nodeCode = nodeCode

def myValidAttach(parentCode, childCode):
   if parentCode == fltLod and childCode == fltLightPoint:
      return MG_FALSE
   if parentCode == fltSwitch and childCode == fltLightPoint:
      return MG_FALSE
   return mgValidAttach(parentCode, childCode)

def ReportError(message):
   # prints twice, comment out whichever you don't want
   # this prints in the script editor log window
   print message
   # this prints to the Creator Status Log
   mgSendMessage(MMSG_ERROR, message)
   
def GatherChildren(node):
   """return a simple list containing the child nodes of 'node'"""
   childList = list()
   child = mgGetChild(node)
   while child:
      childList.append(child)
      child = mgGetNext(child)
   return childList

def InsertNodeAbove(nodes, nodeCode):
   """insert a node of type 'nodeCode' above each node in nodes list"""
   nodeCodeString = ddGetName(nodeCode)
   for node in nodes:
      parent = mgGetParent(node)
      # make sure we can attach a 'nodeCode' node to this nodes parent
      if myValidAttach(mgGetCode(parent), nodeCode):
         # and that this node can be attached to new 'nodeCode'
         if myValidAttach(nodeCode, mgGetCode(node)):
            newNode = mgNewRec(nodeCode)
            mgDetach(node)
            mgAppend(parent, newNode)
            mgAttach(newNode, node)
         else:
            ReportError("Error attaching " + mgGetName(node) + " to " + nodeCodeString)
      else:
         ReportError("Error attaching " + nodeCodeString + " to " + mgGetName(parent))
            
def InsertNodeBelow(nodes, nodeCode):
   """insert a node of type 'nodeCode' below each node in nodes list"""
   nodeCodeString = ddGetName(nodeCode)
   for node in nodes:
      # make sure we can attach a 'nodeCode' node to this node
      if myValidAttach(mgGetCode(node), nodeCode):
         # grab current childen into a list
         children = GatherChildren(node)
         newNode = mgNewRec(nodeCode)
         mgAttach(node, newNode)
         # now move old children to be children of the new node
         for child in children:
            # move the child only if it can be a child of the new node
            if myValidAttach(nodeCode, mgGetCode(child)):
               mgDetach(child)
               mgAppend(newNode, child)
            else:
               ReportError("Error moving child " + mgGetName(child) + " to " + nodeCodeString)
      else:
         ReportError("Error attaching " + nodeCodeString + " to " + mgGetName(node))
            
def PreWalkFunc (db, parent, rec, nodeList):
   if (mgGetCode(rec) == nodeList.nodeCode):
      nodeList.nodes.append(rec)
   return MG_TRUE

def PostWalkFunc (db, parent, rec, nodeList):
   return MG_TRUE

def AddGroupsAboveLods(db, lodList):
   InsertNodeAbove(lodList.nodes, fltGroup)

def AddSwitchesBelowLods(db, lodList):
   InsertNodeBelow(lodList.nodes, fltSwitch)

def GatherLODs(db):
   """walk database, collect and return a list of LOD nodes"""
   lodList = NodeList(fltLod)
   mgWalk (db, PreWalkFunc, PostWalkFunc, lodList, 0)
   return lodList

db = mgGetCurrentDb ()
lodList = GatherLODs(db)
AddGroupsAboveLods(db, lodList)
AddSwitchesBelowLods(db, lodList)

 


1 person likes this

Hi Steve, firstly my apologies for my late replies. I am working at the GMT time zone so I presume we are 12 hours apart.


The light points are no longer being affected, thanks! You're right, the light points must have been made in an older version.


I am trying to add more exceptions to myValidAttach. I have a group node named 'bad' in which I want it and its children to be excluded from the entire script. This is how I am trying to code it:


badParent = mgGetRecByName (db, "bad")

 

def myValidAttach(parentCode, childCode):

 if parentCode == fltLod and childCode == fltLightPoint:

 return MG_FALSE

 if parentCode == fltSwitch and childCode == fltLightPoint:

 return MG_FALSE

 if parentCode == badParent and childCode == fltPolygon:

 return MG_FALSE


It doesn't give an error but it doesn't exclude the group node 'bad' either. What am I doing wrong?

When you say you want certain groups "excluded from the entire script" can you be more specific? I assume you mean you just want to "freeze" that node and all its children. In other words "don't process any LODs you find under that group". If that is the case, you can try the update script below.

 

As to using myValidAttach to achieve this...

You can't do it here, by the time myValidAttach is called, it's way too late.

Also the parameters to myValidAttach are "codes" (or levels) like fltGroup, fltLod, fltSwitch, fltLightPoint, etc 

These "codes" are returned by mgGetCode. So for a node that is a "group", mgGetCode(node) would return fltGroup. Again mgGetCode(node) returns the "level" of the node. 

mgGetName(node) returns the "name" of the node. You are comparing a nodes "code" to its "name". That won't work.

In your code above badParent is a node, parentCode is a "level". They will never match.


 

class WalkController:
   def __init__(self):
      self.excludeNames = list()
      self._walkActiveStack = list()
   def Exclude(self,name):
      # add 'name' to list of node names we exclude from the script
      self.excludeNames.append(name)
   def IsExcluded(self,node):
      # returns True if this node is excluded
      name = mgGetName(node)
      return name in self.excludeNames
   def PreNode(self,node):
      # tell the controller you are starting to walk 'node'
      if (self.IsExcluded(node)):
         # if this node is excluded, push it on the stack
         self._walkActiveStack.append(node)  
   def PostNode(self,node):
      # tell the controller you are done walking 'node'
      # if this node is currectly being excluded pop its name off the stack
      length = len(self._walkActiveStack)
      if (length > 0):
         if (self._walkActiveStack[length-1] == node):
            self._walkActiveStack.pop()
   def IsWalkActive(self):
      # if there are any nodes in the stack, that means we are excluding it
      # currently
      return len(self._walkActiveStack) == 0

class NodeList:
   def __init__(self,nodeCode,walkController):
      self.nodes = list()
      self.nodeCode = nodeCode
      self.walkController = walkController

def myValidAttach(parentCode, childCode):
   if parentCode == fltLod and childCode == fltLightPoint:
      return MG_FALSE
   if parentCode == fltSwitch and childCode == fltLightPoint:
      return MG_FALSE
   return mgValidAttach(parentCode, childCode)

def ReportError(message):
   # prints twice, comment out whichever you don't want
   # this prints in the script editor log window
   print (message)
   # this prints to the Creator Status Log
   mgSendMessage(MMSG_ERROR, message)
   
def GatherChildren(node):
   """return a simple list containing the child nodes of 'node'"""
   childList = list()
   child = mgGetChild(node)
   while child:
      childList.append(child)
      child = mgGetNext(child)
   return childList

def InsertNodeAbove(nodes, nodeCode):
   """insert a node of type 'nodeCode' above each node in nodes list"""
   nodeCodeString = ddGetName(nodeCode)
   for node in nodes:
      parent = mgGetParent(node)
      # make sure we can attach a 'nodeCode' node to this nodes parent
      if myValidAttach(mgGetCode(parent), nodeCode):
         # and that this node can be attached to new 'nodeCode'
         if myValidAttach(nodeCode, mgGetCode(node)):
            newNode = mgNewRec(nodeCode)
            mgDetach(node)
            mgAppend(parent, newNode)
            mgAttach(newNode, node)
         else:
            ReportError("Error attaching " + mgGetName(node) + " to " + nodeCodeString)
      else:
         ReportError("Error attaching " + nodeCodeString + " to " + mgGetName(parent))
            
def InsertNodeBelow(nodes, nodeCode):
   """insert a node of type 'nodeCode' below each node in nodes list"""
   nodeCodeString = ddGetName(nodeCode)
   for node in nodes:
      # make sure we can attach a 'nodeCode' node to this node
      if myValidAttach(mgGetCode(node), nodeCode):
         # grab current childen into a list
         children = GatherChildren(node)
         newNode = mgNewRec(nodeCode)
         mgAttach(node, newNode)
         # now move old children to be children of the new node
         for child in children:
            # move the child only if it can be a child of the new node
            if myValidAttach(nodeCode, mgGetCode(child)):
               mgDetach(child)
               mgAppend(newNode, child)
            else:
               ReportError("Error moving child " + mgGetName(child) + " to " + nodeCodeString)
      else:
         ReportError("Error attaching " + nodeCodeString + " to " + mgGetName(node))
            
def PreWalkFunc (db, parent, rec, nodeList):
   nodeList.walkController.PreNode(rec)
   if (nodeList.walkController.IsWalkActive()):
      if (mgGetCode(rec) == nodeList.nodeCode):
         nodeList.nodes.append(rec)
   return MG_TRUE

def PostWalkFunc (db, parent, rec, nodeList):
   nodeList.walkController.PostNode(rec)
   return MG_TRUE

def AddGroupsAboveLods(db, lodList):
   InsertNodeAbove(lodList.nodes, fltGroup)

def AddSwitchesBelowLods(db, lodList):
   InsertNodeBelow(lodList.nodes, fltSwitch)

def GatherLODs(db, walkController):
   """walk database, collect and return a list of LOD nodes"""
   lodList = NodeList(fltLod, walkController)
   mgWalk (db, PreWalkFunc, PostWalkFunc, lodList, 0)
   return lodList

db = mgGetCurrentDb ()
# set up a list of names that the GatherLODs function will exclude from the script
excludeNames = WalkController()
#
# Add the names of the nodes you want to exclude here
#
excludeNames.Exclude("bad")      # add as many names as you want
excludeNames.Exclude("bad2")     # etc
# pass the list of excluded names to GatherLODs
lodList = GatherLODs(db, excludeNames)
AddGroupsAboveLods(db, lodList)
AddSwitchesBelowLods(db, lodList)

 

Hi Steve, it's working perfectly! And now I can even add my own exceptions. I am batching it on the rest of the few hundred models so far and they are turning out well.


You've been a great help to me and I can't stress how thankful I am!

That's great Wang, so glad you're having success. Any more questions, just let us know.

Hi Steve

i try understand your script for many times, and learn the 'class' on youtube for 1 week,

still getting lost in (nodes,node,list),although all function very well. but it is not a good way we always ask other people to  write code for us....


i try disable some functions by #, trying to see each ' def ' does, but all func  wont work.....


could u provide a simplified version, such as only add a switch under every LODs, still  using your 'class'?

so that next time if we need extra stuff I can modify it,at least


maybe too many requirement since the script already works..

or maybe i better open another topic?


Have a good day :)

  

Yingbo


Login to post a comment