602 lines
25 KiB
C#
602 lines
25 KiB
C#
using System;
|
||
using System.Collections.Generic;
|
||
using System.Globalization;
|
||
using System.IO;
|
||
using System.Text;
|
||
using System.Linq;
|
||
using Autodesk.AutoCAD.DatabaseServices;
|
||
using Autodesk.AutoCAD.Geometry;
|
||
using Autodesk.AutoCAD.BoundaryRepresentation;
|
||
|
||
namespace FurnitureExporter
|
||
{
|
||
/// <summary>
|
||
/// OBJ 格式导出器 (v2.0)
|
||
/// 使用 Brep API 从 AutoCAD Solid3d 提取真实三角网格
|
||
/// 保留开孔、倒角等几何细节
|
||
/// </summary>
|
||
public class ObjExporter
|
||
{
|
||
private readonly StringBuilder _vertices = new StringBuilder();
|
||
private readonly StringBuilder _normals = new StringBuilder();
|
||
private readonly StringBuilder _faces = new StringBuilder();
|
||
private readonly Dictionary<string, int> _vertexIndexMap = new Dictionary<string, int>();
|
||
private readonly Dictionary<string, int> _normalIndexMap = new Dictionary<string, int>();
|
||
|
||
private int _vertexCount = 0;
|
||
private int _normalCount = 0;
|
||
private int _solidCount = 0;
|
||
private int _faceCount = 0;
|
||
private Point3d _originOffset = Point3d.Origin;
|
||
|
||
// 网格密度控制参数
|
||
private double _meshDensityFactor = 45.0; // 默认改为更保守的密度,减少大平面过度细分
|
||
private int _maxSubdivisions = 20000; // 默认降低最大细分次数,避免简单板件导出异常膨胀
|
||
|
||
/// <summary>
|
||
/// 设置网格密度因子(默认 100,值越大网格越密,文件越大)
|
||
/// </summary>
|
||
public void SetMeshDensity(double densityFactor, int maxSubdivisions = 20000)
|
||
{
|
||
_meshDensityFactor = Math.Max(10, Math.Min(200, densityFactor));
|
||
_maxSubdivisions = Math.Max(1000, Math.Min(200000, maxSubdivisions));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 设置原点偏移(将模型移动到世界原点)
|
||
/// </summary>
|
||
public void SetOriginOffset(Point3d offset)
|
||
{
|
||
_originOffset = offset;
|
||
}
|
||
|
||
private static string BuildVertexKey(Point3d point)
|
||
{
|
||
return string.Format(
|
||
CultureInfo.InvariantCulture,
|
||
"{0:F6}|{1:F6}|{2:F6}",
|
||
point.X,
|
||
point.Y,
|
||
point.Z
|
||
);
|
||
}
|
||
|
||
private static string BuildNormalKey(Vector3d normal)
|
||
{
|
||
return string.Format(
|
||
CultureInfo.InvariantCulture,
|
||
"{0:F6}|{1:F6}|{2:F6}",
|
||
normal.X,
|
||
normal.Y,
|
||
normal.Z
|
||
);
|
||
}
|
||
|
||
private int GetOrCreateVertexIndex(Point3d point)
|
||
{
|
||
var key = BuildVertexKey(point);
|
||
if (_vertexIndexMap.TryGetValue(key, out var existingIndex))
|
||
{
|
||
return existingIndex;
|
||
}
|
||
|
||
_vertices.AppendLine(string.Format(CultureInfo.InvariantCulture,
|
||
"v {0:F6} {1:F6} {2:F6}", point.X, point.Y, point.Z));
|
||
_vertexCount++;
|
||
_vertexIndexMap[key] = _vertexCount;
|
||
return _vertexCount;
|
||
}
|
||
|
||
private int GetOrCreateNormalIndex(Vector3d normal)
|
||
{
|
||
var key = BuildNormalKey(normal);
|
||
if (_normalIndexMap.TryGetValue(key, out var existingIndex))
|
||
{
|
||
return existingIndex;
|
||
}
|
||
|
||
_normals.AppendLine(string.Format(CultureInfo.InvariantCulture,
|
||
"vn {0:F6} {1:F6} {2:F6}", normal.X, normal.Y, normal.Z));
|
||
_normalCount++;
|
||
_normalIndexMap[key] = _normalCount;
|
||
return _normalCount;
|
||
}
|
||
|
||
private double CalculateMaxNodeSpacing(Extents3d extents)
|
||
{
|
||
var size = extents.MinPoint.GetVectorTo(extents.MaxPoint);
|
||
var longestEdge = Math.Max(size.X, Math.Max(size.Y, size.Z));
|
||
var spacingByDensity = longestEdge / _meshDensityFactor;
|
||
|
||
// 约束取值:
|
||
// - 过小会导致大平面和倒角被极度细分
|
||
// - 过大又会明显破坏孔和边缘观感
|
||
return Math.Max(4.0, Math.Min(18.0, spacingByDensity));
|
||
}
|
||
|
||
/// <summary>
|
||
/// 添加任意支持的实体到 OBJ 导出器
|
||
/// </summary>
|
||
public void AddEntity(Entity entity, string? name = null)
|
||
{
|
||
if (entity == null) return;
|
||
|
||
if (entity is Solid3d solid)
|
||
{
|
||
AddSolid(solid, name);
|
||
return;
|
||
}
|
||
|
||
if (entity is BlockReference blockReference)
|
||
{
|
||
AddBlockReference(blockReference, name);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用 Brep API 添加一个 Solid3d 实体(提取真实几何)
|
||
/// </summary>
|
||
public void AddSolid(Solid3d solid, string? name = null)
|
||
{
|
||
if (solid == null) return;
|
||
|
||
try
|
||
{
|
||
_solidCount++;
|
||
var solidName = name ?? $"panel_{_solidCount - 1:D3}";
|
||
|
||
var extents = solid.GeometricExtents;
|
||
|
||
// 对象名必须与该对象的面一起输出;否则 obj2gltf 会把全部面归到最后一个对象
|
||
_faces.AppendLine($"o {solidName}");
|
||
|
||
// 使用 Brep API 提取真实几何
|
||
using (var brep = new Brep(solid))
|
||
{
|
||
using (var meshControl = new Mesh2dControl())
|
||
{
|
||
// 设置网格控制参数
|
||
// MaxNodeSpacing 控制网格点之间的最大距离
|
||
meshControl.MaxNodeSpacing = CalculateMaxNodeSpacing(extents);
|
||
meshControl.MaxSubdivisions = (uint)_maxSubdivisions;
|
||
|
||
using (var meshFilter = new Mesh2dFilter())
|
||
{
|
||
// 将控制参数应用到 Brep
|
||
meshFilter.Insert(brep, meshControl);
|
||
|
||
using (var mesh = new Mesh2d(meshFilter))
|
||
{
|
||
// 遍历网格元素(三角形或四边形)
|
||
foreach (Element2d element in mesh.Element2ds)
|
||
{
|
||
var points = new List<Point3d>();
|
||
// 提取元素的顶点
|
||
foreach (Node node in element.Nodes)
|
||
{
|
||
// 应用原点偏移
|
||
var point = new Point3d(
|
||
node.Point.X + _originOffset.X,
|
||
node.Point.Y + _originOffset.Y,
|
||
node.Point.Z + _originOffset.Z
|
||
);
|
||
points.Add(point);
|
||
|
||
// 必须显式 Dispose 节点,否则会崩溃
|
||
node.Dispose();
|
||
}
|
||
|
||
// 必须显式 Dispose 元素,否则会崩溃
|
||
element.Dispose();
|
||
|
||
if (points.Count < 3) continue;
|
||
|
||
// 计算面法线
|
||
Vector3d normal = CalculateFaceNormal(points);
|
||
var normalIndex = GetOrCreateNormalIndex(normal);
|
||
|
||
// 写入顶点(复用相同坐标,避免 OBJ 文本重复膨胀)
|
||
var vertexIndices = new List<int>();
|
||
foreach (var pt in points)
|
||
{
|
||
vertexIndices.Add(GetOrCreateVertexIndex(pt));
|
||
}
|
||
|
||
// 写入面
|
||
if (points.Count == 3)
|
||
{
|
||
// 三角形
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndex} {vertexIndices[1]}//{normalIndex} {vertexIndices[2]}//{normalIndex}");
|
||
_faceCount++;
|
||
}
|
||
else if (points.Count == 4)
|
||
{
|
||
// 四边形 - 拆分为两个三角形
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndex} {vertexIndices[1]}//{normalIndex} {vertexIndices[2]}//{normalIndex}");
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndex} {vertexIndices[2]}//{normalIndex} {vertexIndices[3]}//{normalIndex}");
|
||
_faceCount += 2;
|
||
}
|
||
else
|
||
{
|
||
// 多边形 - 扇形三角化
|
||
for (int i = 1; i < points.Count - 1; i++)
|
||
{
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndex} {vertexIndices[i]}//{normalIndex} {vertexIndices[i + 1]}//{normalIndex}");
|
||
_faceCount++;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
catch (Autodesk.AutoCAD.Runtime.Exception ex)
|
||
{
|
||
// Brep API 失败时回退到边界框方法
|
||
System.Diagnostics.Debug.WriteLine($"Brep 导出失败,回退到边界框: {ex.Message}");
|
||
AddSolidBoundingBox(solid, name);
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"导出 Solid3d 失败: {ex.Message}");
|
||
// 尝试回退到边界框
|
||
try
|
||
{
|
||
AddSolidBoundingBox(solid, name);
|
||
}
|
||
catch
|
||
{
|
||
// 完全失败,跳过此实体
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 尝试展开图块引用并导出内部几何。
|
||
/// 如果图块内部没有可导出的 3D 实体,则退化为包围盒模型,至少保证资产中心有模型可用。
|
||
/// </summary>
|
||
private void AddBlockReference(BlockReference blockReference, string? name = null)
|
||
{
|
||
if (blockReference == null) return;
|
||
|
||
var exploded = new DBObjectCollection();
|
||
try
|
||
{
|
||
blockReference.Explode(exploded);
|
||
|
||
var baseName = name ?? $"block_{_solidCount:D3}";
|
||
var childIndex = 0;
|
||
var exportedAny = false;
|
||
|
||
foreach (DBObject dbObject in exploded)
|
||
{
|
||
if (dbObject is not Entity entity)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
if (entity is not Solid3d && entity is not BlockReference)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
AddEntity(entity, $"{baseName}_{childIndex:D3}");
|
||
childIndex++;
|
||
exportedAny = true;
|
||
}
|
||
|
||
if (!exportedAny)
|
||
{
|
||
AddBlockReferenceBoundingBox(blockReference, baseName);
|
||
}
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
System.Diagnostics.Debug.WriteLine($"导出 BlockReference 失败,回退到边界框: {ex.Message}");
|
||
AddBlockReferenceBoundingBox(blockReference, name);
|
||
}
|
||
finally
|
||
{
|
||
foreach (DBObject dbObject in exploded)
|
||
{
|
||
dbObject.Dispose();
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 图块引用的包围盒导出(回退方案)
|
||
/// </summary>
|
||
private void AddBlockReferenceBoundingBox(BlockReference blockReference, string? name = null)
|
||
{
|
||
if (blockReference == null) return;
|
||
|
||
_solidCount++;
|
||
var solidName = name ?? $"block_{_solidCount - 1:D3}";
|
||
|
||
var extents = blockReference.GeometricExtents;
|
||
var min = extents.MinPoint;
|
||
var max = extents.MaxPoint;
|
||
|
||
var minOffset = new Point3d(min.X + _originOffset.X, min.Y + _originOffset.Y, min.Z + _originOffset.Z);
|
||
var maxOffset = new Point3d(max.X + _originOffset.X, max.Y + _originOffset.Y, max.Z + _originOffset.Z);
|
||
|
||
var vertices = new Point3d[]
|
||
{
|
||
new Point3d(minOffset.X, minOffset.Y, minOffset.Z),
|
||
new Point3d(maxOffset.X, minOffset.Y, minOffset.Z),
|
||
new Point3d(maxOffset.X, maxOffset.Y, minOffset.Z),
|
||
new Point3d(minOffset.X, maxOffset.Y, minOffset.Z),
|
||
new Point3d(minOffset.X, minOffset.Y, maxOffset.Z),
|
||
new Point3d(maxOffset.X, minOffset.Y, maxOffset.Z),
|
||
new Point3d(maxOffset.X, maxOffset.Y, maxOffset.Z),
|
||
new Point3d(minOffset.X, maxOffset.Y, maxOffset.Z),
|
||
};
|
||
|
||
_faces.AppendLine($"o {solidName}");
|
||
|
||
var vertexIndices = vertices.Select(GetOrCreateVertexIndex).ToArray();
|
||
var normalIndices = new int[]
|
||
{
|
||
GetOrCreateNormalIndex(new Vector3d(0, 0, -1)),
|
||
GetOrCreateNormalIndex(new Vector3d(0, 0, 1)),
|
||
GetOrCreateNormalIndex(new Vector3d(0, -1, 0)),
|
||
GetOrCreateNormalIndex(new Vector3d(0, 1, 0)),
|
||
GetOrCreateNormalIndex(new Vector3d(-1, 0, 0)),
|
||
GetOrCreateNormalIndex(new Vector3d(1, 0, 0)),
|
||
};
|
||
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndices[0]} {vertexIndices[2]}//{normalIndices[0]} {vertexIndices[1]}//{normalIndices[0]}");
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndices[0]} {vertexIndices[3]}//{normalIndices[0]} {vertexIndices[2]}//{normalIndices[0]}");
|
||
|
||
_faces.AppendLine($"f {vertexIndices[4]}//{normalIndices[1]} {vertexIndices[5]}//{normalIndices[1]} {vertexIndices[6]}//{normalIndices[1]}");
|
||
_faces.AppendLine($"f {vertexIndices[4]}//{normalIndices[1]} {vertexIndices[6]}//{normalIndices[1]} {vertexIndices[7]}//{normalIndices[1]}");
|
||
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndices[2]} {vertexIndices[1]}//{normalIndices[2]} {vertexIndices[5]}//{normalIndices[2]}");
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndices[2]} {vertexIndices[5]}//{normalIndices[2]} {vertexIndices[4]}//{normalIndices[2]}");
|
||
|
||
_faces.AppendLine($"f {vertexIndices[2]}//{normalIndices[3]} {vertexIndices[3]}//{normalIndices[3]} {vertexIndices[7]}//{normalIndices[3]}");
|
||
_faces.AppendLine($"f {vertexIndices[2]}//{normalIndices[3]} {vertexIndices[7]}//{normalIndices[3]} {vertexIndices[6]}//{normalIndices[3]}");
|
||
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndices[4]} {vertexIndices[4]}//{normalIndices[4]} {vertexIndices[7]}//{normalIndices[4]}");
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndices[4]} {vertexIndices[7]}//{normalIndices[4]} {vertexIndices[3]}//{normalIndices[4]}");
|
||
|
||
_faces.AppendLine($"f {vertexIndices[1]}//{normalIndices[5]} {vertexIndices[2]}//{normalIndices[5]} {vertexIndices[6]}//{normalIndices[5]}");
|
||
_faces.AppendLine($"f {vertexIndices[1]}//{normalIndices[5]} {vertexIndices[6]}//{normalIndices[5]} {vertexIndices[5]}//{normalIndices[5]}");
|
||
|
||
_faceCount += 12;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 计算面法线(使用 Newell 方法,适用于任意多边形)
|
||
/// </summary>
|
||
private Vector3d CalculateFaceNormal(List<Point3d> points)
|
||
{
|
||
double nx = 0, ny = 0, nz = 0;
|
||
int n = points.Count;
|
||
|
||
for (int i = 0; i < n; i++)
|
||
{
|
||
var current = points[i];
|
||
var next = points[(i + 1) % n];
|
||
|
||
nx += (current.Y - next.Y) * (current.Z + next.Z);
|
||
ny += (current.Z - next.Z) * (current.X + next.X);
|
||
nz += (current.X - next.X) * (current.Y + next.Y);
|
||
}
|
||
|
||
var normal = new Vector3d(nx, ny, nz);
|
||
|
||
// 归一化
|
||
double length = normal.Length;
|
||
if (length > 1e-10)
|
||
{
|
||
normal = normal.DivideBy(length);
|
||
}
|
||
else
|
||
{
|
||
// 如果法线接近零,使用默认值
|
||
normal = new Vector3d(0, 0, 1);
|
||
}
|
||
|
||
return normal;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 使用边界框方法添加实体(作为回退方案)
|
||
/// </summary>
|
||
private void AddSolidBoundingBox(Solid3d solid, string? name = null)
|
||
{
|
||
if (solid == null) return;
|
||
|
||
_solidCount++;
|
||
var solidName = name ?? $"panel_{_solidCount - 1:D3}";
|
||
|
||
// 获取实体的边界框
|
||
var extents = solid.GeometricExtents;
|
||
var min = extents.MinPoint;
|
||
var max = extents.MaxPoint;
|
||
|
||
// 应用原点偏移
|
||
var minOffset = new Point3d(min.X + _originOffset.X, min.Y + _originOffset.Y, min.Z + _originOffset.Z);
|
||
var maxOffset = new Point3d(max.X + _originOffset.X, max.Y + _originOffset.Y, max.Z + _originOffset.Z);
|
||
|
||
// 生成长方体的 8 个顶点
|
||
var vertices = new Point3d[]
|
||
{
|
||
new Point3d(minOffset.X, minOffset.Y, minOffset.Z), // 0: 前左下
|
||
new Point3d(maxOffset.X, minOffset.Y, minOffset.Z), // 1: 前右下
|
||
new Point3d(maxOffset.X, maxOffset.Y, minOffset.Z), // 2: 后右下
|
||
new Point3d(minOffset.X, maxOffset.Y, minOffset.Z), // 3: 后左下
|
||
new Point3d(minOffset.X, minOffset.Y, maxOffset.Z), // 4: 前左上
|
||
new Point3d(maxOffset.X, minOffset.Y, maxOffset.Z), // 5: 前右上
|
||
new Point3d(maxOffset.X, maxOffset.Y, maxOffset.Z), // 6: 后右上
|
||
new Point3d(minOffset.X, maxOffset.Y, maxOffset.Z), // 7: 后左上
|
||
};
|
||
|
||
// 对象名必须与该对象的面一起输出;否则 obj2gltf 会把全部面归到最后一个对象
|
||
_faces.AppendLine($"o {solidName}");
|
||
|
||
var vertexIndices = vertices.Select(GetOrCreateVertexIndex).ToArray();
|
||
var normalIndices = new int[]
|
||
{
|
||
GetOrCreateNormalIndex(new Vector3d(0, 0, -1)),
|
||
GetOrCreateNormalIndex(new Vector3d(0, 0, 1)),
|
||
GetOrCreateNormalIndex(new Vector3d(0, -1, 0)),
|
||
GetOrCreateNormalIndex(new Vector3d(0, 1, 0)),
|
||
GetOrCreateNormalIndex(new Vector3d(-1, 0, 0)),
|
||
GetOrCreateNormalIndex(new Vector3d(1, 0, 0)),
|
||
};
|
||
|
||
// 底面 (Z min)
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndices[0]} {vertexIndices[2]}//{normalIndices[0]} {vertexIndices[1]}//{normalIndices[0]}");
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndices[0]} {vertexIndices[3]}//{normalIndices[0]} {vertexIndices[2]}//{normalIndices[0]}");
|
||
|
||
// 顶面 (Z max)
|
||
_faces.AppendLine($"f {vertexIndices[4]}//{normalIndices[1]} {vertexIndices[5]}//{normalIndices[1]} {vertexIndices[6]}//{normalIndices[1]}");
|
||
_faces.AppendLine($"f {vertexIndices[4]}//{normalIndices[1]} {vertexIndices[6]}//{normalIndices[1]} {vertexIndices[7]}//{normalIndices[1]}");
|
||
|
||
// 前面 (Y min)
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndices[2]} {vertexIndices[1]}//{normalIndices[2]} {vertexIndices[5]}//{normalIndices[2]}");
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndices[2]} {vertexIndices[5]}//{normalIndices[2]} {vertexIndices[4]}//{normalIndices[2]}");
|
||
|
||
// 后面 (Y max)
|
||
_faces.AppendLine($"f {vertexIndices[2]}//{normalIndices[3]} {vertexIndices[3]}//{normalIndices[3]} {vertexIndices[7]}//{normalIndices[3]}");
|
||
_faces.AppendLine($"f {vertexIndices[2]}//{normalIndices[3]} {vertexIndices[7]}//{normalIndices[3]} {vertexIndices[6]}//{normalIndices[3]}");
|
||
|
||
// 左面 (X min)
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndices[4]} {vertexIndices[4]}//{normalIndices[4]} {vertexIndices[7]}//{normalIndices[4]}");
|
||
_faces.AppendLine($"f {vertexIndices[0]}//{normalIndices[4]} {vertexIndices[7]}//{normalIndices[4]} {vertexIndices[3]}//{normalIndices[4]}");
|
||
|
||
// 右面 (X max)
|
||
_faces.AppendLine($"f {vertexIndices[1]}//{normalIndices[5]} {vertexIndices[2]}//{normalIndices[5]} {vertexIndices[6]}//{normalIndices[5]}");
|
||
_faces.AppendLine($"f {vertexIndices[1]}//{normalIndices[5]} {vertexIndices[6]}//{normalIndices[5]} {vertexIndices[5]}//{normalIndices[5]}");
|
||
|
||
_faceCount += 12;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存为 OBJ 文件
|
||
/// </summary>
|
||
public void Save(string outputPath)
|
||
{
|
||
var content = new StringBuilder();
|
||
|
||
// 文件头
|
||
content.AppendLine("# Furniture Exporter OBJ File (v2.0 - True Geometry)");
|
||
content.AppendLine($"# Generated: {DateTime.Now:yyyy-MM-dd HH:mm:ss}");
|
||
content.AppendLine($"# Solids: {_solidCount}");
|
||
content.AppendLine($"# Vertices: {_vertexCount}");
|
||
content.AppendLine($"# Normals: {_normalCount}");
|
||
content.AppendLine($"# Faces: {_faceCount}");
|
||
content.AppendLine();
|
||
|
||
// 顶点
|
||
content.Append(_vertices);
|
||
content.AppendLine();
|
||
|
||
// 法线
|
||
content.Append(_normals);
|
||
content.AppendLine();
|
||
|
||
// 面(包含对象分组标记)
|
||
content.Append(_faces);
|
||
|
||
File.WriteAllText(outputPath, content.ToString(), Encoding.UTF8);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取统计信息
|
||
/// </summary>
|
||
public (int Solids, int Vertices, int Normals, int Faces) GetStats()
|
||
{
|
||
return (_solidCount, _vertexCount, _normalCount, _faceCount);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导出多个实体到 OBJ 文件
|
||
/// </summary>
|
||
/// <param name="solidIds">实体 ID 数组</param>
|
||
/// <param name="outputPath">输出文件路径</param>
|
||
/// <param name="originOffset">原点偏移(可选,用于将模型移动到世界原点)</param>
|
||
/// <param name="meshDensity">网格密度因子(可选,默认 45)</param>
|
||
/// <param name="maxSubdivisions">最大细分次数(可选,默认 20000)</param>
|
||
public static void Export(
|
||
ObjectId[] solidIds,
|
||
string outputPath,
|
||
Point3d? originOffset = null,
|
||
double meshDensity = 45,
|
||
int maxSubdivisions = 20000)
|
||
{
|
||
var exporter = new ObjExporter();
|
||
|
||
if (originOffset.HasValue)
|
||
{
|
||
exporter.SetOriginOffset(originOffset.Value);
|
||
}
|
||
|
||
exporter.SetMeshDensity(meshDensity, maxSubdivisions);
|
||
|
||
var db = solidIds[0].Database;
|
||
|
||
using (var tr = db.TransactionManager.StartTransaction())
|
||
{
|
||
int index = 0;
|
||
foreach (var id in solidIds)
|
||
{
|
||
var solid = tr.GetObject(id, OpenMode.ForRead) as Solid3d;
|
||
if (solid != null)
|
||
{
|
||
exporter.AddSolid(solid, $"panel_{index:D3}");
|
||
index++;
|
||
}
|
||
}
|
||
tr.Commit();
|
||
}
|
||
|
||
exporter.Save(outputPath);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 导出多个实体(3D Solid / BlockReference)到 OBJ 文件
|
||
/// </summary>
|
||
public static void ExportEntities(
|
||
ObjectId[] entityIds,
|
||
string outputPath,
|
||
Database db,
|
||
Point3d? originOffset = null,
|
||
double meshDensity = 45,
|
||
int maxSubdivisions = 20000)
|
||
{
|
||
var exporter = new ObjExporter();
|
||
|
||
if (originOffset.HasValue)
|
||
{
|
||
exporter.SetOriginOffset(originOffset.Value);
|
||
}
|
||
|
||
exporter.SetMeshDensity(meshDensity, maxSubdivisions);
|
||
|
||
using (var tr = db.TransactionManager.StartTransaction())
|
||
{
|
||
int index = 0;
|
||
foreach (var id in entityIds)
|
||
{
|
||
var entity = tr.GetObject(id, OpenMode.ForRead) as Entity;
|
||
if (entity == null)
|
||
{
|
||
continue;
|
||
}
|
||
|
||
exporter.AddEntity(entity, $"asset_{index:D3}");
|
||
index++;
|
||
}
|
||
tr.Commit();
|
||
}
|
||
|
||
exporter.Save(outputPath);
|
||
}
|
||
}
|
||
}
|