Hi everyone,
I am facing an issue with a Python script. I have to admit that I am not very knowledgeable in Python.
What I want to accomplish is this: I currently have a Dynamo script that modifies the attributes of the dynamic block in the paper space of a Civil 3D project, taking data from a .csv file. Now, I would like to improve this script by applying these changes to multiple .dwg files located in a folder.
The inputs to the Python script would be the directory path of the folder, a string with the block name, and a list of attributes along with their values.
I came across a thread in the forum that addresses this topic, and I tried to modify it, but I was unable to make it work.
In the image below, you can see the Dynamo script and the code in the Python script.
Hi @hosneyalaa, thank you for your response. Here are the files. The dwgs are basically the same. However, the names of the blocks and the csv file have changed from what you have in the Dynamo file, so you will need to update them. The forum don’t let me upload a csv file, so I sent you a xls
Thank you very much!
I attempted to modify the script by including a separate input for attribute tag and value. There seems to be an error in the first function, but I cannot locate it. I have attached the dyn file.
import clr
import os
import re
# Add Assemblies for AutoCAD APIs
clr.AddReference('acmgd')
clr.AddReference('acdbmgd')
clr.AddReference('accoremgd')
# 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 Autodesk.AutoCAD.ApplicationServices.Application as acapp
import Autodesk.AutoCAD.ApplicationServices.DocumentCollectionExtension
#------------------------
DWGPaths = IN[0]
oldBlockReferenceName = IN[1]
newBlockReferenceName = IN[2]
#Real Code
for DWGPath in DWGPaths:
adoc = DocumentCollectionExtension.Open(acapp.DocumentManager, DWGPath, False)
with adoc.LockDocument():
with adoc.Database as db:
with db.TransactionManager.StartTransaction() as t:
###get ID for block reference replacement
blockTable = t.GetObject(db.BlockTableId, OpenMode.ForWrite)
newBlockID = [block for block in blockTable if block.Name == newBlockReferenceName][0]
###
layoutObjectIDs = []
layoutBlockTableIDs = []
layoutDict = t.GetObject(db.LayoutDictionaryId, OpenMode.ForWrite)
for layout in layoutDict:
if "Model" not in layout.Key:
layoutObjectIDs.append(layout.Value)
for layoutObjectID in layoutObjectIDs:
layout = t.GetObject(layoutObjectID, OpenMode.ForWrite)
layoutBlockTableID = layout.BlockTableRecordId
layoutBlockTableIDs.append(layoutBlockTableID)
for layoutBlockTableID in layoutBlockTableIDs:
layoutBTR = t.GetObject(layoutBlockTableID, OpenMode.ForWrite)
blockReferenceIDs = [blockReferenceID for blockReferenceID in layoutBTR]
blockReferences = []
blockReferencePositions = []
for blockReferenceID in blockReferenceIDs:
object = t.GetObject(blockReferenceID, OpenMode.ForWrite)
if object.GetType() == Autodesk.AutoCAD.DatabaseServices.BlockReference and object.Name == oldBlockReferenceName:
blockReferences.append(object)
blockReferencePositions.append(object.Position)
####
for object in blockReferences:
object.Erase(True)
for position in blockReferencePositions:
newbr = BlockReference(position, newBlockID)
layoutBTR.AppendEntity(newbr)
t.AddNewlyCreatedDBObject(newbr, True)
t.Commit()
pass
if adoc:
DocumentExtension.CloseAndSave(adoc, DWGPath)
OUT = layoutBlockTableIDs, layoutObjectIDs, blockReferenceIDs, blockReferences, blockReferencePositions, newBlockID
Understood! I have tried to apply your comments, and I have this error.
Do you have any idea?
Anyway, thanks a lot for your help!
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('AutoCADNodes')
clr.AddReference('Civil3DNodes')
# Add standard Python references
import sys
sys.path.append('C:\Program Files (x86)\IronPython 2.7\Lib')
import os
import math
import logging
import tempfile
FORMAT = '[%(asctime)-15s] %(levelname)s: %(module)s.%(funcName)s %(message)s'
logging.basicConfig(filename=os.path.join(tempfile.gettempdir(), 'D4C3D.log'), level=logging.DEBUG, format=FORMAT, datefmt='%Y/%m/%d %I:%M:%S')
# 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 PropertySets
from Autodesk.Aec.PropertyData import *
from Autodesk.Aec.PropertyData.DatabaseServices import *
# Import references for Civil 3D
from Autodesk.Civil.ApplicationServices import *
from Autodesk.Civil.DatabaseServices import *
# Import AutoCAD and Civil 3D Dynamo Nodes
import Autodesk.AutoCAD.DynamoNodes as DA
import Autodesk.Civil.DynamoNodes as DC
import Autodesk.AutoCAD.ApplicationServices.DocumentCollectionExtension
# Function to update attributes of a block reference
def update_block_attributes(path, block_name, attrib_tag, attrib_value):
if path is None or block_name is None or attrib_tag is None or attrib_value is None:
return False
adoc = DocumentCollectionExtension.Open(acapp.DocumentManager, path, False) # Open for Write
acapp.DocumentManager.CurrentDocument = adoc
if adoc is None:
return False
ret = False
try:
with adoc.LockDocument():
with adoc.Database as db:
with db.TransactionManager.StartTransaction() as t:
blockTable = t.GetObject(db.BlockTableId, OpenMode.ForRead)
newBlockID = blockTable[block_name]
layoutObjectIDs = []
layoutBlockTableIDs = []
layoutDict = t.GetObject(db.LayoutDictionaryId, OpenMode.ForWrite)
for layout in layoutDict:
if "Model" not in layout.Key:
layoutObjectIDs.append(layout.Value)
for layoutObjectID in layoutObjectIDs:
layout = t.GetObject(layoutObjectID, OpenMode.ForWrite)
layoutBlockTableID = layout.BlockTableRecordId
layoutBlockTableIDs.append(layoutBlockTableID)
for layoutBlockTableID in layoutBlockTableIDs:
layoutBTR = t.GetObject(layoutBlockTableID, OpenMode.ForWrite)
blockReferenceIDs = [blockReferenceID for blockReferenceID in layoutBTR if isinstance(t.GetObject(blockReferenceID, OpenMode.ForRead), BlockReference)]
for blockReferenceID in blockReferenceIDs:
bt = t.GetObject(blockReferenceID, OpenMode.ForWrite)
if bt.Name != block_name:
continue
bdef = t.GetObject(bt.BlockTableRecord, OpenMode.ForWrite)
if not isinstance(bdef, BlockTableRecord):
continue
for btr in bdef:
if isinstance(btr, BlockReference) and btr.IsDynamicBlock:
for att in btr.AttributeCollection:
if att.Tag == attrib_tag:
# Update the attribute value
att.UpgradeOpen()
att.TextString = attrib_value
att.DowngradeOpen()
t.Commit()
ret = True
except Exception as ex:
MessageBox.Show("Error updating block attributes: " + str(ex))
finally:
DocumentExtension.CloseAndSave(adoc, path)
return ret
# Function to run the script
def main(path, block_name, attrib_tag, attrib_value):
# If critical inputs are missing returns the insturctions
if path is None or block_name is None or attrib_tag is None or attrib_value is None:
return main.__doc__
# Initialize dictionary
ret = {}
ret['Success'] = []
ret['Failure'] = []
dwgs = []
adoc = acapp.DocumentManager.MdiActiveDocument
# Get DWGs
for dirpath, dnames, fnames in os.walk(path):
for f in fnames:
if f.endswith('.dwg'):
dwgs.append(os.path.join(dirpath, f)) # create the references to the DWG files in the folder and subfolders
# Process DWGs
for dwg in dwgs:
try:
res = update_block_attributes(dwg, block_name, attrib_tag, attrib_value)
if res:
ret.setdefault('Success', []).append(dwg) # populate the dictionary
logging.info('File processed correctly {}'.format(dwg))
else:
ret.setdefault('Failure', []).append(dwg)
logging.error('File not processed {}'.format(dwg))
except Exception() as ex:
logging.exception('{}\n{}'.format(dwg, ex.message)) # add exception to log file
acapp.DocumentManager.CurrentDocument = adoc
return ret
OUT = main(IN[0], IN[1], IN[2], IN[3]) # Using most of the default values for brevity
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('AutoCADNodes')
clr.AddReference('Civil3DNodes')
# Add standard Python references
import sys
sys.path.append('C:\Program Files (x86)\IronPython 2.7\Lib')
import os
import math
import logging
import tempfile
FORMAT = '[%(asctime)-15s] %(levelname)s: %(module)s.%(funcName)s %(message)s'
logging.basicConfig(filename=os.path.join(tempfile.gettempdir(), 'D4C3D.log'), level=logging.DEBUG, format=FORMAT, datefmt='%Y/%m/%d %I:%M:%S')
# 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 PropertySets
from Autodesk.Aec.PropertyData import *
from Autodesk.Aec.PropertyData.DatabaseServices import *
# Import references for Civil 3D
from Autodesk.Civil.ApplicationServices import *
from Autodesk.Civil.DatabaseServices import *
# Import AutoCAD and Civil 3D Dynamo Nodes
import Autodesk.AutoCAD.DynamoNodes as DA
import Autodesk.Civil.DynamoNodes as DC
import Autodesk.AutoCAD.ApplicationServices.DocumentCollectionExtension
# Function to update attributes of a block reference
def update_block_attributes(path, block_name, attrib_tag, attrib_value):
if path is None or block_name is None or attrib_tag is None or attrib_value is None:
return False
adoc = DocumentCollectionExtension.Open(acapp.DocumentManager, path, False) # Open for Write
acapp.DocumentManager.CurrentDocument = adoc
if adoc is None:
return False
ret = False
TTs = []
try:
with adoc.LockDocument():
with adoc.Database as db:
with db.TransactionManager.StartTransaction() as t:
blockTable = t.GetObject(db.BlockTableId, OpenMode.ForRead)
newBlockID = blockTable[block_name]
layoutObjectIDs = []
layoutBlockTableIDs = []
layoutDict = t.GetObject(db.LayoutDictionaryId, OpenMode.ForWrite)
for layout in layoutDict:
if "Model" not in layout.Key:
layoutObjectIDs.append(layout.Value)
for layoutObjectID in layoutObjectIDs:
layout = t.GetObject(layoutObjectID, OpenMode.ForWrite)
layoutBlockTableID = layout.BlockTableRecordId
layoutBlockTableIDs.append(layoutBlockTableID)
for layoutBlockTableID in layoutBlockTableIDs:
layoutBTR = t.GetObject(layoutBlockTableID, OpenMode.ForWrite)
blockReferenceIDs = [blockReferenceID for blockReferenceID in layoutBTR if isinstance(t.GetObject(blockReferenceID, OpenMode.ForRead), BlockReference)]
for blockReferenceID in blockReferenceIDs:
bt = t.GetObject(blockReferenceID, OpenMode.ForWrite)
if bt.Name == block_name:
TTs.append(bt)
for att in bt.AttributeCollection:
if att.Tag == attrib_tag:
att.TextString = attrib_value
t.Commit()
ret = True
except Exception as ex:
MessageBox.Show("Error updating block attributes: " + str(ex))
finally:
DocumentExtension.CloseAndSave(adoc, path)
return TTs
# Function to run the script
def main(path, block_name, attrib_tag, attrib_value):
if path is None or block_name is None or attrib_tag is None or attrib_value is None:
return main.__doc__
# Initialize dictionary
ret = {}
ret['Success'] = []
ret['Failure'] = []
dwgs = []
WWs = []
adoc = acapp.DocumentManager.MdiActiveDocument
# Get DWGs
for dirpath, dnames, fnames in os.walk(path):
for f in fnames:
if f.endswith('.dwg'):
dwgs.append(os.path.join(dirpath, f))
# Process DWGs
ai = 0
for dwg in dwgs:
try:
for j, k in zip( attrib_tag , attrib_value ):
res = update_block_attributes( dwg, block_name, j, k )
WWs.append(res)
except Exception() as ex:
logging.exception('{}\n{}'.format(dwg, ex.message)) # add exception to log file
acapp.DocumentManager.CurrentDocument = adoc
return WWs
OUT = main(IN[0], IN[1], IN[2], IN[3])
I don’t know if you’ve already tried this, but the Camber package has nodes for working with external documents (i.e., other DWGs that don’t need to be open). There are nodes for getting/setting block attributes, including dynamic block attributes.
Thank you very much. It’s working properly. It was exactly what I was trying to do.
I just changed a few lines to make the changes to the attributes all at once.
if bt.Name == block_name:
TTs.append(bt)
for att in bt.AttributeCollection:
for i, tag in enumerate(attrib_tags):
if att.Tag == tag:
att.TextString = attrib_values[i]
I am trying to use it with a dynamic block too, but it seems that the block name is not being recognized. Do I need to change something?
Hi @zachri.jensen, I have the Camber package installed and I’ve checked all the nodes, but I couldn’t find any that work with external documents. Can you please let me know which nodes you’re referring to? Thank you!