第09章 - 格式转换实战

第09章 - 格式转换实战

9.1 格式转换概述

9.1.1 支持的格式

LibreDWG支持多种格式之间的转换:

源格式 目标格式 支持状态
DWG DXF (ASCII) ✓ 完全支持
DWG DXF (Binary) ✓ 完全支持
DWG JSON ✓ 完全支持
DWG GeoJSON ✓ 支持
DWG SVG ⚠ 部分支持
DWG PS ⚠ 部分支持
DXF DWG ✓ 支持
JSON DWG ✓ 支持

9.1.2 转换工具

dwgread    # 通用读取器,支持多种输出格式
dwg2dxf    # DWG到DXF专用转换器
dxf2dwg    # DXF到DWG转换器
dwgwrite   # 从DXF/JSON创建DWG
dwg2SVG    # DWG到SVG转换器
dwg2ps     # DWG到PostScript转换器

9.2 DWG与DXF转换

9.2.1 DWG转DXF

基本转换:

# 转换为ASCII DXF
dwg2dxf drawing.dwg

# 指定输出文件
dwg2dxf drawing.dwg -o output.dxf

# 转换为二进制DXF
dwg2dxf -b drawing.dwg -o output.dxf

# 最小化输出(去除冗余数据)
dwg2dxf -m drawing.dwg -o minimal.dxf

# 指定输出版本
dwg2dxf -r r14 drawing.dwg -o r14_output.dxf

批量转换脚本:

#!/bin/bash
# batch_dwg2dxf.sh - 批量DWG转DXF

INPUT_DIR="${1:-.}"
OUTPUT_DIR="${2:-./dxf_output}"

mkdir -p "$OUTPUT_DIR"

for dwg in "$INPUT_DIR"/*.dwg; do
    if [ -f "$dwg" ]; then
        filename=$(basename "$dwg" .dwg)
        echo "转换: $dwg -> $OUTPUT_DIR/${filename}.dxf"
        dwg2dxf "$dwg" -o "$OUTPUT_DIR/${filename}.dxf"
    fi
done

echo "批量转换完成"

9.2.2 DXF转DWG

基本转换:

# 转换为DWG
dxf2dwg drawing.dxf

# 指定输出文件
dxf2dwg drawing.dxf -o output.dwg

# 指定DWG版本
dxf2dwg -r r2000 drawing.dxf -o r2000.dwg

支持二进制DXF:

# 二进制DXF也可以直接转换
dxf2dwg binary.dxf -o output.dwg

9.2.3 编程方式转换

#include <stdio.h>
#include <string.h>
#include <dwg.h>

// DWG转DXF
int convert_dwg_to_dxf(const char *dwg_file, const char *dxf_file)
{
    Dwg_Data dwg;
    int error;
    
    // 读取DWG
    memset(&dwg, 0, sizeof(Dwg_Data));
    error = dwg_read_file(dwg_file, &dwg);
    
    if (error >= DWG_ERR_CRITICAL) {
        fprintf(stderr, "读取DWG失败: %d\n", error);
        return -1;
    }
    
    // 写入DXF
    error = dxf_write_file(dxf_file, &dwg);
    
    dwg_free(&dwg);
    
    if (error) {
        fprintf(stderr, "写入DXF失败: %d\n", error);
        return -1;
    }
    
    printf("转换成功: %s -> %s\n", dwg_file, dxf_file);
    return 0;
}

// DXF转DWG
int convert_dxf_to_dwg(const char *dxf_file, const char *dwg_file)
{
    Dwg_Data dwg;
    int error;
    
    // 读取DXF
    memset(&dwg, 0, sizeof(Dwg_Data));
    error = dxf_read_file(dxf_file, &dwg);
    
    if (error >= DWG_ERR_CRITICAL) {
        fprintf(stderr, "读取DXF失败: %d\n", error);
        return -1;
    }
    
    // 设置输出版本
    dwg.header.version = R_2000;
    
    // 写入DWG
    error = dwg_write_file(dwg_file, &dwg);
    
    dwg_free(&dwg);
    
    if (error) {
        fprintf(stderr, "写入DWG失败: %d\n", error);
        return -1;
    }
    
    printf("转换成功: %s -> %s\n", dxf_file, dwg_file);
    return 0;
}

9.3 DWG与JSON转换

9.3.1 DWG转JSON

命令行转换:

# 完整JSON输出
dwgread -o json drawing.dwg > drawing.json

# 格式化JSON(配合jq)
dwgread -o json drawing.dwg | jq '.' > formatted.json

# 提取特定内容
dwgread -o json drawing.dwg | jq '.objects[] | select(.type == "LINE")' > lines.json

JSON结构示例:

{
  "header": {
    "version": "AC1015",
    "codepage": 30
  },
  "header_vars": {
    "ACADVER": "AC1015",
    "INSUNITS": 4,
    "EXTMIN": {"x": 0.0, "y": 0.0, "z": 0.0},
    "EXTMAX": {"x": 100.0, "y": 100.0, "z": 0.0}
  },
  "classes": [],
  "objects": [
    {
      "type": "LINE",
      "handle": "1F",
      "layer": "0",
      "color": 256,
      "start": {"x": 0.0, "y": 0.0, "z": 0.0},
      "end": {"x": 100.0, "y": 0.0, "z": 0.0}
    },
    {
      "type": "CIRCLE",
      "handle": "20",
      "layer": "0",
      "center": {"x": 50.0, "y": 50.0, "z": 0.0},
      "radius": 25.0
    }
  ]
}

9.3.2 JSON转DWG

# 从JSON创建DWG
dwgwrite drawing.json -o drawing.dwg

# 指定版本
dwgwrite -r r2000 drawing.json -o drawing.dwg

9.3.3 JSON数据分析

# 统计实体类型
dwgread -o json drawing.dwg | jq '
  .objects 
  | group_by(.type) 
  | map({type: .[0].type, count: length}) 
  | sort_by(.count) 
  | reverse'

# 提取所有文本
dwgread -o json drawing.dwg | jq -r '
  .objects[] 
  | select(.type == "TEXT" or .type == "MTEXT") 
  | if .type == "TEXT" then .text_value else .text end'

# 提取图层列表
dwgread -o json drawing.dwg | jq -r '
  .objects[] 
  | select(.type == "LAYER") 
  | .name'

# 计算总线长
dwgread -o json drawing.dwg | jq '
  [.objects[] 
   | select(.type == "LINE") 
   | pow(.end.x - .start.x; 2) + pow(.end.y - .start.y; 2) 
   | sqrt] 
  | add'

9.3.4 编程转换

#include <stdio.h>
#include <dwg.h>

// DWG转JSON
int convert_dwg_to_json(const char *dwg_file, const char *json_file)
{
    Dwg_Data dwg;
    int error;
    
    memset(&dwg, 0, sizeof(Dwg_Data));
    error = dwg_read_file(dwg_file, &dwg);
    
    if (error >= DWG_ERR_CRITICAL) {
        return -1;
    }
    
    // 写入JSON
    error = dwg_write_json(json_file, &dwg);
    
    dwg_free(&dwg);
    return error ? -1 : 0;
}

// JSON转DWG
int convert_json_to_dwg(const char *json_file, const char *dwg_file)
{
    Dwg_Data dwg;
    int error;
    
    memset(&dwg, 0, sizeof(Dwg_Data));
    error = dwg_read_json(json_file, &dwg);
    
    if (error >= DWG_ERR_CRITICAL) {
        return -1;
    }
    
    dwg.header.version = R_2000;
    error = dwg_write_file(dwg_file, &dwg);
    
    dwg_free(&dwg);
    return error ? -1 : 0;
}

9.4 DWG转GeoJSON

9.4.1 GeoJSON概述

GeoJSON是一种基于JSON的地理空间数据交换格式,适用于:

  • GIS系统集成
  • Web地图显示
  • 空间数据分析

9.4.2 基本转换

# 转换为GeoJSON
dwgread -o geojson drawing.dwg > drawing.geojson

# 指定精度
# 编译时设置: ./configure --with-geojson-precision=6

GeoJSON输出示例:

{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "type": "LINE",
        "layer": "0",
        "color": 7
      },
      "geometry": {
        "type": "LineString",
        "coordinates": [
          [0.0, 0.0],
          [100.0, 0.0]
        ]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "type": "CIRCLE",
        "layer": "0",
        "radius": 25.0
      },
      "geometry": {
        "type": "Point",
        "coordinates": [50.0, 50.0]
      }
    }
  ]
}

9.4.3 GeoJSON后处理

# 验证GeoJSON
npm install -g @mapbox/geojsonhint
geojsonhint drawing.geojson

# 使用ogr2ogr转换
ogr2ogr -f "ESRI Shapefile" output.shp drawing.geojson

# 简化几何
npm install -g geojson-simplify
geojson-simplify drawing.geojson > simplified.geojson

9.4.4 坐标系统处理

DWG文件通常使用本地坐标系,转换为GeoJSON时可能需要坐标转换:

#!/usr/bin/env python3
"""DWG转GeoJSON带坐标转换"""

import json
from pyproj import Transformer

def transform_geojson(input_file, output_file, 
                     from_crs="EPSG:2436",   # 本地坐标系
                     to_crs="EPSG:4326"):    # WGS84
    
    transformer = Transformer.from_crs(from_crs, to_crs, always_xy=True)
    
    with open(input_file, 'r') as f:
        geojson = json.load(f)
    
    for feature in geojson.get('features', []):
        geom = feature.get('geometry', {})
        coords = geom.get('coordinates', [])
        
        if geom['type'] == 'Point':
            x, y = coords
            coords[:] = list(transformer.transform(x, y))
        elif geom['type'] == 'LineString':
            for i, (x, y) in enumerate(coords):
                coords[i] = list(transformer.transform(x, y))
        elif geom['type'] == 'Polygon':
            for ring in coords:
                for i, (x, y) in enumerate(ring):
                    ring[i] = list(transformer.transform(x, y))
    
    with open(output_file, 'w') as f:
        json.dump(geojson, f, indent=2)

if __name__ == '__main__':
    import sys
    if len(sys.argv) >= 3:
        transform_geojson(sys.argv[1], sys.argv[2])

9.5 DWG转SVG

9.5.1 基本转换

# 转换为SVG
dwg2SVG drawing.dwg > drawing.svg

# 指定输出文件
dwg2SVG drawing.dwg -o drawing.svg

9.5.2 SVG后处理

# 使用Inkscape打开和编辑
inkscape drawing.svg

# 使用svgo优化
npm install -g svgo
svgo drawing.svg -o optimized.svg

# 转换为PNG(使用Inkscape)
inkscape --export-type=png --export-filename=output.png drawing.svg

# 转换为PDF
inkscape --export-type=pdf --export-filename=output.pdf drawing.svg

9.5.3 自定义SVG生成

#include <stdio.h>
#include <dwg.h>
#include <math.h>

// 简单的SVG生成器
void write_svg_header(FILE *fp, double width, double height)
{
    fprintf(fp, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
    fprintf(fp, "<svg xmlns=\"http://www.w3.org/2000/svg\" ");
    fprintf(fp, "width=\"%.2f\" height=\"%.2f\" ", width, height);
    fprintf(fp, "viewBox=\"0 0 %.2f %.2f\">\n", width, height);
    fprintf(fp, "<g transform=\"scale(1,-1) translate(0,-%.2f)\">\n", height);
}

void write_svg_footer(FILE *fp)
{
    fprintf(fp, "</g>\n");
    fprintf(fp, "</svg>\n");
}

void write_svg_line(FILE *fp, double x1, double y1, double x2, double y2,
                    const char *color, double width)
{
    fprintf(fp, "<line x1=\"%.4f\" y1=\"%.4f\" x2=\"%.4f\" y2=\"%.4f\" ",
            x1, y1, x2, y2);
    fprintf(fp, "stroke=\"%s\" stroke-width=\"%.2f\" />\n", color, width);
}

void write_svg_circle(FILE *fp, double cx, double cy, double r,
                      const char *stroke, const char *fill, double width)
{
    fprintf(fp, "<circle cx=\"%.4f\" cy=\"%.4f\" r=\"%.4f\" ", cx, cy, r);
    fprintf(fp, "stroke=\"%s\" fill=\"%s\" stroke-width=\"%.2f\" />\n",
            stroke, fill, width);
}

void write_svg_text(FILE *fp, double x, double y, const char *text,
                    double size, const char *color)
{
    fprintf(fp, "<text x=\"%.4f\" y=\"%.4f\" ", x, y);
    fprintf(fp, "font-size=\"%.2f\" fill=\"%s\" ", size, color);
    fprintf(fp, "transform=\"scale(1,-1)\">%s</text>\n", text);
}

// 转换DWG到SVG
int dwg_to_svg(const char *dwg_file, const char *svg_file)
{
    Dwg_Data dwg;
    int error;
    
    memset(&dwg, 0, sizeof(Dwg_Data));
    error = dwg_read_file(dwg_file, &dwg);
    
    if (error >= DWG_ERR_CRITICAL) {
        return -1;
    }
    
    // 计算范围
    double min_x = dwg.header_vars.EXTMIN.x;
    double min_y = dwg.header_vars.EXTMIN.y;
    double max_x = dwg.header_vars.EXTMAX.x;
    double max_y = dwg.header_vars.EXTMAX.y;
    
    double width = max_x - min_x;
    double height = max_y - min_y;
    
    // 添加边距
    double margin = width * 0.05;
    width += 2 * margin;
    height += 2 * margin;
    
    FILE *fp = fopen(svg_file, "w");
    if (!fp) {
        dwg_free(&dwg);
        return -1;
    }
    
    write_svg_header(fp, width, height);
    
    // 遍历实体
    for (BITCODE_BL i = 0; i < dwg.num_objects; i++) {
        Dwg_Object *obj = &dwg.object[i];
        
        if (obj->supertype != DWG_SUPERTYPE_ENTITY) continue;
        
        switch (obj->fixedtype) {
            case DWG_TYPE_LINE: {
                Dwg_Entity_LINE *line = obj->tio.entity->tio.LINE;
                write_svg_line(fp,
                    line->start.x - min_x + margin,
                    line->start.y - min_y + margin,
                    line->end.x - min_x + margin,
                    line->end.y - min_y + margin,
                    "black", 0.5);
                break;
            }
            case DWG_TYPE_CIRCLE: {
                Dwg_Entity_CIRCLE *circle = obj->tio.entity->tio.CIRCLE;
                write_svg_circle(fp,
                    circle->center.x - min_x + margin,
                    circle->center.y - min_y + margin,
                    circle->radius,
                    "black", "none", 0.5);
                break;
            }
            // 其他类型...
        }
    }
    
    write_svg_footer(fp);
    fclose(fp);
    dwg_free(&dwg);
    
    return 0;
}

9.6 批量转换工具

9.6.1 通用批量转换脚本

#!/bin/bash
# batch_convert.sh - 通用批量转换脚本

usage() {
    echo "用法: $0 <输入目录> <输出格式> [输出目录]"
    echo "支持的输出格式: dxf, json, geojson, svg"
    echo ""
    echo "示例:"
    echo "  $0 ./dwg_files dxf ./dxf_output"
    echo "  $0 ./dwg_files json"
}

if [ $# -lt 2 ]; then
    usage
    exit 1
fi

INPUT_DIR="$1"
OUTPUT_FORMAT="$2"
OUTPUT_DIR="${3:-${INPUT_DIR}/${OUTPUT_FORMAT}_output}"

mkdir -p "$OUTPUT_DIR"

# 统计
total=0
success=0
failed=0

for dwg in "$INPUT_DIR"/*.dwg; do
    if [ ! -f "$dwg" ]; then
        continue
    fi
    
    total=$((total + 1))
    filename=$(basename "$dwg" .dwg)
    
    case "$OUTPUT_FORMAT" in
        dxf)
            output="$OUTPUT_DIR/${filename}.dxf"
            dwg2dxf "$dwg" -o "$output" 2>/dev/null
            ;;
        json)
            output="$OUTPUT_DIR/${filename}.json"
            dwgread -o json "$dwg" > "$output" 2>/dev/null
            ;;
        geojson)
            output="$OUTPUT_DIR/${filename}.geojson"
            dwgread -o geojson "$dwg" > "$output" 2>/dev/null
            ;;
        svg)
            output="$OUTPUT_DIR/${filename}.svg"
            dwg2SVG "$dwg" -o "$output" 2>/dev/null
            ;;
        *)
            echo "不支持的格式: $OUTPUT_FORMAT"
            exit 1
            ;;
    esac
    
    if [ $? -eq 0 ] && [ -f "$output" ] && [ -s "$output" ]; then
        echo "✓ $filename"
        success=$((success + 1))
    else
        echo "✗ $filename"
        failed=$((failed + 1))
    fi
done

echo ""
echo "转换完成"
echo "  总数: $total"
echo "  成功: $success"
echo "  失败: $failed"

9.6.2 Python批量转换

#!/usr/bin/env python3
"""批量DWG格式转换工具"""

import os
import sys
import subprocess
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed

def convert_file(dwg_path, output_dir, output_format):
    """转换单个文件"""
    filename = Path(dwg_path).stem
    
    if output_format == 'dxf':
        output_path = output_dir / f"{filename}.dxf"
        cmd = ['dwg2dxf', str(dwg_path), '-o', str(output_path)]
    elif output_format == 'json':
        output_path = output_dir / f"{filename}.json"
        cmd = ['dwgread', '-o', 'json', str(dwg_path)]
    elif output_format == 'geojson':
        output_path = output_dir / f"{filename}.geojson"
        cmd = ['dwgread', '-o', 'geojson', str(dwg_path)]
    elif output_format == 'svg':
        output_path = output_dir / f"{filename}.svg"
        cmd = ['dwg2SVG', str(dwg_path), '-o', str(output_path)]
    else:
        return False, f"不支持的格式: {output_format}"
    
    try:
        if output_format in ['json', 'geojson']:
            with open(output_path, 'w') as f:
                result = subprocess.run(cmd, stdout=f, stderr=subprocess.PIPE,
                                       timeout=60)
        else:
            result = subprocess.run(cmd, capture_output=True, timeout=60)
        
        if result.returncode == 0 and output_path.exists() and \
           output_path.stat().st_size > 0:
            return True, str(output_path)
        else:
            return False, result.stderr.decode() if result.stderr else "未知错误"
    
    except subprocess.TimeoutExpired:
        return False, "超时"
    except Exception as e:
        return False, str(e)

def batch_convert(input_dir, output_format, output_dir=None, max_workers=4):
    """批量转换"""
    input_path = Path(input_dir)
    if not input_path.exists():
        print(f"输入目录不存在: {input_dir}")
        return
    
    if output_dir is None:
        output_path = input_path / f"{output_format}_output"
    else:
        output_path = Path(output_dir)
    
    output_path.mkdir(parents=True, exist_ok=True)
    
    dwg_files = list(input_path.glob("*.dwg")) + list(input_path.glob("*.DWG"))
    
    if not dwg_files:
        print("没有找到DWG文件")
        return
    
    print(f"找到 {len(dwg_files)} 个DWG文件")
    print(f"输出目录: {output_path}")
    print(f"输出格式: {output_format}")
    print("-" * 50)
    
    success = 0
    failed = 0
    
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        futures = {
            executor.submit(convert_file, f, output_path, output_format): f
            for f in dwg_files
        }
        
        for future in as_completed(futures):
            dwg_file = futures[future]
            try:
                ok, msg = future.result()
                if ok:
                    print(f"✓ {dwg_file.name}")
                    success += 1
                else:
                    print(f"✗ {dwg_file.name}: {msg}")
                    failed += 1
            except Exception as e:
                print(f"✗ {dwg_file.name}: {e}")
                failed += 1
    
    print("-" * 50)
    print(f"完成: 成功 {success}, 失败 {failed}")

if __name__ == '__main__':
    if len(sys.argv) < 3:
        print("用法: python batch_convert.py <输入目录> <格式> [输出目录]")
        print("格式: dxf, json, geojson, svg")
        sys.exit(1)
    
    input_dir = sys.argv[1]
    output_format = sys.argv[2]
    output_dir = sys.argv[3] if len(sys.argv) > 3 else None
    
    batch_convert(input_dir, output_format, output_dir)

9.7 版本转换

9.7.1 降级转换

# 将高版本DWG转换为R2000
dwgrewrite -r r2000 r2018_drawing.dwg -o r2000_drawing.dwg

# 通过DXF中转
dwg2dxf -r r14 drawing.dwg -o temp.dxf
dxf2dwg -r r2000 temp.dxf -o output.dwg
rm temp.dxf

9.7.2 版本转换脚本

#!/bin/bash
# version_convert.sh - DWG版本转换

TARGET_VERSION="${1:-r2000}"
shift

if [ $# -eq 0 ]; then
    echo "用法: $0 <目标版本> <dwg文件...>"
    echo "目标版本: r14, r2000, r2004, r2007, r2010"
    exit 1
fi

for dwg in "$@"; do
    if [ -f "$dwg" ]; then
        output="${dwg%.dwg}_${TARGET_VERSION}.dwg"
        echo "转换: $dwg -> $output"
        dwgrewrite -r "$TARGET_VERSION" "$dwg" -o "$output"
    fi
done

9.8 本章小结

本章介绍了LibreDWG的格式转换功能:

  1. DWG↔DXF:双向转换,支持ASCII和二进制DXF
  2. DWG↔JSON:完整数据表示,便于分析和处理
  3. DWG→GeoJSON:地理空间格式,适用于GIS
  4. DWG→SVG:矢量图形,适用于Web显示
  5. 批量转换:Shell和Python脚本实现
  6. 版本转换:不同DWG版本之间的转换

下一章预告第10章 - Python绑定开发指南 - 使用Python进行LibreDWG开发。

posted @ 2026-01-11 00:40  我才是银古  阅读(6)  评论(0)    收藏  举报