feat(All):Initial

This commit is contained in:
2025-10-04 23:36:07 +08:00
commit 2b4e5d2668
1321 changed files with 415958 additions and 0 deletions
@@ -0,0 +1,88 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface I3DfaceEntity extends IEntity {
shape: boolean;
hasContinuousLinetypePattern: boolean;
vertices: IPoint[];
}
export default class ThreeDface implements IGeometry {
public ForEntityName = '3DFACE' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = { type: curr.value as string, vertices: [] as IPoint[] } as I3DfaceEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) break;
switch (curr.code) {
case 70: // 1 = Closed shape, 128 = plinegen?, 0 = default
entity.shape = (((curr.value as number) & 1) === 1);
entity.hasContinuousLinetypePattern = (((curr.value as number) & 128) === 128);
break;
case 10: // X coordinate of point
entity.vertices = parse3dFaceVertices(scanner, curr);
curr = scanner.lastReadGroup as IGroup;
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
function parse3dFaceVertices(scanner:DxfArrayScanner, curr:IGroup) {
var vertices:IPoint[] = [];
var vertexIsStarted = false;
var vertexIsFinished = false;
var verticesPer3dFace = 4; // there can be up to four vertices per face, although 3 is most used for TIN
for (let i = 0; i <= verticesPer3dFace; i++) {
var vertex = {} as IPoint;
while (!scanner.isEOF()) {
if (curr.code === 0 || vertexIsFinished) break;
switch (curr.code) {
case 10: // X0
case 11: // X1
case 12: // X2
case 13: // X3
if (vertexIsStarted) {
vertexIsFinished = true;
continue;
}
vertex.x = curr.value as number;
vertexIsStarted = true;
break;
case 20: // Y
case 21:
case 22:
case 23:
vertex.y = curr.value as number;
break;
case 30: // Z
case 31:
case 32:
case 33:
vertex.z = curr.value as number;
break;
default:
// it is possible to have entity codes after the vertices.
// So if code is not accounted for return to entity parser where it might be accounted for
return vertices;
}
curr = scanner.next();
}
// See https://groups.google.com/forum/#!topic/comp.cad.autocad/9gn8s5O_w6E
vertices.push(vertex);
vertexIsStarted = false;
vertexIsFinished = false;
}
scanner.rewind();
return vertices;
};
@@ -0,0 +1,57 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface IArcEntity extends IEntity {
center: IPoint;
radius: number;
startAngle: number;
endAngle: number;
angleLength: number;
extrusionDirectionX: number;
extrusionDirectionY: number;
extrusionDirectionZ: number;
}
export default class Arc implements IGeometry {
public ForEntityName = 'ARC' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = { type: curr.value } as IArcEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) break;
switch (curr.code) {
case 10: // X coordinate of point
entity.center = helpers.parsePoint(scanner);
break;
case 40: // radius
entity.radius = curr.value as number;
break;
case 50: // start angle
entity.startAngle = Math.PI / 180 * (curr.value as number);
break;
case 51: // end angle
entity.endAngle = Math.PI / 180 * (curr.value as number);
entity.angleLength = entity.endAngle - entity.startAngle; // angleLength is deprecated
break;
case 210:
entity.extrusionDirectionX = curr.value as number;
break;
case 220:
entity.extrusionDirectionY = curr.value as number;
break;
case 230:
entity.extrusionDirectionZ = curr.value as number;
break;
default: // ignored attribute
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,119 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface IAttdefEntity extends IEntity {
scale: number;
textStyle: 'STANDARD' | string;
text: string;
tag: string;
prompt: string;
startPoint: IPoint;
endPoint: IPoint;
thickness: number;
textHeight: number;
rotation: number;
obliqueAngle: number;
invisible: boolean;
constant: boolean;
verificationRequired: boolean;
preset: boolean;
backwards: boolean;
mirrored: boolean;
horizontalJustification: number;
fieldLength: number;
verticalJustification: number;
extrusionDirectionX: number;
extrusionDirectionY: number;
extrusionDirectionZ: number;
}
export default class Attdef implements IGeometry {
public ForEntityName = 'ATTDEF' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = {
type: curr.value,
scale: 1,
textStyle: 'STANDARD'
} as IAttdefEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) {
break;
}
switch (curr.code) {
case 1:
entity.text = curr.value as string;
break;
case 2:
entity.tag = curr.value as string;
break;
case 3:
entity.prompt = curr.value as string;
break;
case 7:
entity.textStyle = curr.value as string;
break;
case 10: // X coordinate of 'first alignment point'
entity.startPoint = helpers.parsePoint(scanner);
break;
case 11: // X coordinate of 'second alignment point'
entity.endPoint = helpers.parsePoint(scanner);
break;
case 39:
entity.thickness = curr.value as number;
break;
case 40:
entity.textHeight = curr.value as number;
break;
case 41:
entity.scale = curr.value as number;
break;
case 50:
entity.rotation = curr.value as number;
break;
case 51:
entity.obliqueAngle = curr.value as number;
break;
case 70:
entity.invisible = !!((curr.value as number) & 0x01);
entity.constant = !!((curr.value as number) & 0x02);
entity.verificationRequired = !!((curr.value as number) & 0x04);
entity.preset = !!((curr.value as number) & 0x08);
break;
case 71:
entity.backwards = !!((curr.value as number) & 0x02);
entity.mirrored = !!((curr.value as number) & 0x04);
break;
case 72:
// TODO: enum values?
entity.horizontalJustification = curr.value as number;
break;
case 73:
entity.fieldLength = curr.value as number;
break;
case 74:
// TODO: enum values?
entity.verticalJustification = curr.value as number;
break;
case 100:
break;
case 210:
entity.extrusionDirectionX = curr.value as number;
break;
case 220:
entity.extrusionDirectionY = curr.value as number;
break;
case 230:
entity.extrusionDirectionZ = curr.value as number;
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,166 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers';
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface IAttribEntity extends IEntity {
scale: number;
textStyle: 'STANDARD' | string;
text: string;
tag: string;
prompt: string;
startPoint: IPoint;
endPoint: IPoint;
thickness: number;
textHeight: number;
rotation: number;
lineSpacingFactor: number;
fillBoxScale: number;
annotationHeight: number;
obliqueAngle: number;
invisible: boolean;
constant: boolean;
verificationRequired: boolean;
preset: boolean;
horizontalJustification: number;
verticalJustification: number;
backgroundFillColor: string;
attachmentPoint: number;
lineSpacing: number;
backgroundFillSetting: string;
extrusionDirection: IPoint;
lockPositionFlag: number;
hardPointerId: number;
}
export default class Attrib implements IGeometry{
public ForEntityName = 'ATTRIB' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = {
type: curr.value,
scale: 1,
textStyle: 'STANDARD',
} as IAttribEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
// 0 为图元类型 (MTEXT),不写入,此处当作 Attrib解析
if (curr.code === 0) {
break;
}
switch (curr.code) {
case 1:
// 字符串本身
entity.text = curr.value as string;
break;
case 2:
// 属性标签(字符串;不能包含空格)
entity.tag = curr.value as string;
break;
case 3:
// 附加文字(始终在长度为 250 个字符的数据块中)(可选)
entity.prompt = curr.value as string;
break;
case 7:
// DXF:X 值;APP:三维矢量文字样式名(如果未提供,则为 STANDARD)(可选)
entity.textStyle = curr.value as string;
break;
case 10: // 文字起点(在 OCS 中)
entity.startPoint = helpers.parsePoint(scanner);
break;
case 11: //X 轴方向矢量(在 WCS 中)
entity.endPoint = helpers.parsePoint(scanner);
break;
case 39:
// 厚度(可选;默认值 = 0
entity.thickness = curr.value as number;
break;
case 40:
// 文字高度
entity.textHeight = curr.value as number;
break;
case 41:
// X 相对缩放比例(宽度)(可选;默认值 = 1)。使用拟合类型的文字时,该值也将进行调整。
entity.scale = curr.value as number;
break;
case 44:
// 多行文字的行距比例(可选):
// 要应用的行距的默认百分比(五分之三)。
// 有效值的范围从 0.25 到 4.00
entity.lineSpacingFactor = curr.value as number;
break;
case 45:
// 填充框大小(可选):
// 确定文字周围的边框大小。
entity.fillBoxScale = curr.value as number;
break;
case 46:
// 定义注释高度
entity.annotationHeight = curr.value as number;
break;
case 50:
//文字旋转角度(可选;默认值 = 0)
entity.rotation = curr.value as number;
break;
case 51:
// 倾斜角(可选;默认值 = 0
entity.obliqueAngle = curr.value as number;
break;
case 63:
//背景填充颜色(可选):
// 组码 90 为 1 时要用于背景填充的颜色。
entity.backgroundFillColor = curr.value as string
break;
case 70:
entity.invisible = !!((curr.value as number) & 0x01);
entity.constant = !!((curr.value as number) & 0x02);
entity.verificationRequired = !!((curr.value as number) & 0x04);
entity.preset = !!((curr.value as number) & 0x08);
break;
case 71:
//附着点:
// 1 = 左上;2 = 中上;3 = 右上
// 4 = 左中;5 = 正中;6 = 右中
// 7 = 左下;8 = 中下;9 = 右下
entity.attachmentPoint = curr.value as number;
break;
case 72:
// 文字水平对正类型(可选;默认值 = 0)
entity.horizontalJustification = curr.value as number;
break;
case 73:
// 字段长度(可选;默认值 = 0)(当前未使用)
entity.lineSpacing = curr.value as number;
break;
case 74:
// 垂直文字对正类型(可选;默认值 = 0)
entity.verticalJustification = curr.value as number;
break;
case 90:
// 背景填充设置:
// 0 = 背景填充关闭
// 1 = 使用背景填充颜色
// 2 = 使用图形窗口颜色作为背景填充颜色
entity.backgroundFillSetting = curr.value as string;
break;
case 210:
// 拉伸方向。仅当图元的拉伸方向与 WCS 的 Z 轴不平行时才出现(可选;默认值 = 0, 0, 1)
entity.extrusionDirection = helpers.parsePoint(scanner);
break;
case 280:
// 锁定位置标志。锁定块参照中属性的位置
entity.lockPositionFlag = curr.value as number;
break;
case 340:
// 次要属性或属性定义的硬指针 ID
entity.hardPointerId = curr.value as number;
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,48 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface ICircleEntity extends IEntity {
center: IPoint;
radius: number;
startAngle: number;
endAngle: number;
angleLength: number;
}
export default class Circle implements IGeometry {
public ForEntityName = 'CIRCLE' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = { type: curr.value } as ICircleEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) break;
switch (curr.code) {
case 10: // X coordinate of point
entity.center = helpers.parsePoint(scanner);
break;
case 40: // radius
entity.radius = (curr.value as number);
break;
case 50: // start angle
entity.startAngle = Math.PI / 180 * (curr.value as number);
break;
case 51: // end angle
const endAngle = Math.PI / 180 * (curr.value as number);
if (endAngle < entity.startAngle)
entity.angleLength = endAngle + 2 * Math.PI - entity.startAngle;
else
entity.angleLength = endAngle - entity.startAngle;
entity.endAngle = endAngle;
break;
default: // ignored attribute
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,79 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface IDimensionEntity extends IEntity{
block: string;
anchorPoint: IPoint;
middleOfText: IPoint;
insertionPoint: IPoint;
linearOrAngularPoint1: IPoint;
linearOrAngularPoint2: IPoint;
diameterOrRadiusPoint: IPoint;
arcPoint: IPoint;
dimensionType: number;
attachmentPoint: number;
actualMeasurement: number;
text: string;
angle: number;
}
export default class Dimension implements IGeometry {
public ForEntityName = 'DIMENSION' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = { type: curr.value } as IDimensionEntity;
curr = scanner.next();
while(!scanner.isEOF()) {
if(curr.code === 0) break;
switch(curr.code) {
case 2: // Referenced block name
entity.block = curr.value as string;
break;
case 10: // X coordinate of 'first alignment point'
entity.anchorPoint = helpers.parsePoint(scanner);
break;
case 11:
entity.middleOfText = helpers.parsePoint(scanner);
break;
case 12: // Insertion point for clones of a dimension
entity.insertionPoint = helpers.parsePoint(scanner);
break;
case 13: // Definition point for linear and angular dimensions
entity.linearOrAngularPoint1 = helpers.parsePoint(scanner);
break;
case 14: // Definition point for linear and angular dimensions
entity.linearOrAngularPoint2 = helpers.parsePoint(scanner);
break;
case 15: // Definition point for diameter, radius, and angular dimensions
entity.diameterOrRadiusPoint = helpers.parsePoint(scanner);
break;
case 16: // Point defining dimension arc for angular dimensions
entity.arcPoint = helpers.parsePoint(scanner);
break;
case 70: // Dimension type
entity.dimensionType = curr.value as number;
break;
case 71: // 5 = Middle center
entity.attachmentPoint = curr.value as number;
break;
case 42: // Actual measurement
entity.actualMeasurement = curr.value as number;
break;
case 1: // Text entered by user explicitly
entity.text = curr.value as string;
break;
case 50: // Angle of rotated, horizontal, or vertical dimensions
entity.angle = curr.value as number;
break;
default: // check common entity attributes
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,52 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface IEllipseEntity extends IEntity {
center: IPoint;
majorAxisEndPoint: IPoint;
axisRatio: number;
startAngle: number;
endAngle: number;
name: string;
}
export default class Ellipse implements IGeometry {
public ForEntityName = 'ELLIPSE' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = { type: curr.value } as IEllipseEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) break;
switch (curr.code) {
case 10:
entity.center = helpers.parsePoint(scanner);
break;
case 11:
entity.majorAxisEndPoint = helpers.parsePoint(scanner);
break;
case 40:
entity.axisRatio = curr.value as number;
break;
case 41:
entity.startAngle = curr.value as number;
break;
case 42:
entity.endAngle = curr.value as number;
break;
case 2:
entity.name = curr.value as string;
break;
default: // check common entity attributes
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,50 @@
import DxfArrayScanner, { IGroup } from "../DxfArrayScanner";
export interface IPoint {
x: number;
y: number;
z: number;
}
export interface IEntity {
lineType: string;
layer: string;
lineTypeScale: number;
visible: boolean;
colorIndex: number;
color: number;
inPaperSpace: boolean;
ownerHandle: string;
materialObjectHandle: number;
lineweight: 0| 5| 9| 13| 15| 18| 20| 25| 30| 35| 40| 50| 53| 60| 70| 80| 90| 100| 106| 120| 140| 158| 200| 211|-3|-2|-1;
extendedData: {
customStrings: string[];
applicationName: string;
};
type: string;
handle: number;
}
export type EntityName = 'POINT'
| '3DFACE'
| 'ARC'
| 'ATTDEF'
| 'ATTRIB'
| 'CIRCLE'
| 'DIMENSION'
| 'ELLIPSE'
| 'HATCH'
| 'INSERT'
| 'LINE'
| 'LWPOLYLINE'
| 'MTEXT'
| 'POLYLINE'
| 'SOLID'
| 'SPLINE'
| 'TEXT'
| 'VERTEX';
export default interface IGeometry {
ForEntityName: EntityName;
parseEntity(scanner: DxfArrayScanner, curr: IGroup): IEntity;
}
@@ -0,0 +1,346 @@
import DxfArrayScanner, {IGroup} from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, {IEntity, IPoint} from './geomtry';
export interface IHatchEntity extends IEntity {
boundaryLoops: any[];
definitionLines: any[];
seedPoints: any[];
patternName: string;
isSolid: boolean;
hatchStyle: string;
patternType: number;
patternAngle: number;
patternScale: number;
}
export default class Hatch implements IGeometry {
public ForEntityName = 'HATCH' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
let entity = {type: curr.value} as IHatchEntity;
let numBoundaryLoops = 0;
let numDefinitionLines = 0;
let numSeedPoints = 0;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) break;
while (numBoundaryLoops > 0) {
const loop = ParseBoundaryLoop(curr, scanner)
if (loop) {
entity.boundaryLoops.push(loop);
numBoundaryLoops--;
curr = scanner.next();
} else {
numBoundaryLoops = 0
}
}
while (numDefinitionLines > 0) {
const line = ParseDefinitionLine(curr, scanner)
if (line) {
entity.definitionLines.push(line);
numDefinitionLines--;
curr = scanner.next();
} else {
numDefinitionLines = 0
}
}
while (numSeedPoints > 0) {
const pt = ParseSeedPoint(curr, scanner);
if (pt) {
entity.seedPoints.push(pt);
numSeedPoints--;
curr = scanner.next();
} else {
numSeedPoints = 0
}
}
if (curr.code === 0) break;
switch (curr.code) {
case 2: // 填充图案名
entity.patternName = curr.value as string;
break;
case 70: // 实体填充标志(实体填充 = 1;图案填充 = 0)
entity.isSolid = curr.value != 0;
break;
case 91: // 边界路径(环)数
numBoundaryLoops = curr.value as number;
if (numBoundaryLoops > 0) {
entity.boundaryLoops = []
}
break;
/**
* 图案填充样式:
* 0 = 对“奇数奇偶校验”区域进行图案填充(普通样式)
* 1 = 仅对最外层区域进行图案填充(“外部”样式)
* 2 = 对整个区域进行图案填充(“忽略”样式)
*/
case 75:
entity.hatchStyle = curr.value as string;
break;
/**
* 填充图案类型:
* 0 = 用户定义;1 = 预定义;2 = 自定义
*/
case 76:
entity.patternType = curr.value as number;
break;
case 52: // 填充图案角度(仅限图案填充)
entity.patternAngle = (curr.value as number) * Math.PI / 180;
break;
case 41: // 填充图案比例或间距(仅限图案填充)
entity.patternScale = curr.value as number;
break;
case 78: // 图案定义直线数
numDefinitionLines = curr.value as number;
if (numDefinitionLines > 0) {
entity.definitionLines = []
}
break;
case 98: // 种子点数
numSeedPoints = curr.value as number;
if (numSeedPoints > 0) {
entity.seedPoints = []
}
break;
default: // 检查通用实体属性
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
};
}
function ParseBoundaryLoop(curr: IGroup, scanner: DxfArrayScanner) {
let entity:any = null
const ParsePolyline = () => {
const pl: { vertices: IPoint[], isClosed: boolean } = {vertices: [], isClosed: false};
let numVertices = 0;
while (true) {
if (numVertices > 0) {
for (let i = 0; i < numVertices; i++) {
if (curr.code != 10) {
break
}
const p = helpers.parsePoint(scanner)
curr = scanner.next();
if (curr.code == 42) {
// @ts-ignore
p.bulge = curr.value;
curr = scanner.next();
}
pl.vertices.push(p)
}
return pl
}
switch (curr.code) {
case 73:
pl.isClosed = curr.value as boolean;
break;
case 93:
numVertices = curr.value as number;
break;
default:
return pl;
}
curr = scanner.next();
}
}
const ParseEdge = () => {
if (curr.code != 72) {
return null
}
const e:any = {type: curr.value}
curr = scanner.next();
const isSpline = e.type == 4;
while (true) {
switch (curr.code) {
case 10:
if (isSpline) {
if (!e.controlPoints) {
e.controlPoints = [];
}
e.controlPoints.push(helpers.parsePoint(scanner));
} else {
e.start = helpers.parsePoint(scanner);
}
break;
case 11:
if (isSpline) {
if (!e.fitPoints) {
e.fitPoints = [];
}
e.fitPoints.push(helpers.parsePoint(scanner));
} else {
e.end = helpers.parsePoint(scanner);
}
break;
case 40:
if (isSpline) {
if (!e.knotValues) {
e.knotValues = [];
}
e.knotValues.push(curr.value);
} else {
e.radius = curr.value;
}
break;
case 50:
e.startAngle = (curr.value as number) * Math.PI / 180;
break;
case 51:
e.endAngle = (curr.value as number) * Math.PI / 180;
break;
case 73:
if (isSpline) {
e.rational = curr.value;
} else {
e.isCcw = curr.value;
}
break;
case 74:
e.periodic = curr.value;
break;
case 94:
e.degreeOfSplineCurve = curr.value;
break;
//XXX暂时忽略一些群体,主要是样条
case 95:
case 96:
case 42:
case 97:
break;
default:
return e
}
curr = scanner.next();
}
}
let polylineParsed = false;
let numEdges = 0;
let numSourceRefs = 0;
while (true) {
if (!entity) {
if (curr.code != 92) {
return null;
}
entity = {type: curr.value};
curr = scanner.next();
}
if ((entity.type & 2) && !polylineParsed) {
entity.polyline = ParsePolyline()
polylineParsed = true
}
while (numEdges) {
const edge = ParseEdge();
if (edge) {
entity.edges.push(edge);
numEdges--;
} else {
numEdges = 0;
}
}
while (numSourceRefs) {
if (curr.code == 330) {
entity.sourceRefs.push(curr.value);
numSourceRefs--;
curr = scanner.next();
} else {
numSourceRefs = 0
}
}
switch (curr.code) {
case 93:
numEdges = curr.value as number;
if (numEdges > 0) {
entity.edges = []
}
break;
case 97:
numSourceRefs = curr.value as number;
if (numSourceRefs > 0) {
entity.sourceRefs = []
}
break;
default:
scanner.rewind();
return entity;
}
curr = scanner.next();
}
}
function ParseDefinitionLine(curr: IGroup, scanner: DxfArrayScanner) {
/* 假设总是从53组开始. */
if (curr.code != 53) {
return null
}
const entity:any = {
angle: (curr.value as number) * Math.PI / 180,
base: {x: 0, y: 0},
offset: {x: 0, y: 0}
};
curr = scanner.next();
let numDashes = 0;
while (true) {
switch (curr.code) {
case 43:
entity.base.x = curr.value as number;
break;
case 44:
entity.base.y = curr.value as number;
break;
case 45:
entity.offset.x = curr.value as number;
break;
case 46:
entity.offset.y = curr.value as number;
break;
case 49:
if (numDashes > 0) {
entity.dashes.push(curr.value);
numDashes--;
}
break;
case 79:
numDashes = curr.value as number;
if (curr.value) {
entity.dashes = []
}
break;
default:
scanner.rewind();
return entity;
}
curr = scanner.next();
}
}
function ParseSeedPoint(curr: IGroup, scanner: DxfArrayScanner) {
if (curr.code != 10) {
return null
}
return helpers.parsePoint(scanner);
}
@@ -0,0 +1,70 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface IInsertEntity extends IEntity {
name: string;
xScale: number;
yScale: number;
zScale: number;
position: IPoint;
rotation: number;
columnCount: number;
rowCount: number;
columnSpacing: number;
rowSpacing: number;
extrusionDirection: IPoint;
}
export default class Insert implements IGeometry {
public ForEntityName = 'INSERT' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = { type: curr.value } as IInsertEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) break;
switch (curr.code) {
case 2:
entity.name = curr.value as string;
break;
case 41:
entity.xScale = curr.value as number;
break;
case 42:
entity.yScale = curr.value as number;
break;
case 43:
entity.zScale = curr.value as number;
break;
case 10:
entity.position = helpers.parsePoint(scanner);
break;
case 50:
entity.rotation = curr.value as number;
break;
case 70:
entity.columnCount = curr.value as number;
break;
case 71:
entity.rowCount = curr.value as number;
break;
case 44:
entity.columnSpacing = curr.value as number;
break;
case 45:
entity.rowSpacing = curr.value as number;
break;
case 210:
entity.extrusionDirection = helpers.parsePoint(scanner);
break;
default: // check common entity attributes
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,40 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface ILineEntity extends IEntity{
vertices: IPoint[];
extrusionDirection: IPoint;
}
export default class Line implements IGeometry{
public ForEntityName= 'LINE' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = { type: curr.value, vertices: [] as IPoint[] } as ILineEntity;
curr = scanner.next();
while(!scanner.isEOF()) {
if(curr.code === 0) break;
switch(curr.code) {
case 10: // X coordinate of point
entity.vertices.unshift(helpers.parsePoint(scanner));
break;
case 11:
entity.vertices.push(helpers.parsePoint(scanner));
break;
case 210:
entity.extrusionDirection = helpers.parsePoint(scanner);
break;
case 100:
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,125 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface IVertex extends IPoint{
startWidth: number;
endWidth: number;
bulge: number;
}
export interface ILwpolylineEntity extends IEntity {
vertices: IVertex[];
elevation: number;
depth: number;
shape: boolean;
hasContinuousLinetypePattern: boolean;
width: number;
extrusionDirectionX: number;
extrusionDirectionY: number;
extrusionDirectionZ: number;
}
export default class Lwpolyline implements IGeometry {
public ForEntityName = 'LWPOLYLINE' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = { type: curr.value, vertices: [] as IVertex[] } as ILwpolylineEntity;
let numberOfVertices = 0;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) break;
switch (curr.code) {
case 38:
entity.elevation = curr.value as number;
break;
case 39:
entity.depth = curr.value as number;
break;
case 70: // 1 = Closed shape, 128 = plinegen?, 0 = default
entity.shape = (((curr.value as number) & 1) === 1);
entity.hasContinuousLinetypePattern = (((curr.value as number) & 128) === 128);
break;
case 90:
numberOfVertices = curr.value as number;
break;
case 10: // X coordinate of point
entity.vertices = parseLWPolylineVertices(numberOfVertices, scanner);
break;
case 43:
if (curr.value !== 0) entity.width = curr.value as number;
break;
case 210:
entity.extrusionDirectionX = curr.value as number;
break;
case 220:
entity.extrusionDirectionY = curr.value as number;
break;
case 230:
entity.extrusionDirectionZ = curr.value as number;
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
function parseLWPolylineVertices(n:number, scanner: DxfArrayScanner) {
if (!n || n <= 0) throw Error('n must be greater than 0 verticies');
const vertices = [] as IVertex[];
let vertexIsStarted = false;
let vertexIsFinished = false;
let curr = scanner.lastReadGroup as IGroup;
for (let i = 0; i < n; i++) {
const vertex = {} as IVertex;
while (!scanner.isEOF()) {
if (curr.code === 0 || vertexIsFinished) break;
switch (curr.code) {
case 10: // X
if (vertexIsStarted) {
vertexIsFinished = true;
continue;
}
vertex.x = curr.value as number;
vertexIsStarted = true;
break;
case 20: // Y
vertex.y = curr.value as number;
break;
case 30: // Z
vertex.z = curr.value as number;
break;
case 40: // start width
vertex.startWidth = curr.value as number;
break;
case 41: // end width
vertex.endWidth = curr.value as number;
break;
case 42: // bulge
if (curr.value != 0) vertex.bulge = curr.value as number;
break;
default:
// if we do not hit known code return vertices. Code might belong to entity
scanner.rewind();
if (vertexIsStarted) {
vertices.push(vertex);
}
scanner.rewind();
return vertices;
}
curr = scanner.next();
}
// See https://groups.google.com/forum/#!topic/comp.cad.autocad/9gn8s5O_w6E
vertices.push(vertex);
vertexIsStarted = false;
vertexIsFinished = false;
}
scanner.rewind();
return vertices;
}
@@ -0,0 +1,62 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface IMtextEntity extends IEntity {
text: string;
position: IPoint;
directionVector: IPoint;
height: number;
width: number;
rotation: number;
attachmentPoint: number;
drawingDirection: number;
}
export default class Mtext implements IGeometry {
public ForEntityName = 'MTEXT' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = { type: curr.value } as IMtextEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) break;
switch (curr.code) {
case 3:
entity.text ? entity.text += curr.value : entity.text = curr.value as string;
break;
case 1:
entity.text ? entity.text += curr.value : entity.text = curr.value as string;
break;
case 10:
entity.position = helpers.parsePoint(scanner);
break;
case 11:
entity.directionVector = helpers.parsePoint(scanner);
break;
case 40:
//Note: this is the text height
entity.height = curr.value as number;
break;
case 41:
entity.width = curr.value as number;
break;
case 50:
entity.rotation = curr.value as number;
break;
case 71:
entity.attachmentPoint = curr.value as number;
break;
case 72:
entity.drawingDirection = curr.value as number;
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,42 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface IPointEntity extends IEntity{
position: IPoint;
thickness: number;
extrusionDirection: IPoint;
}
export default class Point implements IGeometry{
public ForEntityName= 'POINT' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const type = curr.value as string;
const entity = { type } as unknown as IPointEntity;
curr = scanner.next();
while(!scanner.isEOF()) {
if(curr.code === 0) break;
switch(curr.code) {
case 10:
entity.position = helpers.parsePoint(scanner);
break;
case 39:
entity.thickness = curr.value as number;
break;
case 210:
entity.extrusionDirection = helpers.parsePoint(scanner);
break;
case 100:
break;
default: // check common entity attributes
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,107 @@
import * as helpers from '../ParseHelpers'
import VertexParser, { IVertexEntity } from './vertex';
import IGeometry, { IEntity, IPoint } from './geomtry';
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
export interface IPolylineEntity extends IEntity {
vertices: IVertexEntity[];
thickness: number;
shape: boolean;
includesCurveFitVertices: boolean;
includesSplineFitVertices: boolean;
is3dPolyline: boolean;
is3dPolygonMesh: boolean;
is3dPolygonMeshClosed: boolean;
isPolyfaceMesh: boolean;
hasContinuousLinetypePattern: boolean;
extrusionDirection: IPoint;
}
export default class Polyline implements IGeometry {
public ForEntityName = 'POLYLINE' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
var entity = { type: curr.value, vertices: [] as IVertexEntity[] } as IPolylineEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) break;
switch (curr.code) {
case 10: // always 0
break;
case 20: // always 0
break;
case 30: // elevation
break;
case 39: // thickness
entity.thickness = curr.value as number;
break;
case 40: // start width
break;
case 41: // end width
break;
case 70:
entity.shape = ((curr.value as number) & 1) !== 0;
entity.includesCurveFitVertices = ((curr.value as number) & 2) !== 0;
entity.includesSplineFitVertices = ((curr.value as number) & 4) !== 0;
entity.is3dPolyline = ((curr.value as number) & 8) !== 0;
entity.is3dPolygonMesh = ((curr.value as number) & 16) !== 0;
entity.is3dPolygonMeshClosed = ((curr.value as number) & 32) !== 0; // 32 = The polygon mesh is closed in the N direction
entity.isPolyfaceMesh = ((curr.value as number) & 64) !== 0;
entity.hasContinuousLinetypePattern = ((curr.value as number) & 128) !== 0;
break;
case 71: // Polygon mesh M vertex count
break;
case 72: // Polygon mesh N vertex count
break;
case 73: // Smooth surface M density
break;
case 74: // Smooth surface N density
break;
case 75: // Curves and smooth surface type
break;
case 210:
entity.extrusionDirection = helpers.parsePoint(scanner);
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
entity.vertices = parsePolylineVertices(scanner, curr);
return entity;
}
}
function parsePolylineVertices(scanner:DxfArrayScanner, curr:IGroup) {
const vertexParser = new VertexParser();
const vertices: IVertexEntity[] = [];
while (!scanner.isEOF()) {
if (curr.code === 0) {
if (curr.value === 'VERTEX') {
vertices.push(vertexParser.parseEntity(scanner, curr));
curr = scanner.lastReadGroup as IGroup;
} else if (curr.value === 'SEQEND') {
parseSeqEnd(scanner, curr);
break;
}
}
}
return vertices;
}
function parseSeqEnd(scanner:DxfArrayScanner, curr: IGroup) {
const entity = { type: curr.value } as IEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code == 0) break;
helpers.checkCommonEntityProperties(entity, curr, scanner);
curr = scanner.next();
}
return entity;
};
@@ -0,0 +1,44 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface ISolidEntity extends IEntity {
points: IPoint[];
extrusionDirection: IPoint;
}
export default class Solid implements IGeometry {
public ForEntityName = 'SOLID' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = { type: curr.value, points: [] as IPoint[] } as ISolidEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) break;
switch (curr.code) {
case 10:
entity.points[0] = helpers.parsePoint(scanner);
break;
case 11:
entity.points[1] = helpers.parsePoint(scanner);
break;
case 12:
entity.points[2] = helpers.parsePoint(scanner);
break;
case 13:
entity.points[3] = helpers.parsePoint(scanner);
break;
case 210:
entity.extrusionDirection = helpers.parsePoint(scanner);
break;
default: // check common entity attributes
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,86 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface ISplineEntity extends IEntity {
controlPoints?: IPoint[];
fitPoints?: IPoint[];
startTangent: IPoint;
endTangent: IPoint;
knotValues: number[];
closed: boolean;
periodic: boolean;
rational: boolean;
planar: boolean;
linear: boolean;
degreeOfSplineCurve: number;
numberOfKnots: number;
numberOfControlPoints: number;
numberOfFitPoints: number;
normalVector: IPoint;
}
export default class Spline implements IGeometry {
public ForEntityName = 'SPLINE' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = { type: curr.value } as ISplineEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) break;
switch (curr.code) {
case 10:
if (!entity.controlPoints) entity.controlPoints = [];
entity.controlPoints.push(helpers.parsePoint(scanner));
break;
case 11:
if (!entity.fitPoints) entity.fitPoints = [];
entity.fitPoints.push(helpers.parsePoint(scanner));
break;
case 12:
entity.startTangent = helpers.parsePoint(scanner);
break;
case 13:
entity.endTangent = helpers.parsePoint(scanner);
break;
case 40:
if (!entity.knotValues) entity.knotValues = [];
entity.knotValues.push(curr.value as number);
break;
case 70:
if (((curr.value as number) & 1) != 0) entity.closed = true;
if (((curr.value as number) & 2) != 0) entity.periodic = true;
if (((curr.value as number) & 4) != 0) entity.rational = true;
if (((curr.value as number) & 8) != 0) entity.planar = true;
if (((curr.value as number) & 16) != 0) {
entity.planar = true;
entity.linear = true;
}
break;
case 71:
entity.degreeOfSplineCurve = curr.value as number;
break;
case 72:
entity.numberOfKnots = curr.value as number;
break;
case 73:
entity.numberOfControlPoints = curr.value as number;
break;
case 74:
entity.numberOfFitPoints = curr.value as number;
break;
case 210:
entity.normalVector = helpers.parsePoint(scanner);
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,96 @@
import DxfArrayScanner, {IGroup} from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, {IEntity, IPoint} from './geomtry';
export interface ITextEntity extends IEntity {
startPoint: IPoint;
endPoint: IPoint;
textHeight: number;
xScale: number;
rotation: number;
obliqueAngle: number;
text: string;
fontName: string;
thickness: number;
halign: number;
valign: number;
flags: number;
}
export default class Text implements IGeometry {
public ForEntityName = 'TEXT' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
const entity = {type: curr.value} as ITextEntity;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) break;
switch (curr.code) {
case 1:
// 字符串本身
entity.text = curr.value as string;
break;
case 7: // 字体名称/文字样式名(可选;默认值 = 标准)
entity.fontName = curr.value as string;
break;
case 10:
// 第一对齐点(在 OCS 中) DXF:X 值;APP:三维点
entity.startPoint = helpers.parsePoint(scanner);
break;
case 11:
// 第二对齐点(在 OCS 中)(可选);
// DXFX 值;APP:三维点;
// 只有当 72 或 73 组的值非零时,该值才有意义(如果对正不是基线对正/左对正)
entity.endPoint = helpers.parsePoint(scanner);
break;
case 39:
// 厚度(可选;默认值 = 0
entity.thickness = curr.value as number;
break;
case 40:
// 文字高度
entity.textHeight = curr.value as number;
break;
case 41:
// 相对 X 比例因子 — 宽度(可选;默认值 = 1)
// 使用拟合类型的文字时,该值也将进行调整。
entity.xScale = curr.value as number;
break;
case 50:
// 文字旋转角度(可选;默认值 = 0)
entity.rotation = curr.value as number;
break;
case 51:
// 倾斜角度(可选;默认值 = 0)
entity.obliqueAngle = curr.value as number;
break;
case 71:
// 文字生成标志(可选,仅适用于 SHX 文字):
// 2 = 文字反向(在 X 轴方向镜像)
// 4 = 文字倒置(在 Y 轴方向镜像)
entity.flags = curr.value as number;
break;
// NOTE: 72和73没有11就没有意义(第二个对齐点)
case 72:
// 文字水平对正类型(可选;默认值 = 0)整数代码(非按位编码)
// 0 = 左对正;1 = 居中对正;2 = 右对正
// 3 = 对齐(如果垂直对齐 = 0)
// 4 = 中间(如果垂直对齐 = 0)
// 5 = 拟合(如果垂直对齐 = 0)
entity.halign = curr.value as number;
break;
case 73:
// 文字垂直对正类型(可选;默认值 = 0)整数代码(不是按位编码)
// 0 = 基线对正;1 = 底端对正;2 = 居中对正;3 = 顶端对正
entity.valign = curr.value as number;
break;
default:
// check common entity attributes
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
@@ -0,0 +1,79 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import * as helpers from '../ParseHelpers'
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface IVertexEntity extends IEntity, IPoint{
bulge: number;
curveFittingVertex: boolean;
curveFitTangent: boolean;
splineVertex: boolean;
splineControlPoint: boolean;
threeDPolylineVertex: boolean;
threeDPolylineMesh: boolean;
polyfaceMeshVertex: boolean;
faceA: number;
faceB: number;
faceC: number;
faceD: number;
}
export default class Vertex implements IGeometry {
public ForEntityName= 'VERTEX' as const;
public parseEntity(scanner: DxfArrayScanner, curr: IGroup) {
var entity = { type: curr.value } as IVertexEntity;
curr = scanner.next();
while(!scanner.isEOF()) {
if(curr.code === 0) break;
switch(curr.code) {
case 10: // X
entity.x = curr.value as number;
break;
case 20: // Y
entity.y = curr.value as number;
break;
case 30: // Z
entity.z = curr.value as number;
break;
case 40: // start width
break;
case 41: // end width
break;
case 42: // bulge
if(curr.value != 0) entity.bulge = curr.value as number;
break;
case 70: // flags
entity.curveFittingVertex = ((curr.value as number) & 1) !== 0;
entity.curveFitTangent = ((curr.value as number) & 2) !== 0;
entity.splineVertex = ((curr.value as number) & 8) !== 0;
entity.splineControlPoint = ((curr.value as number) & 16) !== 0;
entity.threeDPolylineVertex = ((curr.value as number) & 32) !== 0;
entity.threeDPolylineMesh = ((curr.value as number) & 64) !== 0;
entity.polyfaceMeshVertex = ((curr.value as number) & 128) !== 0;
break;
case 50: // curve fit tangent direction
break;
case 71: // polyface mesh vertex index
entity.faceA = curr.value as number;
break;
case 72: // polyface mesh vertex index
entity.faceB = curr.value as number;
break;
case 73: // polyface mesh vertex index
entity.faceC = curr.value as number;
break;
case 74: // polyface mesh vertex index
entity.faceD = curr.value as number;
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}