I have a python node that work fine outside of a custom node. When I wrap the python node in a custom node, it just returns “Function” and doesn’t do anything.
What makes this script incompatible with wrapping it in a custom node? BTW…this is my first custom node.
import clr
# Add Assemblies for AutoCAD and Civil 3D APIs
clr.AddReference('acmgd')
clr.AddReference('acdbmgd')
clr.AddReference('accoremgd')
clr.AddReference('AecBaseMgd')
clr.AddReference('AecPropDataMgd')
clr.AddReference('AeccDbMgd')
clr.AddReference('AeccPressurePipesMgd')
clr.AddReference('acdbmgdbrep')
clr.AddReference('System.Windows.Forms')
clr.AddReference('Civil3DNodes')
clr.AddReference('AutoCADNodes')
# Add standard Python references
import sys
sys.path.append('C:\Program Files (x86)\IronPython 2.7\Lib')
import os
import math
# Add references to manage arrays, collections and interact with the user
from System import *
from System.IO import *
from System.Collections.Specialized import *
from System.Windows.Forms import MessageBox
# Create an alias to the Autodesk.AutoCAD.ApplicationServices.Application class
import Autodesk.AutoCAD.ApplicationServices.Application as acapp
# Import references from AutoCAD
from Autodesk.AutoCAD.Runtime import *
from Autodesk.AutoCAD.ApplicationServices import *
from Autodesk.AutoCAD.EditorInput import *
from Autodesk.AutoCAD.DatabaseServices import *
from Autodesk.AutoCAD.Geometry import *
# Import references for Civil 3D
from Autodesk.Civil.ApplicationServices import *
from Autodesk.Civil.DatabaseServices import *
# Import references for Dynamo for Civil 3D
from Autodesk.Civil.DynamoNodes import Surface as DynSurface
from Autodesk.Civil.DynamoNodes.Selection import SurfaceByName
from Autodesk.AutoCAD.DynamoNodes import Document as DynDocument
adoc = acapp.DocumentManager.MdiActiveDocument
ed = adoc.Editor
civdoc = CivilApplication.ActiveDocument
def add_featurelines_links(fName, lName):
"""
This method adds feature lines and links to an existing corridor surface.
:param Fnames: List of corridor feature lines to add
:param Lnames: List of corridor links to add
:return: nothing
"""
global adoc
global ed
global civdoc
output = []
# Get the active document in the AutoCAD session:
with adoc.LockDocument():
with adoc.Database as db:
with db.TransactionManager.StartTransaction() as t:
# Get the corridor and corridor surface
corridorId = civdoc.CorridorCollection[corridorName]
corridor = t.GetObject(corridorId, OpenMode.ForWrite)
try:
corridorSurface = corridor.CorridorSurfaces[corridorSurfaceName]
# Add feature lines and links
for n in fName:
corridorSurface.AddFeatureLineCode(n)
for n in lName:
corridorSurface.AddLinkCode(n, True)
if rebuild:
corridor.Rebuild()
t.Commit()
except Exception() as ex:
MessageBox.Show(ex.message)
output.append(SurfaceByName(corridorSurfaceName, DynDocument.Current))
return output
corridorName = IN[0]
corridorSurfaceName = IN[1]
fNames = IN[2]
lNames = IN[3]
rebuild = IN[4]
if not isinstance(fNames, list):
fNames = [fNames]
if not isinstance(lNames, list):
lNames = [lNames]
OUT = add_featurelines_links(fNames, lNames)
Once I was playing with a custom node, I found out the code did not work as it did in the graph itself. It seems that you need to accept lists in a custom node.
I’m not sure what the issue is now. I added the list brackets to the inputs as you suggested. Still doesn’t work. It doesn’t output “Function” anymore, but now it outputs “null”. I wondering if has something to do with levels and/or lacing, but I have no idea how to address that for a python script wrapped in a custom node.
Check out my posted files for my latest attempt. I modified the python script some because it needs to be able to process multiple corridor surfaces. I’m still missing something though.
I’ll have a look later. It’s an interesting script so I am curious. No Python experience but I am a C# programmer so I understand what it does. Now dive into the mysteries of Custom Nodes and lacing.
@keith.sowinski Thanks for dropping the files. I was getting fatal error while opening your (dyn,dyf’s) files, so i recreated custom node using your python. Looks good to me:
Thank you for looking at this. Putting []..[] in the inputs worked. I am curious why you get a fatal error on open, but I don’t. I have follow-up questions.
Are the inputs always supposed to include []..[], or only in certain situations? I tried just [], but that did not work. If I only expect a single item as an input (corridorName in this case), do I leave off the []..[]?
This only seems to work if I have multiple surfaces with multiple lists of feature lines and links. If I only have a single surface, it does not work.
If the list of feature lines and/or links is empty, it does not work.
Here you find more information about the brackets:
I thought you only add them where you expect a list (featurelines and links) but it seems not to work very well. It’s one of the mysteries I don’t understand yet, mainly because it works completely different if you put them in a Custom Node instead of directly in the graph.
I found that it will work for a single surface if I set the featurelines and links inputs to @L3. I would prefer to have the output list flattened, but haven’t figured that out yet. I’m sure I have something structurally incorrect, but it is functioning.
But if the Custom Node runs multiple times because of the replication, it will stil result in a list. You can flatten the output in your graph as well, then it is more flattened.
I’m not convinced the @L3 option is the solution. Will it work in any situation? One Corridor? Multiple? Or multiple featureline sets?
I’m not sure this has anything to do with the customnode… I think it’s likely you should not call the civil nodes from code - they are likely written like the Dynamo Revit constructor nodes and use element binding which will do strange things when invoked from code…
thats just a guess though… try replacing your python with something simpler as a test.
Thank you for the suggestion. I tried removing the SurfaceByName call, but the results were the same.
The issue is that the feature line (and link) list structure is different when processing a single surface vs processing multiple surfaces. Trying to accommodate both scenarios. When there is a single surface, everything is as the same level. When there are multiple surfaces, there are sub lists for each surfaces’ feature lines.
So far, the solution is to set the feature line and link input levels to @L3 to get this structure for a single surface scenario. Alternatively, I can do it in advance as shown with this GetItemAtIndex node.
Maybe that’s just the way it is and I can communicate that to my users. If there is some way to handle this within the python script, that would be ideal.
Thank you for your help. I think I have something that is working. Here is my final python script. I ended up putting in a check for the corridor surface name input list length. If the length is 1, then I sublist the feature line and link lists. I also followed @Michael_Kirschner2 advice and pulled out the civil node call. I don’t think that was the issue in this case, but I did it as a precaution anyway. I’m just returning the corridor surface names instead (pass through). I also made sure to include []..[] in all of my inputs.
import clr
# Add Assemblies for AutoCAD and Civil 3D APIs
clr.AddReference('acmgd')
clr.AddReference('acdbmgd')
clr.AddReference('accoremgd')
clr.AddReference('AecBaseMgd')
clr.AddReference('AecPropDataMgd')
clr.AddReference('AeccDbMgd')
clr.AddReference('AeccPressurePipesMgd')
clr.AddReference('acdbmgdbrep')
clr.AddReference('System.Windows.Forms')
# Add standard Python references
import sys
sys.path.append('C:\Program Files (x86)\IronPython 2.7\Lib')
import os
import math
# Add references to manage arrays, collections and interact with the user
from System import *
from System.IO import *
from System.Collections.Specialized import *
from System.Windows.Forms import MessageBox
# Create an alias to the Autodesk.AutoCAD.ApplicationServices.Application class
import Autodesk.AutoCAD.ApplicationServices.Application as acapp
# Import references from AutoCAD
from Autodesk.AutoCAD.Runtime import *
from Autodesk.AutoCAD.ApplicationServices import *
from Autodesk.AutoCAD.EditorInput import *
from Autodesk.AutoCAD.DatabaseServices import *
from Autodesk.AutoCAD.Geometry import *
# Import references for Civil 3D
from Autodesk.Civil.ApplicationServices import *
from Autodesk.Civil.DatabaseServices import *
adoc = acapp.DocumentManager.MdiActiveDocument
ed = adoc.Editor
civdoc = CivilApplication.ActiveDocument
def add_featurelines_links(name, fName, lName):
"""
This method adds feature lines and links to an existing corridor surface.
:param Fnames: List of corridor feature lines to add
:param Lnames: List of corridor links to add
:return: corridor surface name
"""
global adoc
global ed
global civdoc
# Get the active document in the AutoCAD session:
with adoc.LockDocument():
with adoc.Database as db:
with db.TransactionManager.StartTransaction() as t:
# Get the corridor and corridor surface
corridorId = civdoc.CorridorCollection[corridorName]
corridor = t.GetObject(corridorId, OpenMode.ForWrite)
if len(name) == 1:
fName = [fName]
lName = [lName]
try:
for n, f, l in zip(name, fName, lName):
corridorSurface = corridor.CorridorSurfaces[n]
# Add feature lines and links
for i in f:
corridorSurface.AddFeatureLineCode(i)
for j in l:
corridorSurface.AddLinkCode(j, True)
if rebuild:
corridor.Rebuild()
t.Commit()
except Exception() as ex:
MessageBox.Show(ex.message)
return corridorSurfaceName
corridorName = IN[0]
corridorSurfaceName = IN[1]
fNames = IN[2]
lNames = IN[3]
rebuild = IN[4]
if not isinstance(corridorSurfaceName, list):
corridorSurfaceName = [corridorSurfaceName]
if not isinstance(fNames, list):
fNames = [fNames]
if not isinstance(lNames, list):
lNames = [lNames]
OUT = add_featurelines_links(corridorSurfaceName, fNames, lNames)