当前位置:首页>python>《还在手动改CAD图框?Python调用CAD自带Core Console引擎,100张图10分钟批量搞定!彻底告别手动改图的噩梦~》

《还在手动改CAD图框?Python调用CAD自带Core Console引擎,100张图10分钟批量搞定!彻底告别手动改图的噩梦~》

  • 2026-10-11 05:40:50
《还在手动改CAD图框?Python调用CAD自带Core Console引擎,100张图10分钟批量搞定!彻底告别手动改图的噩梦~》

“带你横跨办公自动化的数据江海”

@摸鱼

前言

关于摸鱼

(

ABOUT MOYU

)

闻道有先后,术业有专攻。

各位大佬朋友们好!

~我依旧是你们的老朋友摸鱼~

在职场摸爬滚打的这十多年里,我用Python悄悄干了不少“正事”——不知不觉攒下了一整套办公自动化的实用项目技巧。去年10月初创立了公众号 「码海听潮」 ,初衷很简单:把重复的劳动交给代码,把摸鱼的时间留给生活。

目前已经吭哧吭哧更新了120多篇原创文章,每一篇都是实操干货,不讲虚的,只聊怎么用代码真正解放双手,帮大家早点下班、准点摸鱼

好了,多了不说,少了不唠,今天给大佬们分享一款本人原创的【CAD图块批量替换工具】,这款小工具专为工程设计人员和图纸管理人员设计,具备智能图框识别、一键批量替换、图块属性保留等核心功能。它支持DWG文件的批量导入,能自动识别图纸中的旧图块引用、精准定位图框插入点、保留图块属性文字,并按照预设的缩放比例进行智能替换,替换后自动炸开图块为可编辑几何图形。工具提供Core Console无界面批处理模式,支持多线程并行处理,大幅提升大批量图纸的替换效率,同时生成详细的运行日志和结果汇总报表,让整个处理过程清晰可控。

《办公工具成品展示:》

《该办公工具核心功能亮点:》

01

 亮点1:双模式处理引擎🎯

        • Core Console模式(首选):使用AutoCAD无界面命令行引擎,无需打开CAD界面,支持多线程并行处理(默认4线程)

        • COM模式(备用):通过Windows COM接口连接正在运行的AutoCAD桌面程序

        02

         亮点2: 智能缩放与定位📱

            • 自动计算并应用缩放比例(默认0.5倍),适配不同尺寸的图框

            • 精确保留原图框的插入点位置(X、Y、Z坐标)

            • 保持图框的旋转角度不变

            03

            亮点3:批量图框替换⚡

            • 自动扫描指定文件夹中的所有DWG图纸

            • 将旧图块(如 A$C23BC78A5)批量替换为新图块(如 A3)

            • 支持自定义旧/新图块名称

            《该办公工具代码展示:》

            下面,我就用python代码让各位大佬见识一下,什么叫"传统文化"遇上"赛博效率"

            # -*- coding: utf-8 -*-import sysfrom pathlib import Pathfrom datetime import datetimefrom typing import List, Dict, Anyfrom PyQt6.QtWidgets import (    QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,    QPushButton, QLabel, QLineEdit, QTextEdit, QFileDialog,    QGroupBox, QGridLayout, QCheckBox, QSpinBox, QDoubleSpinBox,    QProgressBar, QTableWidget, QTableWidgetItem, QHeaderView,    QTabWidget, QSplitter, QMessageBox, QFrame)from PyQt6.QtCore import Qt, QThread, pyqtSignalfrom PyQt6.QtGui import QFont, QTextCursor, QColorclass WorkerThread(QThread):    """模拟后台工作线程"""    log_signal = pyqtSignal(str)    progress_signal = pyqtSignal(int, int)    finished_signal = pyqtSignal(list)    error_signal = pyqtSignal(str)    def __init__(self, config: Dict[str, Any]):        super().__init__()        self.config = config        self.is_running = True    def run(self):        """模拟执行处理任务"""        try:            self.log_signal.emit("[INFO] 开始模拟处理...")            # 模拟获取文件列表            source_dir = Path(self.config.get('source_dir', ''))            if source_dir.exists():                dwg_files = [f for f in source_dir.iterdir() if f.suffix.lower() == '.dwg']                total = len(dwg_files)                self.log_signal.emit(f"[INFO] 找到 {total} 个DWG文件")            else:                total = 5                self.log_signal.emit("[WARN] 源文件夹不存在,使用模拟数据")            # 模拟处理过程            result_rows = []            for i in range(total):                if not self.is_running:                    self.log_signal.emit("[INFO] 用户停止处理")                    break                # 模拟处理进度                self.progress_signal.emit(i + 1, total)                self.log_signal.emit(f"[PROCESS] 正在处理文件 {i+1}/{total}...")                # 模拟处理结果                result_rows.append({                    "文件名": f"图纸_{i+1:03d}.dwg",                    "处理方式": "模拟模式",                    "结果": "成功" if i % 3 != 2 else "失败",                    "替换图框数": i + 1,                    "输出文件": f"C:/Output/图纸_{i+1:03d}.dwg",                    "说明": "模拟处理" if i % 3 != 2 else "模拟失败"                })                # 模拟处理耗时                self.msleep(500)            self.log_signal.emit("[INFO] 模拟处理完成")            self.finished_signal.emit(result_rows)        except Exception as e:            self.error_signal.emit(str(e))    def stop(self):        """停止处理"""        self.is_running = Falseclass CADBatchReplaceGUI(QMainWindow):    """主窗口"""    def __init__(self):        super().__init__()        self.worker = None        self.result_rows = []        self.init_ui()    def init_ui(self):        """初始化界面"""        self.setWindowTitle("CAD图块批量替换工具")        self.setGeometry(100, 100, 1200, 800)        # 中央部件        central_widget = QWidget()        self.setCentralWidget(central_widget)        main_layout = QVBoxLayout(central_widget)        # 创建分割器        splitter = QSplitter(Qt.Orientation.Vertical)        main_layout.addWidget(splitter)        # 上部:配置区域        config_widget = QWidget()        config_layout = QVBoxLayout(config_widget)        # === 配置分组 ===        config_group = QGroupBox("📐 配置参数")        config_grid = QGridLayout(config_group)        # 第0行:源文件夹        config_grid.addWidget(QLabel("源文件夹:"), 0, 0)        self.source_dir_edit = QLineEdit(r"C:\Temp\CAD_Source")        config_grid.addWidget(self.source_dir_edit, 0, 1, 1, 2)        btn_source = QPushButton("浏览...")        btn_source.clicked.connect(self.browse_source_dir)        config_grid.addWidget(btn_source, 0, 3)        # 第1行:输出文件夹        config_grid.addWidget(QLabel("输出文件夹:"), 1, 0)        self.output_dir_edit = QLineEdit(r"C:\Temp\CAD_Output")        config_grid.addWidget(self.output_dir_edit, 1, 1, 1, 2)        btn_output = QPushButton("浏览...")        btn_output.clicked.connect(self.browse_output_dir)        config_grid.addWidget(btn_output, 1, 3)        # 第2行:A3图框文件        config_grid.addWidget(QLabel("A3图框文件:"), 2, 0)        self.a3_path_edit = QLineEdit(r"C:\Temp\A3.dwg")        config_grid.addWidget(self.a3_path_edit, 2, 1, 1, 2)        btn_a3 = QPushButton("浏览...")        btn_a3.clicked.connect(self.browse_a3_file)        config_grid.addWidget(btn_a3, 2, 3)        # 第3行:图块名称        config_grid.addWidget(QLabel("旧图块名称:"), 3, 0)        self.old_block_edit = QLineEdit("A$C23BC78A5")        config_grid.addWidget(self.old_block_edit, 3, 1)        config_grid.addWidget(QLabel("新图块名称:"), 3, 2)        self.new_block_edit = QLineEdit("A3")        config_grid.addWidget(self.new_block_edit, 3, 3)        # 第4行:缩放比例        config_grid.addWidget(QLabel("缩放比例:"), 4, 0)        self.scale_spin = QDoubleSpinBox()        self.scale_spin.setRange(0.01, 10.0)        self.scale_spin.setSingleStep(0.1)        self.scale_spin.setValue(0.5)        config_grid.addWidget(self.scale_spin, 4, 1)        # 第4行:并行数        config_grid.addWidget(QLabel("并行处理数:"), 4, 2)        self.workers_spin = QSpinBox()        self.workers_spin.setRange(1, 16)        self.workers_spin.setValue(4)        config_grid.addWidget(self.workers_spin, 4, 3)        # 第5行:超时时间        config_grid.addWidget(QLabel("超时时间(秒):"), 5, 0)        self.timeout_spin = QSpinBox()        self.timeout_spin.setRange(30, 600)        self.timeout_spin.setValue(180)        config_grid.addWidget(self.timeout_spin, 5, 1)        # 第5行:选项        self.overwrite_check = QCheckBox("覆盖已存在的输出文件")        self.overwrite_check.setChecked(True)        config_grid.addWidget(self.overwrite_check, 5, 2)        self.core_console_check = QCheckBox("强制使用Core Console模式")        self.core_console_check.setChecked(True)        config_grid.addWidget(self.core_console_check, 5, 3)        # 第6行:Core Console路径        config_grid.addWidget(QLabel("Core Console路径:"), 6, 0)        self.accore_path_edit = QLineEdit(r"F:\Auto CAD\AutoCAD 2016\accoreconsole.exe")        config_grid.addWidget(self.accore_path_edit, 6, 1, 1, 2)        btn_accore = QPushButton("浏览...")        btn_accore.clicked.connect(self.browse_accore_file)        config_grid.addWidget(btn_accore, 6, 3)        config_layout.addWidget(config_group)        # === 控制按钮 ===        btn_layout = QHBoxLayout()        self.btn_start = QPushButton("▶ 开始处理")        self.btn_start.setStyleSheet("""            QPushButton {                 font-weight: bold;                 font-size: 14px;                 padding: 8px 20px;                background-color: #4CAF50;                color: white;                border-radius: 5px;            }            QPushButton:hover {                background-color: #45a049;            }            QPushButton:disabled {                background-color: #cccccc;                color: #666666;            }        """)        self.btn_start.clicked.connect(self.start_processing)        btn_layout.addWidget(self.btn_start)        self.btn_stop = QPushButton("⏹ 停止")        self.btn_stop.setStyleSheet("""            QPushButton {                font-weight: bold;                font-size: 14px;                padding: 8px 20px;                background-color: #f44336;                color: white;                border-radius: 5px;            }            QPushButton:hover {                background-color: #d32f2f;            }            QPushButton:disabled {                background-color: #cccccc;                color: #666666;            }        """)        self.btn_stop.setEnabled(False)        self.btn_stop.clicked.connect(self.stop_processing)        btn_layout.addWidget(self.btn_stop)        btn_layout.addStretch()        self.btn_clear_log = QPushButton("🗑 清空日志")        self.btn_clear_log.clicked.connect(self.clear_log)        btn_layout.addWidget(self.btn_clear_log)        self.btn_export = QPushButton("📊 导出结果")        self.btn_export.clicked.connect(self.export_results)        btn_layout.addWidget(self.btn_export)        config_layout.addLayout(btn_layout)        # === 进度条 ===        self.progress_bar = QProgressBar()        self.progress_bar.setVisible(False)        self.progress_bar.setStyleSheet("""            QProgressBar {                border: 1px solid #ccc;                border-radius: 5px;                text-align: center;                height: 25px;            }            QProgressBar::chunk {                background-color: #4CAF50;                border-radius: 5px;            }        """)        config_layout.addWidget(self.progress_bar)        splitter.addWidget(config_widget)        # === 底部:日志和结果 ===        bottom_widget = QWidget()        bottom_layout = QVBoxLayout(bottom_widget)        tab_widget = QTabWidget()        tab_widget.setStyleSheet("""            QTabWidget::pane {                border: 1px solid #ccc;                border-radius: 5px;            }            QTabBar::tab {                padding: 8px 16px;                margin-right: 2px;            }            QTabBar::tab:selected {                background-color: #e0e0e0;                font-weight: bold;            }        """)        # === 日志标签页 ===        log_widget = QWidget()        log_layout = QVBoxLayout(log_widget)        self.log_text = QTextEdit()        self.log_text.setFont(QFont("Consolas", 10))        self.log_text.setReadOnly(True)        self.log_text.setStyleSheet("""            QTextEdit {                background-color: #1e1e1e;                color: #d4d4d4;                border: none;            }        """)        log_layout.addWidget(self.log_text)        tab_widget.addTab(log_widget, "📋 运行日志")        # === 结果标签页 ===        result_widget = QWidget()        result_layout = QVBoxLayout(result_widget)        self.result_table = QTableWidget()        self.result_table.setColumnCount(6)        self.result_table.setHorizontalHeaderLabels(["文件名", "处理方式", "结果", "替换图框数", "输出文件", "说明"])        self.result_table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.ResizeToContents)        self.result_table.horizontalHeader().setSectionResizeMode(0, QHeaderView.ResizeMode.Stretch)        self.result_table.horizontalHeader().setSectionResizeMode(4, QHeaderView.ResizeMode.Stretch)        self.result_table.setAlternatingRowColors(True)        self.result_table.setStyleSheet("""            QTableWidget {                gridline-color: #d0d0d0;            }            QTableWidget::item:selected {                background-color: #4CAF50;                color: white;            }        """)        result_layout.addWidget(self.result_table)        tab_widget.addTab(result_widget, "📊 处理结果")        bottom_layout.addWidget(tab_widget)        splitter.addWidget(bottom_widget)        # 设置分割比例        splitter.setSizes([400, 400])        # === 初始日志 ===        self.log("=" * 70)        self.log("  CAD图块批量替换工具 v2.0")        self.log("  界面加载完成,等待用户操作")        self.log("=" * 70)        self.log("📁 工作目录: " + str(Path(__file__).resolve().parent))        self.log("💡 提示: 点击「开始处理」执行模拟任务")        self.log("=" * 70)    # ==================== 日志方法 ====================    def log(self, msg: str):        """添加日志"""        timestamp = datetime.now().strftime("%H:%M:%S")        self.log_text.append(f"[{timestamp}] {msg}")        self.log_text.moveCursor(QTextCursor.MoveOperation.End)    # ==================== 浏览方法 ====================    def browse_source_dir(self):        """浏览源文件夹"""        dir_path = QFileDialog.getExistingDirectory(            self,             "选择源文件夹",            self.source_dir_edit.text()        )        if dir_path:            self.source_dir_edit.setText(dir_path)    def browse_output_dir(self):        """浏览输出文件夹"""        dir_path = QFileDialog.getExistingDirectory(            self,             "选择输出文件夹",            self.output_dir_edit.text()        )        if dir_path:            self.output_dir_edit.setText(dir_path)    def browse_a3_file(self):        """浏览A3图框文件"""        file_path, _ = QFileDialog.getOpenFileName(            self,             "选择A3图框文件",            self.a3_path_edit.text(),            "DWG Files (*.dwg)"        )        if file_path:            self.a3_path_edit.setText(file_path)    def browse_accore_file(self):        """浏览accoreconsole.exe"""        file_path, _ = QFileDialog.getOpenFileName(            self,             "选择accoreconsole.exe",            "",            "Executable Files (*.exe)"        )        if file_path:            self.accore_path_edit.setText(file_path)    # ==================== 处理方法 ====================    def start_processing(self):        """开始处理"""        # 收集配置        config = {            'source_dir': self.source_dir_edit.text(),            'output_dir': self.output_dir_edit.text(),            'a3_path': self.a3_path_edit.text(),            'old_block': self.old_block_edit.text(),            'new_block': self.new_block_edit.text(),            'scale_ratio': self.scale_spin.value(),            'overwrite': self.overwrite_check.isChecked(),            'force_core_console': self.core_console_check.isChecked(),            'accore_path': self.accore_path_edit.text(),            'workers': self.workers_spin.value(),            'timeout': self.timeout_spin.value(),        }        # 清空旧结果        self.result_table.setRowCount(0)        self.result_rows = []        # 禁用按钮        self.btn_start.setEnabled(False)        self.btn_stop.setEnabled(True)        self.progress_bar.setVisible(True)        self.progress_bar.setValue(0)        self.log("\n" + "=" * 70)        self.log("▶ 开始执行处理任务")        self.log(f"📂 源文件夹: {config['source_dir']}")        self.log(f"📂 输出文件夹: {config['output_dir']}")        self.log(f"🔄 替换: {config['old_block']} → {config['new_block']}")        self.log("=" * 70)        # 启动工作线程        self.worker = WorkerThread(config)        self.worker.log_signal.connect(self.log)        self.worker.progress_signal.connect(self.update_progress)        self.worker.finished_signal.connect(self.on_finished)        self.worker.error_signal.connect(self.on_error)        self.worker.start()    def stop_processing(self):        """停止处理"""        if self.worker and self.worker.isRunning():            self.worker.stop()            self.log("⏹ 用户请求停止处理...")            self.btn_stop.setEnabled(False)    def update_progress(self, current: int, total: int):        """更新进度"""        if total > 0:            percent = int(current / total * 100)            self.progress_bar.setValue(percent)            self.progress_bar.setFormat(f"{current}/{total} ({percent}%)")    def on_finished(self, result_rows: list):        """处理完成"""        self.result_rows = result_rows        self.update_result_table(result_rows)        # 统计信息        success = sum(1 for row in result_rows if row["结果"] == "成功")        fail = sum(1 for row in result_rows if row["结果"] == "失败")        skipped = sum(1 for row in result_rows if row["结果"] == "跳过")        replaced = sum(int(row["替换图框数"] or 0) for row in result_rows)        self.log("\n" + "=" * 70)        self.log("✅ 处理完成!")        self.log(f"📊 统计: 成功 {success} | 失败 {fail} | 跳过 {skipped}")        self.log(f"🔄 替换图框总数: {replaced}")        self.log("=" * 70)        # 恢复按钮        self.btn_start.setEnabled(True)        self.btn_stop.setEnabled(False)        self.progress_bar.setVisible(False)        # 显示提示        QMessageBox.information(            self,             "处理完成",            f"✅ 处理完成!\n\n"            f"📊 成功: {success}\n"            f"❌ 失败: {fail}\n"            f"⏭ 跳过: {skipped}\n"            f"🔄 替换图框: {replaced}"        )    def on_error(self, error_msg: str):        """错误处理"""        self.log(f"❌ [ERROR] {error_msg}")        QMessageBox.critical(self, "错误", f"处理过程中发生错误:\n\n{error_msg}")        self.btn_start.setEnabled(True)        self.btn_stop.setEnabled(False)        self.progress_bar.setVisible(False)    # ==================== 表格方法 ====================    def update_result_table(self, rows: list):        """更新结果表格"""        self.result_table.setRowCount(len(rows))        for i, row in enumerate(rows):            # 文件名            self.result_table.setItem(i, 0, QTableWidgetItem(row["文件名"]))            # 处理方式            self.result_table.setItem(i, 1, QTableWidgetItem(row["处理方式"]))            # 结果(带颜色)            status_item = QTableWidgetItem(row["结果"])            if row["结果"] == "成功":                status_item.setBackground(QColor(200, 255, 200))                status_item.setForeground(QColor(0, 128, 0))            elif row["结果"] == "失败":                status_item.setBackground(QColor(255, 200, 200))                status_item.setForeground(QColor(255, 0, 0))            else:                status_item.setBackground(QColor(255, 255, 200))                status_item.setForeground(QColor(128, 128, 0))            self.result_table.setItem(i, 2, status_item)            # 替换图框数            self.result_table.setItem(i, 3, QTableWidgetItem(str(row["替换图框数"])))            # 输出文件            output_item = QTableWidgetItem(str(row["输出文件"]))            output_item.setToolTip(str(row["输出文件"]))            self.result_table.setItem(i, 4, output_item)            # 说明            self.result_table.setItem(i, 5, QTableWidgetItem(row["说明"]))        # 调整列宽        self.result_table.resizeColumnsToContents()    # ==================== 其他方法 ====================    def clear_log(self):        """清空日志"""        self.log_text.clear()        self.log("🗑 日志已清空")    def export_results(self):        """导出结果"""        if not self.result_rows:            QMessageBox.information(self, "提示", "没有结果可导出")            return        file_path, _ = QFileDialog.getSaveFileName(            self,            "导出结果",            f"处理结果_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv",            "CSV Files (*.csv)"        )        if file_path:            try:                import csv                with open(file_path, 'w', newline='', encoding='utf-8-sig') as f:                    writer = csv.DictWriter(f, fieldnames=self.result_rows[0].keys())                    writer.writeheader()                    writer.writerows(self.result_rows)                QMessageBox.information(self, "成功", f"✅ 结果已导出到:\n{file_path}")                self.log(f"📊 结果已导出: {file_path}")            except Exception as e:                QMessageBox.critical(self, "错误", f"导出失败:\n{e}")                self.log(f"❌ 导出失败: {e}")def main():    """主函数"""    app = QApplication(sys.argv)    app.setStyle('Fusion')    # 设置应用图标(可选)    app.setApplicationName("CAD图块批量替换工具")    app.setApplicationDisplayName("CAD图块批量替换工具")    window = CADBatchReplaceGUI()    window.show()    sys.exit(app.exec())if __name__ == "__main__":    main()

            通过这款CAD图块批量替换工具,仅需几分钟配置好源文件夹、输出路径、图块名称及缩放比例等核心参数,即可自动完成原需手动逐一打开图纸、查找替换图框、调整缩放比例的一整天重复性工作。从最初逐个打开DWG文件手动替换图框的疲惫与焦虑,到用自动化工具实现一键批量处理上百张图纸的畅快,工作效率获得指数级提升,终于解放了双手和时间!

            大佬们也可以举一反三,参照上面的代码思路根据自己工作中的实际情况来具体问题具体分析,实现自己定制化的需求。

            《该办公工具的适用场景:》

            1. 设计院/工程公司批量图纸更新

            • 项目标准变更时,需要统一替换所有图纸中的图框

            • 新旧图框尺寸不一致(如半宽图框改为标准A3图框),需要批量调整缩放比例

            • 几百上千张图纸,手动替换耗时数天

            2. 企业标准规范化改造

            • 公司统一更换图框模板(如LOGO更新、公司名称变更)

            • 图纸归档前统一替换为标准图框

            • 不同部门图纸合并时需要统一图框格式

            结语

            当Python遇见办公,牛马打工人终于笑出了猪叫声

            【职场人必看】每天早上一睁眼,想到又要面对:

            1.📊 堆积如山的Excel表格

            2.📑 机械重复的复制粘贴

            3.✍️ 永远改不完的各类文档

            4.诸如此类的更多........

            是不是连Ctrl+Alt+Delete的心都有了?

            别慌!别急,摸鱼这位“职场外挂”已经带着Python代码来拯救你了!

            感谢各位大佬观看,还望各位大佬抬抬贵手一键三连,多多关注点赞转发评论,大佬们的支持才是摸鱼孜孜不倦更新原创干货的动力!

            另外,本篇文章的exe已上传nas私有云盘,有需要的大佬私信摸鱼君获取

            最新文章

            随机文章