第12章 - 二次开发实战案例
12.1 案例一:DWG批量信息提取器
12.1.1 需求描述
开发一个工具,能够批量处理DWG文件并提取以下信息:
- 文件基本信息(版本、对象数量)
- 图层列表和实体统计
- 文本内容提取
- 生成汇总报告
12.1.2 C语言实现
/* dwg_extractor.c - DWG信息批量提取器 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <dwg.h>
// 报告结构
typedef struct {
char filename[256];
char version[32];
int object_count;
int layer_count;
int line_count;
int circle_count;
int text_count;
int error;
} FileReport;
// 处理单个DWG文件
int process_dwg(const char *filepath, FileReport *report)
{
Dwg_Data dwg;
int error;
strncpy(report->filename, filepath, sizeof(report->filename) - 1);
memset(&dwg, 0, sizeof(Dwg_Data));
error = dwg_read_file(filepath, &dwg);
if (error >= DWG_ERR_CRITICAL) {
report->error = error;
return -1;
}
// 基本信息
strncpy(report->version, dwg.header.version, sizeof(report->version) - 1);
report->object_count = dwg.num_objects;
// 统计各类型数量
report->layer_count = 0;
report->line_count = 0;
report->circle_count = 0;
report->text_count = 0;
for (BITCODE_BL i = 0; i < dwg.num_objects; i++) {
Dwg_Object *obj = &dwg.object[i];
switch (obj->fixedtype) {
case DWG_TYPE_LAYER:
report->layer_count++;
break;
case DWG_TYPE_LINE:
report->line_count++;
break;
case DWG_TYPE_CIRCLE:
report->circle_count++;
break;
case DWG_TYPE_TEXT:
case DWG_TYPE_MTEXT:
report->text_count++;
break;
}
}
report->error = 0;
dwg_free(&dwg);
return 0;
}
// 提取文本内容到文件
void extract_texts(const char *dwg_file, FILE *output)
{
Dwg_Data dwg;
memset(&dwg, 0, sizeof(Dwg_Data));
if (dwg_read_file(dwg_file, &dwg) >= DWG_ERR_CRITICAL) {
return;
}
fprintf(output, "=== %s ===\n", dwg_file);
for (BITCODE_BL i = 0; i < dwg.num_objects; i++) {
Dwg_Object *obj = &dwg.object[i];
if (obj->fixedtype == DWG_TYPE_TEXT) {
Dwg_Entity_TEXT *text = obj->tio.entity->tio.TEXT;
if (text->text_value && strlen(text->text_value) > 0) {
fprintf(output, "[TEXT] %s\n", text->text_value);
}
}
else if (obj->fixedtype == DWG_TYPE_MTEXT) {
Dwg_Entity_MTEXT *mtext = obj->tio.entity->tio.MTEXT;
if (mtext->text && strlen(mtext->text) > 0) {
fprintf(output, "[MTEXT] %s\n", mtext->text);
}
}
}
fprintf(output, "\n");
dwg_free(&dwg);
}
// 批量处理目录
void batch_process(const char *dir_path, const char *output_file)
{
DIR *dir;
struct dirent *entry;
char filepath[512];
FileReport *reports = NULL;
int report_count = 0;
int report_capacity = 100;
reports = malloc(report_capacity * sizeof(FileReport));
dir = opendir(dir_path);
if (!dir) {
fprintf(stderr, "无法打开目录: %s\n", dir_path);
free(reports);
return;
}
// 打开文本输出文件
FILE *text_output = fopen("extracted_texts.txt", "w");
while ((entry = readdir(dir)) != NULL) {
// 检查是否为DWG文件
size_t len = strlen(entry->d_name);
if (len < 4) continue;
const char *ext = entry->d_name + len - 4;
if (strcasecmp(ext, ".dwg") != 0) continue;
snprintf(filepath, sizeof(filepath), "%s/%s", dir_path, entry->d_name);
printf("处理: %s\n", entry->d_name);
// 扩展数组
if (report_count >= report_capacity) {
report_capacity *= 2;
reports = realloc(reports, report_capacity * sizeof(FileReport));
}
// 处理文件
process_dwg(filepath, &reports[report_count]);
// 提取文本
if (text_output && reports[report_count].error == 0) {
extract_texts(filepath, text_output);
}
report_count++;
}
closedir(dir);
if (text_output) fclose(text_output);
// 生成报告
FILE *report_file = fopen(output_file, "w");
if (report_file) {
fprintf(report_file, "DWG批量处理报告\n");
fprintf(report_file, "================\n\n");
fprintf(report_file, "%-30s %-10s %-8s %-8s %-8s %-8s %-8s\n",
"文件名", "版本", "对象数", "图层", "直线", "圆", "文字");
fprintf(report_file, "%s\n",
"--------------------------------------------------------------------------------");
int total_objects = 0, total_lines = 0, total_circles = 0, total_texts = 0;
int success_count = 0, fail_count = 0;
for (int i = 0; i < report_count; i++) {
FileReport *r = &reports[i];
if (r->error) {
fprintf(report_file, "%-30s 错误 (0x%x)\n", r->filename, r->error);
fail_count++;
} else {
fprintf(report_file, "%-30s %-10s %-8d %-8d %-8d %-8d %-8d\n",
r->filename, r->version, r->object_count,
r->layer_count, r->line_count, r->circle_count, r->text_count);
total_objects += r->object_count;
total_lines += r->line_count;
total_circles += r->circle_count;
total_texts += r->text_count;
success_count++;
}
}
fprintf(report_file, "\n统计:\n");
fprintf(report_file, " 处理文件数: %d (成功: %d, 失败: %d)\n",
report_count, success_count, fail_count);
fprintf(report_file, " 总对象数: %d\n", total_objects);
fprintf(report_file, " 总直线数: %d\n", total_lines);
fprintf(report_file, " 总圆数: %d\n", total_circles);
fprintf(report_file, " 总文字数: %d\n", total_texts);
fclose(report_file);
printf("\n报告已保存到: %s\n", output_file);
}
free(reports);
}
int main(int argc, char *argv[])
{
if (argc < 2) {
printf("用法: %s <DWG目录> [报告文件]\n", argv[0]);
return 1;
}
const char *output = argc > 2 ? argv[2] : "dwg_report.txt";
batch_process(argv[1], output);
return 0;
}
12.1.3 编译和使用
# 编译
gcc -o dwg_extractor dwg_extractor.c -lredwg
# 使用
./dwg_extractor ./drawings report.txt
12.2 案例二:图纸审核工具
12.2.1 需求描述
开发一个图纸审核工具,检查:
- 必需图层是否存在
- 文字高度是否符合规范
- 线型比例是否正确
- 尺寸标注样式是否统一
12.2.2 Python实现
#!/usr/bin/env python3
"""图纸审核工具"""
import libredwg
import sys
from dataclasses import dataclass, field
from typing import List, Dict, Optional
@dataclass
class AuditRule:
"""审核规则"""
name: str
description: str
check_function: callable
@dataclass
class AuditResult:
"""审核结果"""
passed: bool
rule_name: str
message: str
details: List[str] = field(default_factory=list)
class DWGAuditor:
"""DWG图纸审核器"""
# 必需图层
REQUIRED_LAYERS = ['0', 'DEFPOINTS']
# 推荐图层
RECOMMENDED_LAYERS = ['WALLS', 'DOORS', 'WINDOWS', 'TEXT', 'DIMENSIONS']
# 文字高度规范(毫米)
TEXT_HEIGHT_MIN = 2.5
TEXT_HEIGHT_MAX = 100.0
TEXT_HEIGHT_STANDARD = [2.5, 3.5, 5.0, 7.0, 10.0]
# 线型比例范围
LTYPE_SCALE_MIN = 0.1
LTYPE_SCALE_MAX = 100.0
def __init__(self, filename: str):
self.filename = filename
self.dwg = None
self.results: List[AuditResult] = []
def load(self) -> bool:
"""加载DWG文件"""
self.dwg = libredwg.Dwg_Data()
error = libredwg.dwg_read_file(self.filename, self.dwg)
if error >= libredwg.DWG_ERR_CRITICAL:
self.results.append(AuditResult(
passed=False,
rule_name="文件加载",
message=f"无法加载文件,错误码: {error}"
))
return False
return True
def audit(self) -> List[AuditResult]:
"""执行审核"""
if not self.dwg:
if not self.load():
return self.results
# 执行各项检查
self.check_required_layers()
self.check_recommended_layers()
self.check_text_heights()
self.check_ltype_scales()
self.check_dimension_styles()
self.check_zero_length_entities()
self.check_duplicate_entities()
return self.results
def check_required_layers(self):
"""检查必需图层"""
existing_layers = set()
for i in range(self.dwg.num_objects):
obj = self.dwg.object[i]
if obj.fixedtype == libredwg.DWG_TYPE_LAYER:
layer = obj.tio.object.tio.LAYER
if layer.name:
existing_layers.add(layer.name.upper())
missing = []
for layer in self.REQUIRED_LAYERS:
if layer.upper() not in existing_layers:
missing.append(layer)
if missing:
self.results.append(AuditResult(
passed=False,
rule_name="必需图层",
message=f"缺少必需图层: {', '.join(missing)}"
))
else:
self.results.append(AuditResult(
passed=True,
rule_name="必需图层",
message="所有必需图层都存在"
))
def check_recommended_layers(self):
"""检查推荐图层"""
existing_layers = set()
for i in range(self.dwg.num_objects):
obj = self.dwg.object[i]
if obj.fixedtype == libredwg.DWG_TYPE_LAYER:
layer = obj.tio.object.tio.LAYER
if layer.name:
existing_layers.add(layer.name.upper())
missing = []
for layer in self.RECOMMENDED_LAYERS:
if layer.upper() not in existing_layers:
missing.append(layer)
if missing:
self.results.append(AuditResult(
passed=True, # 推荐图层不是强制的
rule_name="推荐图层",
message=f"建议添加图层: {', '.join(missing)}",
details=missing
))
else:
self.results.append(AuditResult(
passed=True,
rule_name="推荐图层",
message="所有推荐图层都存在"
))
def check_text_heights(self):
"""检查文字高度"""
invalid_heights = []
non_standard_heights = []
for i in range(self.dwg.num_objects):
obj = self.dwg.object[i]
height = None
handle = f"0x{obj.handle.value:x}"
if obj.fixedtype == libredwg.DWG_TYPE_TEXT:
height = obj.tio.entity.tio.TEXT.height
elif obj.fixedtype == libredwg.DWG_TYPE_MTEXT:
# MTEXT的文字高度需要从样式获取
pass
if height is not None:
if height < self.TEXT_HEIGHT_MIN or height > self.TEXT_HEIGHT_MAX:
invalid_heights.append((handle, height))
elif height not in self.TEXT_HEIGHT_STANDARD:
non_standard_heights.append((handle, height))
if invalid_heights:
details = [f"{h}: {ht:.2f}" for h, ht in invalid_heights[:10]]
self.results.append(AuditResult(
passed=False,
rule_name="文字高度",
message=f"发现 {len(invalid_heights)} 个文字高度超出范围",
details=details
))
else:
self.results.append(AuditResult(
passed=True,
rule_name="文字高度",
message="所有文字高度在合理范围内"
))
if non_standard_heights:
self.results.append(AuditResult(
passed=True,
rule_name="文字高度规范",
message=f"发现 {len(non_standard_heights)} 个非标准文字高度"
))
def check_ltype_scales(self):
"""检查线型比例"""
invalid_scales = []
for i in range(self.dwg.num_objects):
obj = self.dwg.object[i]
if obj.supertype != libredwg.DWG_SUPERTYPE_ENTITY:
continue
scale = obj.tio.entity.ltype_scale
handle = f"0x{obj.handle.value:x}"
if scale < self.LTYPE_SCALE_MIN or scale > self.LTYPE_SCALE_MAX:
invalid_scales.append((handle, scale))
if invalid_scales:
details = [f"{h}: {s:.2f}" for h, s in invalid_scales[:10]]
self.results.append(AuditResult(
passed=False,
rule_name="线型比例",
message=f"发现 {len(invalid_scales)} 个线型比例异常",
details=details
))
else:
self.results.append(AuditResult(
passed=True,
rule_name="线型比例",
message="所有线型比例正常"
))
def check_dimension_styles(self):
"""检查尺寸样式"""
dimstyles = []
for i in range(self.dwg.num_objects):
obj = self.dwg.object[i]
if obj.fixedtype == libredwg.DWG_TYPE_DIMSTYLE:
dimstyle = obj.tio.object.tio.DIMSTYLE
if dimstyle.name:
dimstyles.append(dimstyle.name)
if len(dimstyles) > 1:
self.results.append(AuditResult(
passed=True,
rule_name="尺寸样式",
message=f"存在 {len(dimstyles)} 个尺寸样式: {', '.join(dimstyles)}",
details=dimstyles
))
else:
self.results.append(AuditResult(
passed=True,
rule_name="尺寸样式",
message="尺寸样式统一"
))
def check_zero_length_entities(self):
"""检查零长度实体"""
zero_length = []
for i in range(self.dwg.num_objects):
obj = self.dwg.object[i]
handle = f"0x{obj.handle.value:x}"
if obj.fixedtype == libredwg.DWG_TYPE_LINE:
line = obj.tio.entity.tio.LINE
dx = line.end.x - line.start.x
dy = line.end.y - line.start.y
dz = line.end.z - line.start.z
length = (dx*dx + dy*dy + dz*dz) ** 0.5
if length < 1e-10:
zero_length.append(handle)
elif obj.fixedtype == libredwg.DWG_TYPE_CIRCLE:
circle = obj.tio.entity.tio.CIRCLE
if circle.radius < 1e-10:
zero_length.append(handle)
if zero_length:
self.results.append(AuditResult(
passed=False,
rule_name="零长度实体",
message=f"发现 {len(zero_length)} 个零长度/零半径实体",
details=zero_length[:10]
))
else:
self.results.append(AuditResult(
passed=True,
rule_name="零长度实体",
message="无零长度实体"
))
def check_duplicate_entities(self):
"""检查重复实体(简化检查)"""
# 只检查完全重合的直线
lines = []
duplicates = []
for i in range(self.dwg.num_objects):
obj = self.dwg.object[i]
if obj.fixedtype == libredwg.DWG_TYPE_LINE:
line = obj.tio.entity.tio.LINE
key = (round(line.start.x, 4), round(line.start.y, 4),
round(line.end.x, 4), round(line.end.y, 4))
if key in lines:
duplicates.append(f"0x{obj.handle.value:x}")
else:
lines.append(key)
if duplicates:
self.results.append(AuditResult(
passed=False,
rule_name="重复实体",
message=f"发现 {len(duplicates)} 个重复直线",
details=duplicates[:10]
))
else:
self.results.append(AuditResult(
passed=True,
rule_name="重复实体",
message="无明显重复实体"
))
def generate_report(self) -> str:
"""生成审核报告"""
lines = []
lines.append("=" * 60)
lines.append("DWG图纸审核报告")
lines.append("=" * 60)
lines.append(f"文件: {self.filename}")
lines.append("")
passed_count = sum(1 for r in self.results if r.passed)
failed_count = len(self.results) - passed_count
lines.append(f"审核结果: 通过 {passed_count}, 不通过 {failed_count}")
lines.append("-" * 60)
for result in self.results:
status = "✓" if result.passed else "✗"
lines.append(f"{status} [{result.rule_name}] {result.message}")
for detail in result.details[:5]:
lines.append(f" - {detail}")
if len(result.details) > 5:
lines.append(f" ... 还有 {len(result.details) - 5} 项")
lines.append("-" * 60)
return "\n".join(lines)
def close(self):
"""释放资源"""
if self.dwg:
libredwg.dwg_free(self.dwg)
self.dwg = None
# 使用示例
if __name__ == '__main__':
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <dwg文件>")
sys.exit(1)
auditor = DWGAuditor(sys.argv[1])
auditor.audit()
report = auditor.generate_report()
print(report)
auditor.close()
12.3 案例三:DWG到HTML转换器
12.3.1 需求描述
开发一个将DWG转换为交互式HTML的工具,使用Canvas绘制图形。
12.3.2 实现
#!/usr/bin/env python3
"""DWG到HTML转换器"""
import libredwg
import sys
import math
import html
def get_color_hex(color_index):
"""ACI颜色索引转RGB"""
colors = {
1: "#FF0000", # 红
2: "#FFFF00", # 黄
3: "#00FF00", # 绿
4: "#00FFFF", # 青
5: "#0000FF", # 蓝
6: "#FF00FF", # 品红
7: "#FFFFFF", # 白
8: "#808080", # 灰
9: "#C0C0C0", # 亮灰
}
return colors.get(color_index, "#FFFFFF")
def dwg_to_html(dwg_file, html_file):
"""将DWG转换为HTML"""
dwg = libredwg.Dwg_Data()
error = libredwg.dwg_read_file(dwg_file, dwg)
if error >= libredwg.DWG_ERR_CRITICAL:
print(f"读取失败: {error}")
return
# 获取范围
min_x = dwg.header_vars.EXTMIN.x
min_y = dwg.header_vars.EXTMIN.y
max_x = dwg.header_vars.EXTMAX.x
max_y = dwg.header_vars.EXTMAX.y
width = max_x - min_x
height = max_y - min_y
# 计算缩放以适应画布
canvas_width = 800
canvas_height = 600
scale = min(canvas_width / width, canvas_height / height) * 0.9
# 生成HTML
html_content = f'''<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>DWG Viewer - {html.escape(dwg_file)}</title>
<style>
body {{
margin: 0;
background: #1a1a2e;
display: flex;
flex-direction: column;
align-items: center;
padding: 20px;
}}
h1 {{ color: white; }}
#canvas {{
background: #16213e;
border: 1px solid #0f3460;
}}
#info {{
color: #e94560;
margin: 10px;
}}
#controls {{
margin: 10px;
}}
button {{
background: #e94560;
color: white;
border: none;
padding: 10px 20px;
margin: 5px;
cursor: pointer;
}}
button:hover {{
background: #ff6b6b;
}}
</style>
</head>
<body>
<h1>DWG Viewer</h1>
<div id="info">
文件: {html.escape(dwg_file)} |
范围: ({min_x:.2f}, {min_y:.2f}) - ({max_x:.2f}, {max_y:.2f})
</div>
<div id="controls">
<button onclick="zoomIn()">放大</button>
<button onclick="zoomOut()">缩小</button>
<button onclick="resetView()">重置</button>
</div>
<canvas id="canvas" width="{canvas_width}" height="{canvas_height}"></canvas>
<script>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
let currentScale = {scale};
let offsetX = {-min_x * scale + 20};
let offsetY = {max_y * scale + 20};
const baseScale = {scale};
function transform(x, y) {{
return [
x * currentScale + offsetX,
-y * currentScale + offsetY
];
}}
function drawEntities() {{
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.lineWidth = 1;
'''
# 生成绘图代码
for i in range(dwg.num_objects):
obj = dwg.object[i]
if obj.supertype != libredwg.DWG_SUPERTYPE_ENTITY:
continue
ent = obj.tio.entity
color = get_color_hex(ent.color.index if ent.color.index != 256 else 7)
if obj.fixedtype == libredwg.DWG_TYPE_LINE:
line = ent.tio.LINE
html_content += f'''
ctx.strokeStyle = '{color}';
ctx.beginPath();
let [x1, y1] = transform({line.start.x}, {line.start.y});
let [x2, y2] = transform({line.end.x}, {line.end.y});
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
'''
elif obj.fixedtype == libredwg.DWG_TYPE_CIRCLE:
circle = ent.tio.CIRCLE
html_content += f'''
ctx.strokeStyle = '{color}';
ctx.beginPath();
let [cx, cy] = transform({circle.center.x}, {circle.center.y});
ctx.arc(cx, cy, {circle.radius} * currentScale, 0, 2 * Math.PI);
ctx.stroke();
'''
elif obj.fixedtype == libredwg.DWG_TYPE_ARC:
arc = ent.tio.ARC
html_content += f'''
ctx.strokeStyle = '{color}';
ctx.beginPath();
let [acx, acy] = transform({arc.center.x}, {arc.center.y});
ctx.arc(acx, acy, {arc.radius} * currentScale, -{arc.end_angle}, -{arc.start_angle});
ctx.stroke();
'''
elif obj.fixedtype == libredwg.DWG_TYPE_TEXT:
text = ent.tio.TEXT
if text.text_value:
escaped_text = html.escape(text.text_value).replace("'", "\\'")
html_content += f'''
ctx.fillStyle = '{color}';
ctx.font = Math.max(8, {text.height} * currentScale) + 'px Arial';
let [tx, ty] = transform({text.ins_pt.x}, {text.ins_pt.y});
ctx.fillText('{escaped_text}', tx, ty);
'''
html_content += '''
}
function zoomIn() {
currentScale *= 1.2;
drawEntities();
}
function zoomOut() {
currentScale /= 1.2;
drawEntities();
}
function resetView() {
currentScale = baseScale;
drawEntities();
}
// 鼠标拖拽
let isDragging = false;
let lastX, lastY;
canvas.addEventListener('mousedown', (e) => {
isDragging = true;
lastX = e.clientX;
lastY = e.clientY;
});
canvas.addEventListener('mousemove', (e) => {
if (isDragging) {
offsetX += e.clientX - lastX;
offsetY += e.clientY - lastY;
lastX = e.clientX;
lastY = e.clientY;
drawEntities();
}
});
canvas.addEventListener('mouseup', () => { isDragging = false; });
canvas.addEventListener('mouseleave', () => { isDragging = false; });
// 鼠标滚轮缩放
canvas.addEventListener('wheel', (e) => {
e.preventDefault();
if (e.deltaY < 0) {
currentScale *= 1.1;
} else {
currentScale /= 1.1;
}
drawEntities();
});
// 初始绘制
drawEntities();
</script>
</body>
</html>
'''
with open(html_file, 'w', encoding='utf-8') as f:
f.write(html_content)
print(f"已生成: {html_file}")
libredwg.dwg_free(dwg)
if __name__ == '__main__':
if len(sys.argv) < 2:
print(f"用法: {sys.argv[0]} <dwg文件> [输出html]")
sys.exit(1)
dwg_file = sys.argv[1]
html_file = sys.argv[2] if len(sys.argv) > 2 else dwg_file.replace('.dwg', '.html')
dwg_to_html(dwg_file, html_file)
12.4 本章小结
本章通过三个实战案例展示了LibreDWG的应用开发:
- 批量信息提取器:批量处理DWG文件,生成汇总报告
- 图纸审核工具:检查图纸规范性,生成审核报告
- DWG到HTML转换器:生成交互式Web预览
这些案例涵盖了LibreDWG的主要应用场景,可以作为实际项目开发的参考。
下一章预告:第13章 - 性能优化与最佳实践 - 学习如何优化LibreDWG应用的性能。