Run a script of a Dynamo in multiple dwg files

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.

Thank you in advance for your help.

GetBlockReferenceAttributes.dyn (65.1 KB)

HI
CAN YOU Share a complete example
Excel fileAnd several drawings
I’m experimenting with it
1111

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!

Proposal Drainage-06.dwg (509.9 KB)
Proposal Drainage-06.dwg (509.9 KB)

BWT_ProjectInformation_A1.xlsx (9.8 KB)

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.

GetBlockReferenceAttributes_V2.dyn (80.4 KB)

hi
You must search in layouts

Need example from

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


But what if I don’t want to replace the block, but only update the attribute values of the block? Do I still need to search in layouts?

your block Exists in layouts
So you should get it from it

Understood! I have tried to apply your comments, and I have this error.

6

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

I think
Try for one item

res = update_block_attributes(dwg, block_name, attrib_tag[0], attrib_value[0])

If it working
You need loops

@fcrobu TRY

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])  



2 Likes

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. :hugs: :partying_face:
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 @mzjensen, 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!

They are under the “External” shelf.

Hi @fcrobu,

Do you have any idea if the values in each of blocks are different?
Maybe Input[0] is a file path (not directory path). ???

Thank you

IN[0] is the folder where there are all the dwg files that the script is going to run on. The values of the tags are in IN[3].

I mean that the block in all dwg files in the same values.
If values in block of each file is different, Do you need loop?