Skip to content

Commit 26c2c0a

Browse files
committed
#6 bug fixed on exporting blocks and version updates
1 parent c7efeb5 commit 26c2c0a

7 files changed

Lines changed: 380 additions & 38 deletions

File tree

AutoCAD/AutoCAD_Module.py

Lines changed: 164 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
import os
12
import subprocess
23
import psutil
34
import pythoncom
45
import win32com.client
56
from enum import Enum
7+
import time
68

79
# project by jones peter
810
class Color(Enum):
@@ -691,20 +693,37 @@ def __init__(self):
691693
CADException: If AutoCAD application cannot be initialized.
692694
"""
693695
try:
694-
self.acad = win32com.client.Dispatch("AutoCAD.Application")
696+
try:
697+
self.acad = win32com.client.gencache.EnsureDispatch("AutoCAD.Application")
698+
except Exception:
699+
self.acad = win32com.client.Dispatch("AutoCAD.Application")
695700
self.acad.Visible = True
696701
except Exception as e:
697702
raise CADException(f"Error initializing AutoCAD: {e}")
698703

704+
def _get_with_retry(self, func, retries=5, delay=0.5):
705+
"""
706+
Helper method to retry COM property/method access.
707+
Often needed when AutoCAD is busy initializing or loading a document.
708+
"""
709+
for i in range(retries):
710+
try:
711+
return func()
712+
except Exception as e:
713+
# "Call was rejected by callee" (-2147418111) usually means busy
714+
if i == retries - 1:
715+
raise e
716+
time.sleep(delay)
717+
699718
@property
700719
def doc(self):
701720
""" Returns `ActiveDocument` of current :attr:`Application`"""
702-
return self.acad.ActiveDocument
721+
return self._get_with_retry(lambda: self.acad.ActiveDocument)
703722

704723
@property
705724
def modelspace(self):
706-
""" Returns `ActiveDocument` of current :attr:`Application`"""
707-
return self.doc.ModelSpace
725+
""" Returns `ModelSpace` of current :attr:`Application`"""
726+
return self._get_with_retry(lambda: self.doc.ModelSpace)
708727

709728
@property
710729
def properties(self):
@@ -751,9 +770,17 @@ def iter_objects(self, object_type=None):
751770
Yields:
752771
The AutoCAD object.
753772
"""
754-
for obj in self.modelspace:
755-
if object_type is None or obj.EntityName == object_type:
756-
yield obj
773+
# Changed to index-based iteration to avoid COM enumeration issues with early binding
774+
model_space = self.modelspace
775+
count = self._get_with_retry(lambda: model_space.Count)
776+
for i in range(count):
777+
try:
778+
obj = self._get_with_retry(lambda: model_space.Item(i), retries=3, delay=0.1)
779+
entity_name = self._get_with_retry(lambda: obj.EntityName, retries=3, delay=0.1)
780+
if object_type is None or entity_name == object_type:
781+
yield obj
782+
except Exception:
783+
continue
757784

758785
def add_circle(self, center, radius):
759786
"""
@@ -798,8 +825,9 @@ def _ensure_linetype_loaded(self, linetype_name):
798825
try:
799826
linetypes = self.doc.Linetypes
800827
linetype_exists = False
801-
for lt in linetypes:
802-
if lt.Name.lower() == linetype_name.lower():
828+
# Iterate by index to avoid potential enumeration issues
829+
for i in range(linetypes.Count):
830+
if linetypes.Item(i).Name.lower() == linetype_name.lower():
803831
linetype_exists = True
804832
break
805833

@@ -1101,8 +1129,12 @@ def get_user_defined_blocks(self):
11011129
"""
11021130
try:
11031131
blocks = self.doc.Blocks
1104-
user_defined_blocks = [block.Name for block in blocks
1105-
if not block.IsLayout and not block.Name.startswith('*') and block.Name != 'GENAXEH']
1132+
user_defined_blocks = []
1133+
# Changed to index-based iteration
1134+
for i in range(blocks.Count):
1135+
block = blocks.Item(i)
1136+
if not block.IsLayout and not block.Name.startswith('*') and block.Name != 'GENAXEH':
1137+
user_defined_blocks.append(block.Name)
11061138
return user_defined_blocks
11071139
except Exception as e:
11081140
raise CADException(f"Error getting user-defined blocks: {e}")
@@ -1204,6 +1236,8 @@ def open_file(self, file_path):
12041236
"""
12051237
try:
12061238
self.acad.Documents.Open(file_path)
1239+
# Give AutoCAD a small moment to initialize the COM objects for the newly opened file
1240+
time.sleep(1)
12071241
except Exception as e:
12081242
raise CADException(f"Error opening file '{file_path}': {e}")
12091243

@@ -1364,9 +1398,10 @@ def set_layer_linetype(self, layer_name, linetype_name):
13641398
"""
13651399
try:
13661400
layer = self.doc.Layers.Item(layer_name)
1367-
linetypes = self.doc.Linetypes
1368-
if linetype_name not in linetypes:
1369-
self.doc.Linetypes.Load(linetype_name, linetype_name)
1401+
1402+
# Ensure linetype is loaded using the helper method
1403+
self._ensure_linetype_loaded(linetype_name)
1404+
13701405
layer.Linetype = linetype_name
13711406
except Exception as e:
13721407
raise CADException(f"Error setting linetype of layer '{layer_name}': {e}")
@@ -1494,20 +1529,120 @@ def insert_block_from_file(self, file_path, insertion_point, scale=1.0, rotation
14941529
except Exception as e:
14951530
raise CADException(f"Error inserting block from file '{file_path}': {e}")
14961531

1497-
def export_block_to_file(self, block_name, file_path):
1498-
"""
1499-
Exports a block definition from the current drawing to a new .dwg file.
1500-
Args:
1501-
block_name (str): The name of the block to export.
1502-
file_path (str): The destination path for the new .dwg file.
1503-
Raises:
1504-
CADException: If the block cannot be exported.
1505-
"""
1532+
def export_blocks_to_file(self, block_names, file_path):
1533+
ss_name = "TEMP_EXPORT_SS"
1534+
temp_file = file_path + ".tmp.dwg"
1535+
temp_instances = []
1536+
1537+
def retry_com(func, retries=10, delay=2.0):
1538+
for attempt in range(retries):
1539+
try:
1540+
return func()
1541+
except Exception as e:
1542+
if "callee" in str(e).lower() or "-2147418111" in str(e):
1543+
print(f"AutoCAD busy, retrying ({attempt + 1}/{retries})...")
1544+
time.sleep(delay)
1545+
else:
1546+
raise
1547+
raise CADException(f"AutoCAD rejected the call after {retries} retries.")
1548+
15061549
try:
1507-
block = self.doc.Blocks.Item(block_name)
1508-
block.Export(file_path)
1550+
try:
1551+
self.doc.SelectionSets.Item(ss_name).Delete()
1552+
except:
1553+
pass
1554+
1555+
export_ss = self.doc.SelectionSets.Add(ss_name)
1556+
1557+
# Use a far-away point so we never overlap existing objects
1558+
safe_point = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [1e18, 1e18, 0.0])
1559+
1560+
for block_name in block_names:
1561+
found_block = None
1562+
for i in range(self.doc.Blocks.Count):
1563+
block = self.doc.Blocks.Item(i)
1564+
if block.Name.lower() == block_name.lower():
1565+
found_block = block
1566+
break
1567+
1568+
if not found_block:
1569+
raise CADException(f"Block '{block_name}' not found in the current drawing.")
1570+
1571+
print(f"Found '{block_name}'")
1572+
temp_instance = retry_com(lambda: self.doc.ModelSpace.InsertBlock(safe_point, block_name, 1, 1, 1, 0))
1573+
temp_instances.append(temp_instance)
1574+
time.sleep(0.3)
1575+
1576+
objs_array = win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_DISPATCH, temp_instances)
1577+
export_ss.AddItems(objs_array)
1578+
1579+
time.sleep(1.0)
1580+
1581+
old_expert = self.doc.GetVariable("EXPERT")
1582+
self.doc.SetVariable("EXPERT", 2)
1583+
try:
1584+
retry_com(lambda: self.doc.Wblock(temp_file, export_ss))
1585+
finally:
1586+
self.doc.SetVariable("EXPERT", old_expert)
1587+
1588+
# Clean up temp instances from ModelSpace
1589+
for inst in temp_instances:
1590+
try:
1591+
inst.Delete()
1592+
except:
1593+
pass
1594+
temp_instances = []
1595+
1596+
try:
1597+
self.doc.SelectionSets.Item(ss_name).Delete()
1598+
except:
1599+
pass
1600+
1601+
time.sleep(1.0)
1602+
1603+
if os.path.exists(file_path):
1604+
target_doc = retry_com(lambda: self.acad.Documents.Open(file_path))
1605+
inserted = retry_com(lambda: target_doc.ModelSpace.InsertBlock(
1606+
win32com.client.VARIANT(pythoncom.VT_ARRAY | pythoncom.VT_R8, [1e18, 1e18, 0.0]),
1607+
temp_file, 1, 1, 1, 0
1608+
))
1609+
try:
1610+
inserted.Delete()
1611+
except:
1612+
pass
1613+
target_doc.Save()
1614+
target_doc.Close(False)
1615+
else:
1616+
os.rename(temp_file, file_path)
1617+
1618+
print(f"Exported {block_names} to '{file_path}'")
1619+
1620+
self.close(save_changes=False)
1621+
1622+
except CADException:
1623+
raise
15091624
except Exception as e:
1510-
raise CADException(f"Error exporting block '{block_name}' to '{file_path}': {e}")
1625+
raise CADException(f"Export failed: {e}")
1626+
1627+
finally:
1628+
for inst in temp_instances:
1629+
try:
1630+
inst.Delete()
1631+
except:
1632+
pass
1633+
try:
1634+
self.doc.SelectionSets.Item(ss_name).Delete()
1635+
except:
1636+
pass
1637+
if os.path.exists(temp_file):
1638+
for attempt in range(5):
1639+
try:
1640+
time.sleep(0.5)
1641+
os.remove(temp_file)
1642+
print(f"Temp file deleted: {temp_file}")
1643+
break
1644+
except Exception as e:
1645+
print(f"Warning: Could not delete temp file (attempt {attempt + 1}/5): {e}")
15111646

15121647
def modify_block_attribute(self, block_ref, tag, new_value):
15131648
"""
@@ -1717,8 +1852,9 @@ def add_mtext(self, content: str, insertion_point: APoint, width: float, height:
17171852
if text_style:
17181853
text_styles = self.doc.TextStyles
17191854
style_exists = False
1720-
for style in text_styles:
1721-
if style.Name.lower() == text_style.lower():
1855+
# Changed to index-based iteration
1856+
for i in range(text_styles.Count):
1857+
if text_styles.Item(i).Name.lower() == text_style.lower():
17221858
style_exists = True
17231859
break
17241860

AutoCAD/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
from .AutoCAD_Module import *
2-
__version__ = "0.1.11"
2+
__version__ = "0.1.12"

AutoCAD/__main__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,4 @@
77
print("| github: https://github.com/Jones-peter By - JonesPeter |")
88
print("| |")
99
print("╚──────────────────────────Unlimited CAD Automation───────────────────────────╝")
10-
print(f"Version - 0.1.11")
10+
print(f"Version - 0.1.12")

0 commit comments

Comments
 (0)