夜雨聆风学习资料网

ARTICLE · 1158685

实战|用 AI 写 CAD 插件:CAD 批量打印(附源码 + 提示词)

实战|用 AI 写 CAD 插件:CAD 批量打印(附源码 + 提示词)
点击蓝字,关注我们
这是《零基础 CAD 插件从 0 到 1》第 22 篇

痛点:出图的折磨────────────────────────────────────────────────────────────────────────────────────────────────────

今天给大家写一个批量打印的插件,我们做设计的,也好绘图的也好,难免遇到几十甚至上百张图的场景,打开图,输入打印命令,选打印机,选纸张,选打印样式,选打印范围,框选图框,预览一下,确认没问题,点确定,等它转圈,生成PDF,关闭打印对话框。然后打开第二张图,重复上面所有步骤。第三张,第四张,第五张……一百张图,很浪费时间,

如果你用的是布局空间,稍微好一点,可以批量发布。但大部分国内工程图纸都在模型空间,每张图一个图框,图纸和图纸之间没有关联,只能一张一张来。

插件效果:一键框选,直接打印────────────────────────────────────────────────────────────────────────────────────────────────────

先看效果。

以前你打开一张图、打印一张图、关闭一张图,循环一百次,现在你只需要做三步。第一步,打开批量打印工具。第二步,框选所有图框。第三步,点开始打印。

程序会自动识别你框选的每一个图框,不管是块、矩形还是四条直线拼的,都会自动提取出来。然后按你选的排序方式重新编号,在图纸上画上淡黄色的预览框和序号,让你一眼就能看出打印顺序对不对。确认没问题,点开始打印,程序就一张一张自动输出PDF,进度实时显示在界面上,打印完自动清理预览。

以前一下午的活儿,现在几分钟搞定。

打印方向可以选自动、横向、竖向,自动模式会按图框本身的宽高比智能判断,横的图框打横的,竖的图框打竖的。打印样式默认用 acad.ctb,你改过一次之后就记住你的选择了。文件名前缀默认用当前图纸的名字,比如你打开的是"办公楼一层平面图.dwg",输出的PDF就叫"办公楼一层平面图_001.pdf"、"办公楼一层平面图_002.pdf",一看就知道是哪个项目的。

这个插件到底解决了什么问题?────────────────────────────────────────────────────────────────────────────────────────────────────

从实际使用角度看,它解决了四个层次的问题。

1最表面的问题:重复劳动。

一百张图,一百次打印设置,一百次重复操作。这是纯粹的体力活儿,程序最适合干这种活。

2识别问题:图框是各式各样的。

有的人图框是块,有的人图框是矩形,有的人图框是四条直线拼的。一个通用的批量打印工具,必须能识别所有这些情况。我们的方案是框选一批实体,然后从里面自动提取"图框单元"。块参照算一个,闭合矩形算一个,端点相连的直线组算一个。这样不管你图框怎么画的,框选一下都能识别出来。

3排序问题:打印顺序得对。

工程图纸的打印顺序是有讲究的。一般要求从左到右、从上到下。但如果你框选的时候是从右往左框的,程序按框选顺序编号,那就反了。所以插件提供了八种排序方式:按选择顺序、从左到右、从右到左、从上到下、从下到上、网格逐行从左、网格逐行从右、蛇形左右交替。切换排序方式的时候,CAD里画的预览序号会立即跟着变,一眼就能看出来对不对。

4方向问题:横图竖图得分开处理。

同一个DWG里可能既有横向的A0图框,也有竖向的A0图框。如果统一按一个方向打印,要么横向的图被打成竖的(内容缩得很小),要么竖向的图被打成横的(内容被切掉)。我们的自动模式会逐张判断每个图框的宽高比,横的打横的,竖的打竖的,完全不用你操心。

交互方式:一个命令搞定────────────────────────────────────────────────────────────────────────────────────────────────────

加载好插件之后,只需要记住一个命令:ABPT。

在CAD命令行输入这个命令,回车,弹出工具窗口。窗口分四个区块。

1图框识别区:

可以选择按图层识别、按块名识别、自动识别,或者直接用框选识别。框选识别是推荐方式,点一下按钮,工具窗口自动隐藏,CAD获得焦点,你框选一批实体,回车确认,工具窗口自动回到最前面。

2识别结果区:

显示所有识别到的图框,每行一个,包含序号、来源类型、尺寸、左下角坐标、右上角坐标。左侧有复选框,可以单独取消某几张不打印。上方有排序下拉框,切换排序方式会立即重排列表并刷新CAD预览。

3输出设置区:

输出目录、打印设备、图纸尺寸、打印样式、方向、文件名前缀、偏移,全部可视化设置。设置会自动保存,下次打开时恢复上次的选择(方向和前缀除外,每次都重置为默认值)。

4底部日志区:

实时显示打印进度,每张图完成都显示文件名和耗时,最后汇总总耗时和整体速率。

没有复杂配置,没有学习成本,一个命令搞定。

核心干货:给AI的提示词────────────────────────────────────────────────────────────────────────────────────────────────────

下面是我实际用的提示词。

1立项目标

我跟AI说,我想做一个CAD批量打印工具,用C#和.NET  4.8。需求有六条。

  1. 能框选一批实体,自动识别出里面的图框。图框可能是块参照,可能是闭合矩形多段线,可能是四条端点相连的直线。

  2. 识别出来的图框要能排序,支持从左到右、从上到下这些常见方式。
  3. 在CAD图形区绘制预览,淡黄色矩形包围盒加序号,让我一眼看出打印顺序。
  4. 支持批量输出PDF,每张图一个文件,文件名用图纸名加序号。
  5. 打印方向可以选自动、横向、竖向。
  6. 界面用WPF,不要太老土的样式。

最后加一句:请给我完整的项目结构和代码,并解释一下思路。

2解决识别问题

第一版做出来之后,我发现框选识别的时候,块参照识别得挺好,但矩形和直线组识别不准。有的矩形被识别成好几个图框,有的直线组干脆识别不到。

于是我跟AI说:图框识别不够准。闭合矩形应该算一个图框,不应该拆成四条边。直线组要按端点连通性分组,一组算一个图框。另外,识别出来的图框要过滤掉被包含的小图框,只保留最外层的大图框。

3加排序和预览

识别准了之后,我又跟AI说:加一个排序功能,支持八种排序方式,切换的时候CAD预览要立即更新。排序用图框的包围盒坐标算,从左到右按左下角X升序,从上到下按左下角Y降序,网格模式先按Y分行,行内按X排序。

4加打印方向

打印能跑通之后,我发现横竖图框混在一起的时候方向不对。于是跟AI说:加打印方向选项,自动模式下要按图框的宽高比逐张判断旋转角度。横向图框输出横向PDF,竖向图框输出竖向PDF。

5优化性能和体验

最后做优化。框选的时候窗口要自动隐藏,框选完自动恢复并置顶。打印的时候不要弹进度条,要静默输出。每一步要记录耗时,方便排查慢在哪里。

写提示词的核心原则────────────────────────────────────────────────────────────────────────────────────────────────────

回头看,这次成功的关键,还是把提示词写成了需求说明书,而不是一句"帮我写个批量打印插件"。

总结出几条原则,你写任何AI辅助开发都能用。

1明确技术方向,不留余地。

错误写法是"做个CAD插件"。正确写法是"用C#语言,.NET  4.8版本,做一个CAD插件"。说清楚版本和环境,AI才不会给你一个跑不了的方案。

2把业务规则讲成判断条件。

错误写法是"让图框智能识别"。正确写法是"闭合多段线算一个图框,端点相连的直线组算一个图框,块参照算一个图框,被包含的图框要过滤掉"。说清楚什么情况下该做什么,AI才知道怎么判断。

3一次一个目标,别贪多。

错误写法是"做一个工具,能识别图框,能排序,能预览,能打印,能合并PDF,能加水印"。正确写法是"先做图框识别和排序,打印后面再说"。分批交付,每轮都验证,比一次性给一大堆更容易成功。

4反馈要具体到报错信息。

错误写法是"不行,有问题"。正确写法是"编译时报错,说找不到某个方法,文件是某某文件第几行,完整错误码是某某"。把错误码、文件、行号一起给,AI定位问题才快。

5让AI自我解释。

在你觉得方案有疑问时,加一句:请先解释你的方案思路,我确认后再给代码。这一句能省下大量返工时间。

核心源码分享────────────────────────────────────────────────────────────────────────────────────────────────────

这是一部分代码,完整代码可以领取,见文末

CSHARP
✏️ 编辑

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

213

214

215

216

217

218

219

220

221

222

223

224

225

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

241

242

243

244

245

246

247

248

249

250

251

252

253

254

255

256

257

258

259

260

261

262

263

264

265

266

267

268

269

270

271

272

273

274

275

276

277

278

279

280

281

282

283

284

285

286

287

288

289

290

291

292

293

294

295

296

297

298

299

300

301

302

303

304

305

306

307

308

309

310

311

312

313

314

315

316

317

318

319

320

321

322

323

324

325

326

327

328

329

330

331

332

333

334

335

336

337

338

339

340

341

342

343

344

345

346

347

348

349

350

351

352

353

354

355

356

357

358

359

360

361

362

363

364

365

366

367

368

369

370

371

372

373

374

375

376

377

378

379

380

381

382

383

384

385

386

387

388

389

390

391

392

393

394

395

396

397

398

399

400

401

402

403

404

405

406

407

408

409

410

411

412

413

414

415

416

417

418

419

420

421

422

423

424

425

426

427

428

429

430

431

432

433

434

435

436

437

438

439

440

441

442

443

444

445

446

447

448

449

450

451

452

453

454

455

456

457

458

459

460

461

462

463

464

465

466

467

468

469

470

471

472

473

474

475

476

477

478

479

480

481

482

483

484

485

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

using Autodesk.AutoCAD.ApplicationServices;

using Autodesk.AutoCAD.DatabaseServices;

using Autodesk.AutoCAD.PlottingServices;

using CadBatchPlot.Models;

using System;

using System.Collections.Generic;

using System.Diagnostics;

using System.IO;

namespaceCadBatchPlot.Services

{

///<summary>

/// 批量打印服务

/// 方向选项:横向/竖向/自动

///   横向 = 全部输出横向 PDF

///   竖向 = 全部输出竖向 PDF

///   自动 = 按图框宽高比,横的对横,竖的对竖

///</summary>

internalstaticclassPlotService

    {

publicstatic List<string> GetDevices()

        {

var list = new List<string>();

try

            {

var psv = PlotSettingsValidator.Current;

foreach (string s in psv.GetPlotDeviceList())

                    list.Add(s);

            }

catch (Exception ex)

            {

                PlotLogger.Error("获取打印设备列表失败", ex);

            }

            list.Sort();

            PlotLogger.Info($"获取打印设备列表完成,共 {list.Count} 个设备");

return list;

        }

publicstatic List<PaperSizeItem> GetPaperSizes(string deviceName, Database db)

        {

var list = new List<PaperSizeItem>();

            PlotLogger.Separator($"获取纸张列表: {deviceName}");

try

            {

using (var tr = db.TransactionManager.StartTransaction())

                {

var modelLayoutId = GetModelLayoutId(db, tr);

if (modelLayoutId.IsNull)

                    {

                        PlotLogger.Error("找不到模型空间布局");

return list;

                    }

var layout = (Layout)tr.GetObject(modelLayoutId, OpenMode.ForRead);

var ps = new PlotSettings(layout.ModelType);

                    ps.CopyFrom(layout);

var psv = PlotSettingsValidator.Current;

                    psv.SetPlotConfigurationName(ps, deviceName, null);

try { psv.RefreshLists(ps); } catch { }

var mediaList = psv.GetCanonicalMediaNameList(ps);

                    PlotLogger.Info($"设备返回了 {mediaList.Count} 个纸张尺寸");

for (int i = 0; i < mediaList.Count; i++)

                    {

string canon = mediaList[i];

if (!IsEngineeringPaper(canon)) continue;

string display = "";

try { display = psv.GetLocaleMediaName(ps, i); } catch { }

if (string.IsNullOrWhiteSpace(display)) display = canon;

                        list.Add(new PaperSizeItem

                        {

                            Canonical = canon,

                            Display = display

                        });

                    }

                    PlotLogger.Info($"过滤后保留 {list.Count} 个工程图纸尺寸");

                    tr.Commit();

                }

            }

catch (Exception ex)

            {

                PlotLogger.Error($"获取纸张列表失败 (设备: {deviceName})", ex);

            }

            PlotLogger.Separator();

return list;

        }

publicstatic List<string> GetPlotStyles()

        {

var result = new List<string>();

var dirs = new List<string>();

            PlotLogger.Separator("扫描打印样式文件");

string[] roots =

            {

                Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),

                Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles),

                Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86),

                Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),

            };

foreach (var root in roots)

            {

if (string.IsNullOrEmpty(root) || !Directory.Exists(root)) continue;

try

                {

foreach (var autodesk in Directory.GetDirectories(root, "Autodesk"))

                    {

try

                        {

foreach (var ps in Directory.GetDirectories(

                                autodesk, "Plot Styles", SearchOption.AllDirectories))

                            {

                                dirs.Add(ps);

                            }

                        }

catch { }

                    }

                }

catch { }

            }

var seenDirs = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

foreach (var d in dirs)

            {

if (!seenDirs.Add(d)) continue;

try

                {

foreach (var f in Directory.GetFiles(d))

                    {

string ext = Path.GetExtension(f).ToLowerInvariant();

if (ext == ".ctb" || ext == ".stb")

                            result.Add(Path.GetFileName(f));

                    }

                }

catch { }

            }

var unique = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

var final = new List<string>();

foreach (var s in result)

if (unique.Add(s)) final.Add(s);

            final.Sort(StringComparer.OrdinalIgnoreCase);

if (final.Count == 0)

            {

                final.Add("monochrome.ctb");

                final.Add("acad.ctb");

            }

            PlotLogger.Info($"找到 {final.Count} 个打印样式文件");

            PlotLogger.Separator();

return final;

        }

publicstaticintPlotFramesToPdf(

            Database db,

            List<FrameInfo> frames,

            BatchPlotSettings settings,

string outputFolder,

            Action<string> logAction)

        {

if (frames == null || frames.Count == 0) return0;

var totalSw = Stopwatch.StartNew();

string dirText = settings.Orientation == "Landscape" ? "横向"

                           : settings.Orientation == "Portrait" ? "竖向"

                           : "自动(按图框方向)";

            PlotLogger.Separator("打印参数");

            PlotLogger.Info($"图框数量  : {frames.Count}");

            PlotLogger.Info($"输出目录  : {outputFolder}");

            PlotLogger.Info($"打印设备  : {settings.DeviceName}");

            PlotLogger.Info($"图纸尺寸  : {settings.PaperSize}");

            PlotLogger.Info($"方向选择  : {settings.Orientation} ({dirText})");

            PlotLogger.Info($"打印样式  : {settings.PlotStyleFile}");

            PlotLogger.Separator();

            logAction?.Invoke($"开始打印 {frames.Count} 张,请稍候...");

short originalBgPlot = 0;

try

            {

                originalBgPlot = (short)Application.GetSystemVariable("BACKGROUNDPLOT");

                Application.SetSystemVariable("BACKGROUNDPLOT", 0);

                PlotLogger.Info($"BACKGROUNDPLOT 由 {originalBgPlot} 临时改为 0");

            }

catch (Exception ex)

            {

                PlotLogger.Warn($"设置 BACKGROUNDPLOT 失败: {ex.Message}");

            }

if (PlotFactory.ProcessPlotState != ProcessPlotState.NotPlotting)

            {

                PlotLogger.Warn("已有打印任务在进行");

                logAction?.Invoke("已有打印任务在进行,请等待完成后再试。");

return0;

            }

int successCount = 0;

int failCount = 0;

var perFrameTimes = new List<double>();

try

            {

// ============================================================

// 阶段一:设备配置(只做一次)

// ============================================================

var configTimer = new StepTimer("  [配置] ");

                PlotLogger.Info("===== 阶段一:设备配置 =====");

                PlotInfo pi = null;

                PlotSettings ps = null;

                PlotSettingsValidator psv = null;

using (var tr = db.TransactionManager.StartTransaction())

                {

var modelLayoutId = GetModelLayoutId(db, tr);

if (modelLayoutId.IsNull)

                    {

                        PlotLogger.Error("找不到模型空间布局");

return0;

                    }

var layout = (Layout)tr.GetObject(modelLayoutId, OpenMode.ForRead);

                    pi = new PlotInfo();

                    pi.Layout = modelLayoutId;

                    configTimer.Mark("创建 PlotInfo");

                    ps = new PlotSettings(layout.ModelType);

                    ps.CopyFrom(layout);

                    configTimer.Mark("创建 PlotSettings");

                    psv = PlotSettingsValidator.Current;

                    psv.SetPlotConfigurationName(ps, settings.DeviceName, settings.PaperSize);

                    configTimer.Mark("SetPlotConfigurationName");

try { psv.RefreshLists(ps); } catch { }

                    configTimer.Mark("RefreshLists");

                    psv.SetUseStandardScale(ps, true);

                    psv.SetStdScaleType(ps, StdScaleType.ScaleToFit);

                    psv.SetPlotCentered(ps, true);

                    configTimer.Mark("比例与居中");

// 旋转不在这里设置,改到循环内根据图框动态设置

if (!string.IsNullOrWhiteSpace(settings.PlotStyleFile))

                    {

try

                        {

                            psv.SetCurrentStyleSheet(ps, settings.PlotStyleFile);

                            configTimer.Mark("打印样式");

                        }

catch (Exception ex)

                        {

                            PlotLogger.Warn($"  [配置] 打印样式设置失败: {ex.Message}");

                        }

                    }

                    tr.Commit();

                    configTimer.Mark("提交事务");

                }

                configTimer.Stop();

                PlotLogger.Info($"===== 阶段一完成,耗时 {configTimer.TotalMs:F1}ms =====");

// ============================================================

// 阶段二:逐张打印

// ============================================================

var engineTimer = new StepTimer("  [引擎] ");

                PlotLogger.Info("===== 阶段二:逐张打印 =====");

using (var engine = PlotFactory.CreatePublishEngine())

                {

                    engine.BeginPlot(null, null);

                    engineTimer.Mark("BeginPlot");

                    PlotLogger.Info("PlotEngine 启动成功");

for (int i = 0; i < frames.Count; i++)

                    {

var frame = frames[i];

int seq = i + 1;

string fileName = $"{settings.FileNamePrefix}_{seq:D3}.pdf";

string fullPath = Path.Combine(outputFolder, fileName);

// ⭐ 逐张计算旋转

var rotation = GetRotation(settings.Orientation, frame);

string frameDir = frame.Width >= frame.Height ? "横向" : "竖向";

string outDir = rotation == PlotRotation.Degrees090 ? "横向" : "竖向";

var frameTimer = new StepTimer("    ");

                        PlotLogger.Info($"--- 第 {seq}/{frames.Count} 张: {fileName} ---");

                        PlotLogger.Info($"    图框: {frame.Width:F0} x {frame.Height:F0} ({frameDir}) -> 输出 {outDir}");

try

                        {

                            PlotSingleFrame(db, frame, settings, fullPath, engine,

                                pi, ps, psv, rotation, frameTimer);

                            frameTimer.Stop();

double sec = frameTimer.TotalMs / 1000.0;

                            perFrameTimes.Add(sec);

                            successCount++;

                            PlotLogger.Info($"  ✔ {fileName} 完成,耗时 {frameTimer.TotalMs:F1}ms");

                            logAction?.Invoke($"  [OK] {fileName}  ({seq}/{frames.Count})  {frameTimer.TotalMs:F0}ms");

                        }

catch (Exception ex)

                        {

                            frameTimer.Stop();

                            failCount++;

                            PlotLogger.Error($"  ✘ {fileName} 失败,耗时 {frameTimer.TotalMs:F1}ms", ex);

                            logAction?.Invoke($"  [X] {fileName} 失败 ({frameTimer.TotalMs:F0}ms): {ex.Message}");

                        }

                    }

                    engine.EndPlot(null);

                    engineTimer.Mark("EndPlot");

                    PlotLogger.Info("PlotEngine 结束");

                }

            }

catch (Exception ex)

            {

                PlotLogger.Error("批量打印异常", ex);

                logAction?.Invoke($"打印异常: {ex.Message}");

            }

finally

            {

try

                {

                    Application.SetSystemVariable("BACKGROUNDPLOT", originalBgPlot);

                    PlotLogger.Info($"BACKGROUNDPLOT 已恢复为 {originalBgPlot}");

                }

catch { }

            }

            totalSw.Stop();

double totalSec = totalSw.Elapsed.TotalSeconds;

            PlotLogger.Separator("打印统计");

            PlotLogger.Info($"结束时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");

            PlotLogger.Info($"总耗时  : {FormatDuration(totalSec)}");

            PlotLogger.Info($"成功    : {successCount} 张,失败 {failCount} 张");

if (perFrameTimes.Count > 0)

            {

double sum = 0, min = double.MaxValue, max = 0;

foreach (var t in perFrameTimes)

                {

                    sum += t;

if (t < min) min = t;

if (t > max) max = t;

                }

double avg = sum / perFrameTimes.Count;

                PlotLogger.Info($"单张耗时: 最快 {min * 1000:F1}ms / 最慢 {max * 1000:F1}ms / 平均 {avg * 1000:F1}ms");

            }

if (totalSec > 0 && successCount > 0)

            {

                PlotLogger.Info($"整体速率: {successCount / totalSec:F2} 张/秒");

            }

            PlotLogger.Separator();

            logAction?.Invoke($"──────────────────────────────");

            logAction?.Invoke($"打印完成: 成功 {successCount} 张,失败 {failCount} 张");

            logAction?.Invoke($"总耗时: {FormatDuration(totalSec)}");

return successCount;

        }

privatestaticvoidPlotSingleFrame(

            Database db,

            FrameInfo frame,

            BatchPlotSettings settings,

string outputPath,

            PlotEngine engine,

            PlotInfo pi,

            PlotSettings ps,

            PlotSettingsValidator psv,

            PlotRotation rotation,

            StepTimer timer)

        {

using (var tr = db.TransactionManager.StartTransaction())

            {

                timer.Mark("开始事务");

var ext = new Extents2d(

                    frame.MinX - settings.Offset,

                    frame.MinY - settings.Offset,

                    frame.MaxX + settings.Offset,

                    frame.MaxY + settings.Offset);

                psv.SetPlotWindowArea(ps, ext);

                timer.Mark("SetPlotWindowArea");

// ⭐ 逐张设置旋转

                psv.SetPlotRotation(ps, rotation);

                timer.Mark($"SetPlotRotation({rotation})");

                psv.SetPlotType(ps, Autodesk.AutoCAD.DatabaseServices.PlotType.Window);

                timer.Mark("SetPlotType(Window)");

                pi.OverrideSettings = ps;

                timer.Mark("OverrideSettings");

var piv = new PlotInfoValidator();

                piv.MediaMatchingPolicy = MatchingPolicy.MatchEnabled;

                piv.Validate(pi);

                timer.Mark("Validate");

                engine.BeginDocument(pi, db.Filename, null, 1, true, outputPath);

                timer.Mark("BeginDocument");

var ppi = new PlotPageInfo();

                engine.BeginPage(ppi, pi, true, null);

                timer.Mark("BeginPage");

                engine.BeginGenerateGraphics(null);

                timer.Mark("BeginGenerateGraphics");

                engine.EndGenerateGraphics(null);

                timer.Mark("EndGenerateGraphics");

                engine.EndPage(null);

                timer.Mark("EndPage");

                engine.EndDocument(null);

                timer.Mark("EndDocument");

                tr.Commit();

                timer.Mark("提交事务");

if (!File.Exists(outputPath))

                {

thrownew InvalidOperationException($"输出文件未生成: {outputPath}");

                }

            }

        }

///<summary>

/// 根据方向选项和图框宽高比计算旋转角度

/// 纸张默认是竖向的(例如 A0 = 841x1189)

/// Degrees000 = 竖向输出

/// Degrees090 = 横向输出

///</summary>

privatestatic PlotRotation GetRotation(string orientation, FrameInfo frame)

        {

if (orientation == "Landscape")

            {

// 强制横向:所有图框都旋转 90 度

return PlotRotation.Degrees090;

            }

if (orientation == "Portrait")

            {

// 强制竖向:所有图框都不旋转

return PlotRotation.Degrees000;

            }

// 自动:按图框宽高比

// 图框横向 -> Degrees090(横向输出)

// 图框竖向 -> Degrees000(竖向输出)

bool frameIsLandscape = frame.Width >= frame.Height;

return frameIsLandscape ? PlotRotation.Degrees090 : PlotRotation.Degrees000;

        }

privatestaticstringFormatDuration(double totalSeconds)

        {

if (totalSeconds < 60)

return$"{totalSeconds:F3} 秒";

var ts = TimeSpan.FromSeconds(totalSeconds);

if (ts.TotalHours >= 1)

return$"{(int)ts.TotalHours} 小时 {ts.Minutes} 分 {ts.Seconds} 秒";

else

return$"{ts.Minutes} 分 {ts.Seconds} 秒";

        }

privatestatic ObjectId GetModelLayoutId(Database db, Transaction tr)

        {

var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);

foreach (ObjectId id in bt)

            {

var btr = (BlockTableRecord)tr.GetObject(id, OpenMode.ForRead);

if (btr.IsLayout)

                {

var layout = (Layout)tr.GetObject(btr.LayoutId, OpenMode.ForRead);

if (layout.ModelType) return layout.ObjectId;

                }

            }

return ObjectId.Null;

        }

privatestaticboolIsEngineeringPaper(string canonical)

        {

if (string.IsNullOrEmpty(canonical)) returnfalse;

string s = canonical.ToUpperInvariant();

string[] excludeKeywords =

            {

"PHOTO", "ENVELOPE", "BUSINESS", "CARD", "POSTCARD",

"CREDIT", "JAPANESE", "KAKU", "CHOU", "YOU #",

"CHOU #", "MONARCH", "STATEMENT", "EXECUTIVE",

            };

foreach (var kw in excludeKeywords)

            {

if (s.Contains(kw)) returnfalse;

            }

returntrue;

        }

    }

}

AI开发插件的完整流程────────────────────────────────────────────────────────────────────────────────────────────────────

把这次的实战经验,抽象成可复用的流程。

  1. 明确需求。用一句话概括"我想让CAD做某事",然后拆解成核心功能、附加功能、交互方式。
  2. 编写提示词。包含技术方向、业务规则、禁用项、分批目标。
  3. 第一轮生成。让AI给完整项目结构和代码。
  4. 编译测试。遇到报错,把错误内容完整发给AI。
  5. 加调试日志。让AI加日志系统,看看到底哪一步没生效。
  6. 优化体验。最后再打磨界面、预览、性能这些细节。

成品领取 & 下期预告────────────────────────────────────────────────────────────────────────────────────────────────────

不想自己编译、调代码的朋友,关注公众号 「下班让AI教我做插件」,后台回复 「CAD批量打印」,直接领取编译好的 DLL 插件文件,NETLOAD 加载或者是使用我写的dll插件加载工具箱。 完整工程源码拿到手就能二次修改。

下期预告

下一篇咱们继续图纸提效工具,如果有想要的插件可以在评论区留言,我看看哪个留言最多就做哪个。

我是做插件的设计师,专注用 AI 提高 CAD 设计效率。关注我,下班一起做插件,把重复工作交给程序。

声明本插件完全免费分享,仅供交流学习使用。严禁任何人将其用于商业倒卖或牟利。如需转载,请注明出处。请大家尊重作者的劳动成果,共同维护良好的交流环境。

相关学习资料