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,265 @@
/**
* AutoCad files sometimes use an indexed color value between 1 and 255 inclusive.
* Each value corresponds to a color. index 1 is red, that is 16711680 or 0xFF0000.
* index 0 and 256, while included in this array, are actually reserved for inheritance
* values in AutoCad so they should not be used for index color lookups.
*/
export default [
0,
16711680,
16776960,
65280,
65535,
255,
16711935,
16777215,
8421504,
12632256,
16711680,
16744319,
13369344,
13395558,
10027008,
10046540,
8323072,
8339263,
4980736,
4990502,
16727808,
16752511,
13382400,
13401958,
10036736,
10051404,
8331008,
8343359,
4985600,
4992806,
16744192,
16760703,
13395456,
13408614,
10046464,
10056268,
8339200,
8347455,
4990464,
4995366,
16760576,
16768895,
13408512,
13415014,
10056192,
10061132,
8347392,
8351551,
4995328,
4997670,
16776960,
16777087,
13421568,
13421670,
10000384,
10000460,
8355584,
8355647,
5000192,
5000230,
12582656,
14679935,
10079232,
11717734,
7510016,
8755276,
6258432,
7307071,
3755008,
4344870,
8388352,
12582783,
6736896,
10079334,
5019648,
7510092,
4161280,
6258495,
2509824,
3755046,
4194048,
10485631,
3394560,
8375398,
2529280,
6264908,
2064128,
5209919,
1264640,
3099686,
65280,
8388479,
52224,
6736998,
38912,
5019724,
32512,
4161343,
19456,
2509862,
65343,
8388511,
52275,
6737023,
38950,
5019743,
32543,
4161359,
19475,
2509871,
65407,
8388543,
52326,
6737049,
38988,
5019762,
32575,
4161375,
19494,
2509881,
65471,
8388575,
52377,
6737074,
39026,
5019781,
32607,
4161391,
19513,
2509890,
65535,
8388607,
52428,
6737100,
39064,
5019800,
32639,
4161407,
19532,
2509900,
49151,
8380415,
39372,
6730444,
29336,
5014936,
24447,
4157311,
14668,
2507340,
32767,
8372223,
26316,
6724044,
19608,
5010072,
16255,
4153215,
9804,
2505036,
16383,
8364031,
13260,
6717388,
9880,
5005208,
8063,
4149119,
4940,
2502476,
255,
8355839,
204,
6710988,
152,
5000344,
127,
4145023,
76,
2500172,
4129023,
10452991,
3342540,
8349388,
2490520,
6245528,
2031743,
5193599,
1245260,
3089996,
8323327,
12550143,
6684876,
10053324,
4980888,
7490712,
4128895,
6242175,
2490444,
3745356,
12517631,
14647295,
10027212,
11691724,
7471256,
8735896,
6226047,
7290751,
3735628,
4335180,
16711935,
16744447,
13369548,
13395660,
9961624,
9981080,
8323199,
8339327,
4980812,
4990540,
16711871,
16744415,
13369497,
13395634,
9961586,
9981061,
8323167,
8339311,
4980793,
4990530,
16711807,
16744383,
13369446,
13395609,
9961548,
9981042,
8323135,
8339295,
4980774,
4990521,
16711743,
16744351,
13369395,
13395583,
9961510,
9981023,
8323103,
8339279,
4980755,
4990511,
3355443,
5987163,
8684676,
11382189,
14079702,
16777215
];
@@ -0,0 +1,31 @@
// 维度样式变量可以在DIMSTYLE表中使用,也可以在XDATA中的Dimension实体样式覆盖中使用。
const codes = new Map([
[140, "DIMTXT"],
[142, "DIMTSZ"],
[144, "DIMLFAC"],
[147, "DIMGAP"],
[173, "DIMSAH"],
[175, "DIMSOXD"],
[176, "DIMCLRD"],
[177, "DIMCLRE"],
[178, "DIMCLRT"],
[271, "DIMDEC"],
[278 ,"DIMDSEP"],
[281, "DIMSD1"],
[282, "DIMSD2"],
[3, "DIMPOST"],
[40, "DIMSCALE"],
[41, "DIMASZ"],
[42, "DIMEXO"],
[44, "DIMEXE"],
[45, "DIMRND"],
[46, "DIMDLE"],
[5, "DIMBLK"],
[6, "DIMBLK1"],
[7, "DIMBLK2"],
[75, "DIMSE1"],
[76, "DIMSE2"],
[78, "DIMZIN"],
])
export default codes
@@ -0,0 +1,168 @@
import log from '@/utils/log/Logger';
export interface IGroup {
code: number;
value: number | string | boolean;
}
/**
* DxfArrayScanner
*
* 基于AutoCAD 2012 DXF参考
* http://images.autodesk.com/adsk/files/autocad_2012_pdf_dxf-reference_enu.pdf
*
* 读取表示dxf文件行的数组。接受一个数组,并提供一个简单的接口来提取组代码和值对。
* @param data - 一个数组,其中每个元素表示DXF文件中的一行
* @constructor
*/
export default class DxfArrayScanner {
private _pointer = 0;
private _eof = false;
public lastReadGroup: IGroup | undefined;
private _data: string[];
constructor(data: string[]) {
this._data = data;
// console.log('this._data:', this._data);
}
/**
* 从数组中获取下一组(代码、值)。组是数组中两个连续的元素。第一个是代码,第二个是值。
* @returns {{code: Number}|*}
*/
public next() {
if (!this.hasNext()) {
// console.log('this._eof:', this._eof);
// console.log('this._pointer:', this._pointer);
if (!this._eof){
log.error('输入结束异常:EOF组未在文件结束前读取。代码结束: ' + this._data[this._pointer]);
}else{
log.error('在读取完 EOF 组后无法调用 `next`');
}
this._eof = true;
return { code: 0, value: 'EOF' };
}
const group = {
code: parseInt(this._data[this._pointer])
} as IGroup;
if(isNaN(group.code)){
// 回退code: 如果当前code为NaN,很可能是因为当前行和上一行是连贯数据,被libreDWG解析为两行,所以要回退code采用同一个code解析
this._pointer--;
group.code = parseInt(this._data[this._pointer - 1]);
// TODO: 或者使用跳过当前行操作?
// this._pointer++;
// return this.next();
}
this._pointer++;
group.value = parseGroupValue(group.code, this._data[this._pointer].trim());
this._pointer++;
// if (group.code === 0 && (group.value === 'EOF' || (group.value === 'ENDSEC' && !this.hasNext()))) this._eof = true;
if (group.code === 0 && group.value === 'EOF') this._eof = true;
this.lastReadGroup = group;
return group;
}
public peek() {
if (!this.hasNext()) {
if (!this._eof)
throw new Error('输入结束异常:EOF组未在文件结束前读取。代码结束: ' + this._data[this._pointer]);
else
throw new Error('Cannot call \'next\' after EOF group has been read');
}
const group = {
code: parseInt(this._data[this._pointer])
} as IGroup;
group.value = parseGroupValue(group.code, this._data[this._pointer + 1].trim());
return group;
}
public rewind(numberOfGroups = 1) {
this._pointer = this._pointer - numberOfGroups * 2;
}
/**
* 如果存在另一个代码/值对(数组中的2个元素)则返回true。
* @returns {boolean}
*/
public hasNext() {
// 检查我们是否读过EOF组代码
if (this._eof) {
return false;
}
// 我们需要确保有两条线路可用
return this._pointer <= this._data.length - 2;
}
/**
* 如果扫描器位于数组的末尾,则返回true
* @returns {boolean}
*/
public isEOF() {
return this._eof;
}
}
/**
* 将值解析为其适当类型。
* 参见本文件顶部提供的AutoCAD DXF 2012参考的第3-10页。
*
* @param code
* @param value
* @returns {*}
*/
function parseGroupValue(code: number, value: string) {
if (code <= 9) return value;
if (code >= 10 && code <= 59) return parseFloat(value);
if (code >= 60 && code <= 99) return parseInt(value);
if (code >= 100 && code <= 109) return value;
if (code >= 110 && code <= 149) return parseFloat(value);
if (code >= 160 && code <= 179) return parseInt(value);
if (code >= 210 && code <= 239) return parseFloat(value);
if (code >= 270 && code <= 289) return parseInt(value);
if (code >= 290 && code <= 299) return parseBoolean(value as '0' | '1');
if (code >= 300 && code <= 369) return value;
if (code >= 370 && code <= 389) return parseInt(value);
if (code >= 390 && code <= 399) return value;
if (code >= 400 && code <= 409) return parseInt(value);
if (code >= 410 && code <= 419) return value;
if (code >= 420 && code <= 429) return parseInt(value);
if (code >= 430 && code <= 439) return value;
if (code >= 440 && code <= 459) return parseInt(value);
if (code >= 460 && code <= 469) return parseFloat(value);
if (code >= 470 && code <= 481) return value;
if (code === 999) return value;
if (code >= 1000 && code <= 1009) return value;
if (code >= 1010 && code <= 1059) return parseFloat(value);
if (code >= 1060 && code <= 1071) return parseInt(value);
//log.warn('group代码没有定义的类型: %j', { code: code, value: value });
return value;
}
/**
* 根据1或0值解析布尔值
* @param str
* @returns {boolean}
*/
function parseBoolean(str: '0' | '1') {
if (str === '0') return false;
if (str === '1') return true;
return str;
// throw TypeError('String \'' + str + '\' cannot be cast to Boolean type');
}
+980
View File
@@ -0,0 +1,980 @@
import {Readable} from 'stream';
import DxfArrayScanner, {IGroup} from './DxfArrayScanner';
import AUTO_CAD_COLOR_INDEX from './AutoCadColorIndex';
import dimStyleCodes from './DimStyleCodes';
import Face from './entities/3dface';
import Arc from './entities/arc';
import AttDef from './entities/attdef';
import Attribute from './entities/attribute'
import Circle from './entities/circle';
import Dimension from './entities/dimension';
import Ellipse from './entities/ellipse';
import Hatch from './entities/hatch';
import Insert from './entities/insert';
import Line from './entities/line';
import LWPolyline from './entities/lwpolyline';
import MText from './entities/mtext';
import Point from './entities/point';
import Polyline from './entities/polyline';
import Solid from './entities/solid';
import Spline from './entities/spline';
import Text from './entities/text';
import Vertex from './entities/vertex';
import log from '@/utils/log/Logger';
import IGeometry, {EntityName, IEntity, IPoint} from './entities/geomtry';
export interface IBlock {
entities: IEntity[];
type: number;
ownerHandle: string;
// entities: any[];
xrefPath: string;
name: string;
name2: string;
handle: string;
layer: string;
position: IPoint;
paperSpace: boolean;
}
export interface IViewPort {
name: string;
lowerLeftCorner: IPoint;
upperRightCorner: IPoint;
center: IPoint;
snapBasePoint: IPoint;
snapSpacing: IPoint;
gridSpacing: IPoint;
viewDirectionFromTarget: IPoint;
viewTarget: IPoint;
lensLength: number;
frontClippingPlane: string | number | boolean;
backClippingPlane: string | number | boolean;
viewHeight: number;
snapRotationAngle: number;
viewTwistAngle: number;
orthographicType: string;
ucsOrigin: IPoint;
ucsXAxis: IPoint;
ucsYAxis: IPoint;
renderMode: string;
defaultLightingType: string;
defaultLightingOn: string;
ownerHandle: string;
ambientColor: number;
viewMode: string;
}
export interface IViewPortTableDefinition {
tableRecordsProperty: 'viewPorts';
tableName: 'viewPort';
dxfSymbolName: 'VPORT';
parseTableRecords(): IViewPort[];
}
export interface ILineType {
name: string;
description: string;
pattern: string[];
patternLength: number;
}
export interface ILineTypeTableDefinition {
tableRecordsProperty: 'lineTypes';
tableName: 'lineType';
dxfSymbolName: 'LTYPE';
parseTableRecords(): Record<string, ILineType>;
}
export interface ILayer {
name: string;
visible: boolean;
colorIndex: number;
color: number;
frozen: boolean;
}
export interface ILayerTableDefinition {
tableRecordsProperty: 'layers';
tableName: 'layer';
dxfSymbolName: 'LAYER';
parseTableRecords(): Record<string, ILayer>;
}
export interface ITableDefinitions {
VPORT: IViewPortTableDefinition;
LTYPE: ILineTypeTableDefinition;
LAYER: ILayerTableDefinition;
DIMSTYLE: any;
STYLE: any;
}
export interface IBaseTable {
handle: string;
ownerHandle: string;
}
export interface IViewPortTable extends IBaseTable {
viewPorts: IViewPort[];
}
export interface ILayerTypesTable extends IBaseTable {
lineTypes: Record<string, ILineType>;
}
export interface ILayersTable extends IBaseTable {
layers: Record<string, ILayer>;
}
export interface ITables {
viewPort: IViewPortTable;
lineType: ILayerTypesTable;
layer: ILayersTable;
}
export type ITable = IViewPortTable | ILayerTypesTable | ILayersTable;
export interface IDxf {
header: Record<string, IPoint | number>;
entities: IEntity[];
blocks: Record<string, IBlock>;
tables: ITables;
}
function registerDefaultEntityHandlers(dxfParser: DxfParser) {
// 这里支持的实体(一些实体代码仍然被重构到这个流中)
dxfParser.registerEntityHandler(Face);
dxfParser.registerEntityHandler(Arc);
dxfParser.registerEntityHandler(AttDef);
dxfParser.registerEntityHandler(Attribute);
dxfParser.registerEntityHandler(Circle);
dxfParser.registerEntityHandler(Dimension);
dxfParser.registerEntityHandler(Ellipse);
dxfParser.registerEntityHandler(Hatch);
dxfParser.registerEntityHandler(Insert);
dxfParser.registerEntityHandler(Line);
dxfParser.registerEntityHandler(LWPolyline);
dxfParser.registerEntityHandler(MText);
dxfParser.registerEntityHandler(Point);
dxfParser.registerEntityHandler(Polyline);
dxfParser.registerEntityHandler(Solid);
dxfParser.registerEntityHandler(Spline);
dxfParser.registerEntityHandler(Text);
dxfParser.registerEntityHandler(Vertex);
}
export default class DxfParser {
private _entityHandlers = {} as Record<EntityName, IGeometry>;
constructor() {
registerDefaultEntityHandlers(this);
}
public parse(source: string | Readable) {
if (typeof source === 'string') {
return this._parse(source);
} else {
log.error('无法读取dxf源的类型: `' + typeof (source));
return null;
}
}
public registerEntityHandler(handlerType: new () => IGeometry) {
const instance = new handlerType();
this._entityHandlers[instance.ForEntityName] = instance;
}
public parseSync(source: string) {
return this.parse(source);
}
public parseStream(stream: Readable) {
let dxfString = "";
const self = this;
return new Promise<IDxf>((res, rej) => {
stream.on('data', (chunk) => {
dxfString += chunk;
});
stream.on('end', () => {
try {
res(self._parse(dxfString));
} catch (err) {
rej(err);
}
});
stream.on('error', (err) => {
rej(err);
});
});
}
private _parse(dxfString: string) {
const dxf = {} as IDxf;
let lastHandle = 0;
const dxfLinesArray = dxfString.split(/\r\n|\r|\n/g);
const scanner = new DxfArrayScanner(dxfLinesArray);
if (!scanner.hasNext()) {
log.error('当前 CAD 文件为空文件');
return dxf;
}
const self = this;
let curr: IGroup;
function parseAll() {
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0 && curr.value === 'SECTION') {
curr = scanner.next();
// 确保我们读的是段代码
if (curr.code !== 2) {
log.error(`Unexpected code ${debugCode(curr) } after 0:SECTION`);
curr = scanner.next();
continue;
}
if (curr.value === 'HEADER') {
dxf.header = parseHeader();
} else if (curr.value === 'BLOCKS') {
dxf.blocks = parseBlocks();
} else if (curr.value === 'ENTITIES') {
dxf.entities = parseEntities(false);
} else if (curr.value === 'TABLES') {
dxf.tables = parseTables();
} else if (curr.value === 'EOF') {
log.debug('EOF');
} else {
log.warn('Skipping section ' + curr.value);
}
} else {
curr = scanner.next();
}
// If is a new section
}
}
/**
*
* @return {object} header
*/
function parseHeader() {
// interesting variables:
// $ACADVER, $VIEWDIR, $VIEWSIZE, $VIEWCTR, $TDCREATE, $TDUPDATE
// http://www.autodesk.com/techpubs/autocad/acadr14/dxf/header_section_al_u05_c.htm
// Also see VPORT table entries
let currVarName = null as null | string;
let currVarValue = null as null | IPoint | number;
const header = {} as Record<string, IPoint | number>;
// loop through header variables
curr = scanner.next();
while (true) {
if (groupIs(curr, 0, 'ENDSEC')) {
if (currVarName) header[currVarName as string] = currVarValue!;
break;
} else if (curr.code === 9) {
if (currVarName) header[currVarName as string] = currVarValue!;
currVarName = curr.value as string;
// Filter here for particular variables we are interested in
} else {
if (curr.code === 10) {
currVarValue = {x: curr.value as number} as IPoint;
} else if (curr.code === 20) {
(currVarValue as IPoint).y = curr.value as number;
} else if (curr.code === 30) {
(currVarValue as IPoint).z = curr.value as number;
} else {
currVarValue = curr.value as number;
}
}
curr = scanner.next();
}
// console.log(util.inspect(header, { colors: true, depth: null }));
curr = scanner.next(); // swallow up ENDSEC
return header;
}
function parseBlocks() {
const blocks = {} as Record<string, IBlock>;
curr = scanner.next();
while (curr.value !== 'EOF') {
if (groupIs(curr, 0, 'ENDSEC')) {
break;
}
if (groupIs(curr, 0, 'BLOCK')) {
const block = parseBlock();
ensureHandle(block);
if (!block.name) {
// log.error('block with handle "' + block.handle + '" is missing a name.');
// block.name = 'InvalidBlockName-' + block.ownerHandle;
}else{
blocks[block.name] = block;
}
} else {
logUnhandledGroup(curr);
curr = scanner.next();
}
}
return blocks;
}
function parseBlock() {
const block = {} as IBlock;
curr = scanner.next();
while (curr.value !== 'EOF') {
switch (curr.code) {
case 1:
block.xrefPath = curr.value as string;
curr = scanner.next();
break;
case 2:
block.name = curr.value as string;
curr = scanner.next();
break;
case 3:
block.name2 = curr.value as string;
curr = scanner.next();
break;
case 5:
block.handle = curr.value as string;
curr = scanner.next();
break;
case 8:
block.layer = curr.value as string;
curr = scanner.next();
break;
case 10:
block.position = parsePoint(curr);
curr = scanner.next();
break;
case 67:
block.paperSpace = (curr.value && curr.value == 1) ? true : false;
curr = scanner.next();
break;
case 70:
if (curr.value != 0) {
//if(curr.value & BLOCK_ANONYMOUS_FLAG) console.log(' Anonymous block');
//if(curr.value & BLOCK_NON_CONSTANT_FLAG) console.log(' Non-constant attributes');
//if(curr.value & BLOCK_XREF_FLAG) console.log(' Is xref');
//if(curr.value & BLOCK_XREF_OVERLAY_FLAG) console.log(' Is xref overlay');
//if(curr.value & BLOCK_EXTERNALLY_DEPENDENT_FLAG) console.log(' Is externally dependent');
//if(curr.value & BLOCK_RESOLVED_OR_DEPENDENT_FLAG) console.log(' Is resolved xref or dependent of an xref');
//if(curr.value & BLOCK_REFERENCED_XREF) console.log(' This definition is a referenced xref');
block.type = curr.value as number;
}
curr = scanner.next();
break;
case 100:
// ignore class markers
curr = scanner.next();
break;
case 330:
block.ownerHandle = curr.value as string;
curr = scanner.next();
break;
case 0:
if (curr.value == 'ENDBLK')
break;
block.entities = parseEntities(true);
break;
default:
logUnhandledGroup(curr);
curr = scanner.next();
}
if (groupIs(curr, 0, 'ENDBLK')) {
curr = scanner.next();
break;
}
}
return block;
}
/**
* parseTables
* @return {Object} Object representing tables
*/
function parseTables() {
const tables = {} as ITables;
curr = scanner.next();
while (curr.value !== 'EOF') {
if (groupIs(curr, 0, 'ENDSEC'))
break;
if (groupIs(curr, 0, 'TABLE')) {
curr = scanner.next();
const tableDefinition = tableDefinitions[curr.value as keyof ITableDefinitions];
if (tableDefinition) {
tables[tableDefinitions[curr.value as keyof ITableDefinitions].tableName] = parseTable(curr);
} else {
// log.debug('Unhandled Table ' + curr.value);
}
} else {
// else ignored
curr = scanner.next();
}
}
curr = scanner.next();
return tables;
}
const END_OF_TABLE_VALUE = 'ENDTAB';
function parseTable<T extends IBaseTable = ITable>(group: IGroup) {
const tableDefinition = tableDefinitions[group.value as keyof ITableDefinitions];
const table = {} as T;
let expectedCount = 0;
curr = scanner.next();
while (!groupIs(curr, 0, END_OF_TABLE_VALUE)) {
switch (curr.code) {
case 5:
table.handle = curr.value as string;
curr = scanner.next();
break;
case 330:
table.ownerHandle = curr.value as string;
curr = scanner.next();
break;
case 100:
if (curr.value === 'AcDbSymbolTable') {
// ignore
curr = scanner.next();
} else {
logUnhandledGroup(curr);
curr = scanner.next();
}
break;
case 70:
expectedCount = curr.value as number;
curr = scanner.next();
break;
case 0:
if (curr.value === tableDefinition.dxfSymbolName) {
table[tableDefinition.tableRecordsProperty] = tableDefinition.parseTableRecords();
} else {
logUnhandledGroup(curr);
curr = scanner.next();
}
break;
default:
logUnhandledGroup(curr);
curr = scanner.next();
}
}
const tableRecords = table[tableDefinition.tableRecordsProperty];
if (tableRecords) {
let actualCount = (() => {
if (tableRecords.constructor === Array) {
return tableRecords.length;
} else if (typeof (tableRecords) === 'object') {
return Object.keys(tableRecords).length;
}
return undefined;
})();
if (expectedCount !== actualCount) log.warn('Parsed ' + actualCount + ' ' + tableDefinition.dxfSymbolName + '\'s but expected ' + expectedCount);
}
curr = scanner.next();
return table;
}
function parseViewPortRecords() {
const viewPorts = [] as IViewPort[]; // 多个表项可以具有相同的名称,表示多个视区配置
let viewPort = {} as IViewPort;
curr = scanner.next();
while (!groupIs(curr, 0, END_OF_TABLE_VALUE)) {
switch (curr.code) {
case 2: // 视口名
viewPort.name = curr.value as string;
curr = scanner.next();
break;
case 10: // 视口的左下角
viewPort.lowerLeftCorner = parsePoint(curr);
curr = scanner.next();
break;
case 11: // 视口右上角
viewPort.upperRightCorner = parsePoint(curr);
curr = scanner.next();
break;
case 12: // 视口中心点(在 DCS 中)
viewPort.center = parsePoint(curr);
curr = scanner.next();
break;
case 13: // 捕捉基点(在 DCS 中)
viewPort.snapBasePoint = parsePoint(curr);
curr = scanner.next();
break;
case 14: // 捕捉间距 X 和 Y
viewPort.snapSpacing = parsePoint(curr);
curr = scanner.next();
break;
case 15: // 栅格间距 X 和 Y
viewPort.gridSpacing = parsePoint(curr);
curr = scanner.next();
break;
case 16: // 相对于目标点的观察方向(在 WCS 中)
viewPort.viewDirectionFromTarget = parsePoint(curr);
curr = scanner.next();
break;
case 17: //观察目标点(在 WCS 中)
viewPort.viewTarget = parsePoint(curr);
curr = scanner.next();
break;
case 42: // 焦距
viewPort.lensLength = curr.value as number;
curr = scanner.next();
break;
case 43: //前向剪裁平面(距目标点的偏移)
viewPort.frontClippingPlane = curr.value;
curr = scanner.next();
break;
case 44: // 后向剪裁平面(距目标点的偏移)
viewPort.backClippingPlane = curr.value;
curr = scanner.next();
break;
case 45: // 视图高度
viewPort.viewHeight = curr.value as number;
curr = scanner.next();
break;
case 50: // 捕捉旋转角度
viewPort.snapRotationAngle = curr.value as number;
curr = scanner.next();
break;
case 51: // 视图扭转角度
viewPort.viewTwistAngle = curr.value as number;
curr = scanner.next();
break;
case 71: // 视图模式(参见 VIEWMODE 系统变量)
viewPort.viewMode = curr.value as string;
curr = scanner.next();
break;
case 79: // UCS 的正交类型 0 = UCS 不正交 1 = 俯视图;2 = 仰视图 3 = 主视图;4 = 后视图 5 = 左视图;6 = 右视图
viewPort.orthographicType = curr.value as string;
curr = scanner.next();
break;
case 110: // UCS 原点(在 DCS 中)
viewPort.ucsOrigin = parsePoint(curr);
curr = scanner.next();
break;
case 111: // UCS X 轴
viewPort.ucsXAxis = parsePoint(curr);
curr = scanner.next();
break;
case 112: // UCS Y 轴
viewPort.ucsYAxis = parsePoint(curr);
curr = scanner.next();
break;
case 281:
/*
* 渲染模式: 0 = 二维优化(传统二维) 1 = 线框图 2 = 隐藏线 3 = 平面着色 4 = 体着色 5 = 带线框平面着色 6 = 带线框体着色
* 所有非二维优化渲染模式均使用新三维图形管道。这些值直接与 SHADEMODE 命令和 AcDbAbstractViewTableRecord::RenderMode 枚举相对应
*/
viewPort.renderMode = curr.value as string;
curr = scanner.next();
break;
case 292:
viewPort.defaultLightingOn = curr.value as string;
curr = scanner.next();
break;
case 330:
viewPort.ownerHandle = curr.value as string;
curr = scanner.next();
break;
case 63: // These are all ambient color. Perhaps should be a gradient when multiple are set.
case 421:
case 431:
viewPort.ambientColor = curr.value as number;
curr = scanner.next();
break;
case 0:
// New ViewPort
if (curr.value === 'VPORT') {
viewPorts.push(viewPort);
viewPort = {} as IViewPort;
curr = scanner.next();
}
break;
default:
logUnhandledGroup(curr);
curr = scanner.next();
break;
}
}
// Note: do not call scanner.next() here,
// parseTable() needs the current group
viewPorts.push(viewPort);
return viewPorts;
}
function parseLineTypes() {
const ltypes = {} as Record<string, ILineType>;
let ltype = {} as ILineType;
let length = 0;
let ltypeName: string;
curr = scanner.next();
while (!groupIs(curr, 0, 'ENDTAB')) {
switch (curr.code) {
case 2:
ltype.name = curr.value as string;
ltypeName = curr.value as string;
curr = scanner.next();
break;
case 3:
ltype.description = curr.value as string;
curr = scanner.next();
break;
case 73: // Number of elements for this line type (dots, dashes, spaces);
length = curr.value as number;
if (length > 0) ltype.pattern = [];
curr = scanner.next();
break;
case 40: // total pattern length
ltype.patternLength = curr.value as number;
curr = scanner.next();
break;
case 49:
ltype.pattern.push(curr.value as string);
curr = scanner.next();
break;
case 0:
if (length > 0 && length !== ltype.pattern.length) log.warn('lengths do not match on LTYPE pattern');
ltypes[ltypeName!] = ltype;
ltype = {} as ILineType;
curr = scanner.next();
break;
default:
curr = scanner.next();
}
}
ltypes[ltypeName!] = ltype;
return ltypes;
}
function parseLayers() {
const layers = {} as Record<string, ILayer>;
let layer = {} as ILayer;
let layerName: string | undefined;
curr = scanner.next();
while (!groupIs(curr, 0, 'ENDTAB')) {
switch (curr.code) {
case 2: // layer name
layer.name = curr.value as string;
layerName = curr.value as string;
curr = scanner.next();
break;
case 62: // color, visibility
// @ts-ignore
layer.visible = curr.value >= 0;
// TODO 0 and 256 are BYBLOCK and BYLAYER respectively. Need to handle these values for layers?.
layer.colorIndex = Math.abs(curr.value as number);
layer.color = getAcadColor(layer.colorIndex as number);
curr = scanner.next();
break;
case 70: // frozen layer
layer.frozen = (((curr.value as number) & 1) != 0 || ((curr.value as number) & 2) != 0);
curr = scanner.next();
break;
case 0:
// New Layer
if (curr.value === 'LAYER') {
layers[layerName!] = layer;
layer = {} as ILayer;
layerName = undefined;
curr = scanner.next();
}
break;
default:
logUnhandledGroup(curr);
curr = scanner.next();
break;
}
}
// Note: do not call scanner.next() here,
// parseLayerTable() needs the current group
layers[layerName!] = layer;
return layers;
}
function parseDimStyles() {
let dimStyles = {},
styleName: string | undefined,
style: any = {};
curr = scanner.next();
while (!groupIs(curr, 0, 'ENDTAB')) {
if (dimStyleCodes.has(curr.code)) {
style[dimStyleCodes.get(curr.code) as string] = curr.value
curr = scanner.next();
} else {
switch (curr.code) {
case 2: // style name
style.name = curr.value;
styleName = curr.value as string;
curr = scanner.next();
break;
case 0:
// New style
if (curr.value === 'DIMSTYLE') {
// @ts-ignore
dimStyles[styleName] = style;
style = {};
styleName = undefined;
curr = scanner.next();
}
break;
default:
logUnhandledGroup(curr);
curr = scanner.next();
break;
}
}
}
// Note: do not call scanner.next() here,
// parseLayerTable() needs the current group
dimStyles[styleName as string] = style;
return dimStyles;
}
const parseStyles = function () {
const styles = {};
let style: any = {};
let styleName;
curr = scanner.next();
while (!groupIs(curr, 0, END_OF_TABLE_VALUE)) {
switch (curr.code) {
case 100:
style.subClassMarker = curr.value;
curr = scanner.next();
break;
case 2:
style.styleName = curr.value;
styleName = curr.value;
curr = scanner.next();
break;
case 70:
style.standardFlag = curr.value;
curr = scanner.next();
break;
case 40:
style.fixedTextHeight = curr.value;
curr = scanner.next();
break;
case 41:
style.widthFactor = curr.value;
curr = scanner.next();
break;
case 50:
style.obliqueAngle = curr.value;
curr = scanner.next();
break;
case 71:
style.textGenerationFlag = curr.value;
curr = scanner.next();
break;
case 42:
style.lastHeight = curr.value;
curr = scanner.next();
break;
case 3:
style.font = curr.value;
curr = scanner.next();
break;
case 4:
style.bigFont = curr.value;
curr = scanner.next();
break;
case 1071:
style.extendedFont = curr.value;
curr = scanner.next();
break;
case 0:
if (curr.value === 'STYLE') {
styles[styleName] = style;
style = {};
styleName = undefined;
curr = scanner.next();
}
break;
default:
logUnhandledGroup(curr);
curr = scanner.next();
break;
}
}
styles[styleName] = style;
return styles;
};
const tableDefinitions = {
VPORT: {
tableRecordsProperty: 'viewPorts',
tableName: 'viewPort',
dxfSymbolName: 'VPORT',
parseTableRecords: parseViewPortRecords
},
LTYPE: {
tableRecordsProperty: 'lineTypes',
tableName: 'lineType',
dxfSymbolName: 'LTYPE',
parseTableRecords: parseLineTypes
},
LAYER: {
tableRecordsProperty: 'layers',
tableName: 'layer',
dxfSymbolName: 'LAYER',
parseTableRecords: parseLayers
},
DIMSTYLE: {
tableRecordsProperty: 'dimStyles',
tableName: 'dimstyle',
dxfSymbolName: 'DIMSTYLE',
parseTableRecords: parseDimStyles
},
STYLE: {
tableRecordsProperty: 'styles',
tableName: 'style',
dxfSymbolName: 'STYLE',
parseTableRecords: parseStyles,
},
} as ITableDefinitions;
/**
* 在解析器首次读取0:ENTITIES组后调用。扫描器应该已经在第一个实体的开始。
* @return {Array} the resulting entities
*/
function parseEntities(forBlock: boolean) {
const entities = [] as IEntity[];
const endingOnValue = forBlock ? 'ENDBLK' : 'ENDSEC';
if (!forBlock) {
curr = scanner.next();
}
while (true) {
if (curr.code === 0) {
if (curr.value === endingOnValue) {
break;
}
const handler = self._entityHandlers[curr.value as EntityName];
if (handler != null) {
const entity = handler.parseEntity(scanner, curr);
curr = scanner.lastReadGroup!;
ensureHandle(entity);
entities.push(entity);
} else {
// log.debug('Unhandled entity ' + curr.value);
curr = scanner.next();
continue;
}
} else {
// ignored lines from unsupported entity
curr = scanner.next();
}
}
if (endingOnValue == 'ENDSEC') curr = scanner.next(); // swallow up ENDSEC, but not ENDBLK
return entities;
}
/**
* 解析一个2D或3D点.
* 如果它是3D的,将其作为具有x, y和(有时)z属性的对象返回。
* 假定当前组是被读入点的x,并且scanner.next()将返回y。
* 解析器将自动确定是否存在z点。
* @return {Object} 二维或三维点作为具有 x、y[,z] 的对象
*/
function parsePoint(curr: IGroup) {
const point = {} as IPoint;
let code = curr.code;
point.x = curr.value as number;
code += 10;
curr = scanner.next();
if (curr.code != code)
throw new Error('Expected code for point value to be ' + code +
' but got ' + curr.code + '.');
point.y = curr.value as number;
code += 10;
curr = scanner.next();
if (curr.code != code) {
scanner.rewind();
return point;
}
point.z = curr.value as number;
return point;
}
function ensureHandle(entity: IEntity | IBlock) {
if (!entity) throw new TypeError('entity cannot be undefined or null');
if (!entity.handle) entity.handle = lastHandle++;
}
parseAll();
return dxf;
}
}
function groupIs(group: IGroup, code: number, value: string | number | boolean) {
return group.code === code && group.value === value;
}
// @ts-ignore
function logUnhandledGroup(curr: IGroup) {
// log.debug('unhandled group ' + debugCode(curr));
}
function debugCode(curr: IGroup) {
return curr.code + ':' + curr.value;
}
/**
* Returns the truecolor value of the given AutoCad color index value
* @return {Number} truecolor value as a number
*/
function getAcadColor(index: number) {
return AUTO_CAD_COLOR_INDEX[index];
}
// const BLOCK_ANONYMOUS_FLAG = 1;
// const BLOCK_NON_CONSTANT_FLAG = 2;
// const BLOCK_XREF_FLAG = 4;
// const BLOCK_XREF_OVERLAY_FLAG = 8;
// const BLOCK_EXTERNALLY_DEPENDENT_FLAG = 16;
// const BLOCK_RESOLVED_OR_DEPENDENT_FLAG = 32;
// const BLOCK_REFERENCED_XREF = 64;
/* Notes */
// Code 6 of an entity indicates inheritance of properties (eg. color).
// BYBLOCK means inherits from block
// BYLAYER (default) mean inherits from layer
@@ -0,0 +1,121 @@
import log from '@/utils/log/Logger';
export default class ExtendedDataParser {
private appName: null;
private appNameWarningShown: boolean;
private lastString: any;
private sectionStack: { values: any[] }[];
private failure: boolean;
constructor() {
this.appName = null
this.appNameWarningShown = false
this.lastString = null
this.sectionStack = [this._CreateSection()]
this.failure = false
}
/**
* Feed next token.
* @return {boolean} 如果应该为此令牌创建新的解析器实例,则为True。
*/
Feed(curr) {
if (!this.appName) {
if (curr.code == 1001) {
this.appName = curr.value
return false
}
if (!this.appNameWarningShown) {
this.appNameWarningShown = true
log.warn("XDATA部分不以应用程序名称开头")
}
return false
}
if (curr.code == 1001) {
return true
}
if (this.failure) {
/* Ignore all the rest content in case of parsing failure. */
return false
}
if (curr.code == 1000) {
if (this.lastString) {
log.warn("XDATA section unused string: " + this.lastString)
}
this.lastString = curr.value
return false
}
const curSection = this._currentSection
if (curr.code == 1002) {
if (curr.value == "{") {
if (!this.lastString) {
log.warn("Unnamed XDATA section encountered")
this.failure = true
return false
}
const newSection = this._CreateSection()
curSection[this.lastString] = newSection
this.lastString = null
this.sectionStack.push(newSection)
return false
}
if (curr.value == "}") {
if (this.sectionStack.length < 2) {
log.warn("Unmatched XDATA section closing")
this.failure = true
return false
}
this.sectionStack.length = this.sectionStack.length - 1
return false
}
log.warn("Bad XDATA section control string encountered: " + curr.value)
this.failure = true
return false
}
if (this.lastString !== null) {
curSection.values.push(this._CreateValue(1000, this.lastString))
this.lastString = null
}
curSection.values.push(this._CreateValue(curr.code, curr.value))
return false
}
/** Finalize XDATA section parsing. */
Finish(entity) {
if (this.failure) {
return
}
if (this.appName) {
let xdata
if (entity.hasOwnProperty("xdata")) {
xdata = entity.xdata
} else {
xdata = {}
entity.xdata = xdata
}
xdata[this.appName] = this.sectionStack[0]
}
}
get _currentSection() {
return this.sectionStack[this.sectionStack.length - 1]
}
_CreateSection() {
return {
values: []
}
}
_CreateValue(code, value) {
return {code, value}
}
}
+156
View File
@@ -0,0 +1,156 @@
import AUTO_CAD_COLOR_INDEX from './AutoCadColorIndex';
import ExtendedDataParser from './ExtendedDataParser';
import DxfArrayScanner, { IGroup } from './DxfArrayScanner';
import { IEntity, IPoint } from './entities/geomtry';
/**
* 返回给定AutoCad颜色索引值的对应color值
* @return {Number} color value as a number
*/
export function getAcadColor(index: number) {
return AUTO_CAD_COLOR_INDEX[index];
}
/**
* 解析2D或3D坐标、矢量或点。完成后,扫描器保持在坐标的最后一组上。
* @param {*} scanner
*/
export function parsePoint(scanner: DxfArrayScanner) {
const point = {} as IPoint;
// 重读第一个坐标的组
scanner.rewind();
let curr = scanner.next();
let code = curr.code;
point.x = curr.value as number;
code += 10;
curr = scanner.next();
if (curr.code != code)
throw new Error('Expected code for point value to be ' + code +
' but got ' + curr.code + '.');
point.y = curr.value as number;
code += 10;
curr = scanner.next();
if (curr.code != code) {
// Only the x and y are specified. Don't read z.
scanner.rewind(); // Let the calling code advance off the point
return point;
}
point.z = curr.value as number;
return point;
}
/**
* 某些实体可能包含由组101开始的嵌入对象。实体结束前的所有其他数据不应被解释为实体属性。该特性没有相关文档。
* @param scanner
*/
export function skipEmbeddedObject(scanner: DxfArrayScanner) {
/* 确保正确启动组. */
scanner.rewind()
let curr = scanner.next()
if (curr.code !== 101) {
throw new Error("Bad call for skipEmbeddedObject()")
}
do {
curr = scanner.next()
} while (curr.code !== 0)
scanner.rewind()
}
/**
* 尝试解析所有实体通用的代码。如果组由此函数处理,则返回true。
* @param {*} entity - the entity currently being parsed
* @param {*} curr - the current group being parsed
* @param scanner
*/
export function checkCommonEntityProperties(entity: IEntity, curr:IGroup, scanner:DxfArrayScanner) {
let xdataParser;
while (curr.code >= 1000) {
if (!xdataParser) {
xdataParser = new ExtendedDataParser()
}
if (xdataParser.Feed(curr)) {
xdataParser.Finish(entity)
xdataParser = null;
} else {
curr = scanner.next()
}
}
if (xdataParser) {
xdataParser.Finish(entity);
scanner.rewind();
return true;
}
switch (curr.code) {
case 0:
entity.type = curr.value as string;
break;
case 5:
entity.handle = curr.value as number;
break;
case 6:
entity.lineType = curr.value as string;
break;
case 8: // Layer name
entity.layer = curr.value as string;
break;
case 48:
entity.lineTypeScale = curr.value as number;
break;
case 60:
entity.visible = curr.value === 0;
break;
case 62: // Acad Index Color. 0 inherits ByBlock. 256 inherits ByLayer. Default is bylayer
entity.colorIndex = curr.value as number;
entity.color = getAcadColor(Math.abs(curr.value as number));
break;
case 67:
// 不存在或0表示图元位于模型空间中。
// 1 表示图元位于图纸空间中(可选)
entity.inPaperSpace = curr.value !== 0;
break;
case 100:
//ignore
break;
case 101: // Embedded Object in ACAD 2018.
// See https://ezdxf.readthedocs.io/en/master/dxfinternals/dxftags.html#embedded-objects
while (curr.code != 0) {
curr = scanner.next();
}
scanner.rewind();
break;
case 330:
entity.ownerHandle = curr.value as string;
break;
case 347:
entity.materialObjectHandle = curr.value as number;
break;
case 370:
//From https://www.woutware.com/Forum/Topic/955/lineweight?returnUrl=%2FForum%2FUserPosts%3FuserId%3D478262319
// An integer representing 100th of mm, must be one of the following values:
// 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 = STANDARD, -2 = BYLAYER, -1 = BYBLOCK
entity.lineweight = curr.value as 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;
break;
case 420: // TrueColor Color
entity.color = curr.value as number;
break;
case 1000:
entity.extendedData = entity.extendedData || {};
entity.extendedData.customStrings = entity.extendedData.customStrings || [];
entity.extendedData.customStrings.push(curr.value as string);
break;
case 1001:
entity.extendedData = entity.extendedData || {};
entity.extendedData.applicationName = curr.value as string;
break;
default:
return false;
}
return true;
}
@@ -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;
}
}
+22
View File
@@ -0,0 +1,22 @@
import DxfParser from './DxfParser';
export { default as DxfParser } from './DxfParser';
export type { IDxf, IBlock, ILayerTypesTable, ILayersTable, ITables, IViewPortTable, IBaseTable, ILayer, ILayerTableDefinition, ILineType, ILineTypeTableDefinition, ITable, ITableDefinitions, IViewPort, IViewPortTableDefinition } from './DxfParser';
export type { IEntity, IPoint } from './entities/geomtry';
export type { I3DfaceEntity } from './entities/3dface';
export type { IArcEntity } from './entities/arc';
export type { IAttdefEntity } from './entities/attdef';
export type { ICircleEntity } from './entities/circle';
export type { IDimensionEntity } from './entities/dimension';
export type { IEllipseEntity } from './entities/ellipse';
export type { IInsertEntity } from './entities/insert';
export type { ILineEntity } from './entities/line';
export type { ILwpolylineEntity } from './entities/lwpolyline';
export type { IMtextEntity } from './entities/mtext';
export type { IPointEntity } from './entities/point';
export type { IPolylineEntity } from './entities/polyline';
export type { ISolidEntity } from './entities/solid';
export type { ISplineEntity } from './entities/spline';
export type { ITextEntity } from './entities/text';
export type { IVertexEntity } from './entities/vertex';
export default DxfParser