OneBot: Leantime 改造版源码(BOM/Univer 表格/AI 接管/品牌替换等)

This commit is contained in:
wangruiguo
2026-09-03 18:49:20 +08:00
commit d647428529
3501 changed files with 1988906 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
/**
* 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.
*/
declare const _default: number[];
export default _default;

View File

@@ -0,0 +1,264 @@
/**
* 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
];

View File

@@ -0,0 +1,40 @@
export interface IGroup {
code: number;
value: number | string | boolean;
}
/**
* DxfArrayScanner
*
* Based off the AutoCad 2012 DXF Reference
* http://images.autodesk.com/adsk/files/autocad_2012_pdf_dxf-reference_enu.pdf
*
* Reads through an array representing lines of a dxf file. Takes an array and
* provides an easy interface to extract group code and value pairs.
* @param data - an array where each element represents a line in the dxf file
* @constructor
*/
export default class DxfArrayScanner {
private _pointer;
private _eof;
lastReadGroup: IGroup;
private _data;
constructor(data: string[]);
/**
* Gets the next group (code, value) from the array. A group is two consecutive elements
* in the array. The first is the code, the second is the value.
* @returns {{code: Number}|*}
*/
next(): IGroup;
peek(): IGroup;
rewind(numberOfGroups?: number): void;
/**
* Returns true if there is another code/value pair (2 elements in the array).
* @returns {boolean}
*/
hasNext(): boolean;
/**
* Returns true if the scanner is at the end of the array
* @returns {boolean}
*/
isEOF(): boolean;
}

View File

@@ -0,0 +1,149 @@
/**
* DxfArrayScanner
*
* Based off the AutoCad 2012 DXF Reference
* http://images.autodesk.com/adsk/files/autocad_2012_pdf_dxf-reference_enu.pdf
*
* Reads through an array representing lines of a dxf file. Takes an array and
* provides an easy interface to extract group code and value pairs.
* @param data - an array where each element represents a line in the dxf file
* @constructor
*/
export default class DxfArrayScanner {
constructor(data) {
this._pointer = 0;
this._eof = false;
this._data = data;
}
/**
* Gets the next group (code, value) from the array. A group is two consecutive elements
* in the array. The first is the code, the second is the value.
* @returns {{code: Number}|*}
*/
next() {
if (!this.hasNext()) {
if (!this._eof)
throw new Error('Unexpected end of input: EOF group not read before end of file. Ended on code ' + 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])
};
this._pointer++;
group.value = parseGroupValue(group.code, this._data[this._pointer].trim());
this._pointer++;
if (group.code === 0 && group.value === 'EOF')
this._eof = true;
this.lastReadGroup = group;
return group;
}
peek() {
if (!this.hasNext()) {
if (!this._eof)
throw new Error('Unexpected end of input: EOF group not read before end of file. Ended on code ' + 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])
};
group.value = parseGroupValue(group.code, this._data[this._pointer + 1].trim());
return group;
}
rewind(numberOfGroups = 1) {
this._pointer = this._pointer - numberOfGroups * 2;
}
/**
* Returns true if there is another code/value pair (2 elements in the array).
* @returns {boolean}
*/
hasNext() {
// Check if we have read EOF group code
if (this._eof) {
return false;
}
// We need to be sure there are two lines available
if (this._pointer > this._data.length - 2) {
return false;
}
return true;
}
/**
* Returns true if the scanner is at the end of the array
* @returns {boolean}
*/
isEOF() {
return this._eof;
}
}
/**
* Parse a value to its proper type.
* See pages 3 - 10 of the AutoCad DXF 2012 reference given at the top of this file
*
* @param code
* @param value
* @returns {*}
*/
function parseGroupValue(code, value) {
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);
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);
console.log('WARNING: Group code does not have a defined type: %j', { code: code, value: value });
return value;
}
/**
* Parse a boolean according to a 1 or 0 value
* @param str
* @returns {boolean}
*/
function parseBoolean(str) {
if (str === '0')
return false;
if (str === '1')
return true;
throw TypeError('String \'' + str + '\' cannot be cast to Boolean type');
}

View File

@@ -0,0 +1,111 @@
/// <reference types="node" />
import { Readable } from 'stream';
import IGeometry, { IEntity, IPoint } from './entities/geomtry';
export interface IBlock {
entities: IEntity[];
type: number;
ownerHandle: string;
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;
}
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;
}
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 declare type ITable = IViewPortTable | ILayerTypesTable | ILayersTable;
export interface IDxf {
header: Record<string, IPoint | number>;
entities: IEntity[];
blocks: Record<string, IBlock>;
tables: ITables;
}
export default class DxfParser {
private _entityHandlers;
constructor();
parse(source: string): IDxf | null;
registerEntityHandler(handlerType: new () => IGeometry): void;
parseSync(source: string): IDxf | null;
parseStream(stream: Readable): Promise<IDxf>;
private _parse;
}

View File

@@ -0,0 +1,722 @@
import DxfArrayScanner from './DxfArrayScanner.js';
import AUTO_CAD_COLOR_INDEX from './AutoCadColorIndex.js';
import Face from './entities/3dface.js';
import Arc from './entities/arc.js';
import AttDef from './entities/attdef.js';
import Circle from './entities/circle.js';
import Dimension from './entities/dimension.js';
import Ellipse from './entities/ellipse.js';
import Insert from './entities/insert.js';
import Line from './entities/line.js';
import LWPolyline from './entities/lwpolyline.js';
import MText from './entities/mtext.js';
import Point from './entities/point.js';
import Polyline from './entities/polyline.js';
import Solid from './entities/solid.js';
import Spline from './entities/spline.js';
import Text from './entities/text.js';
//import Vertex from './entities/.js';
import log from 'loglevel';
//log.setLevel('trace');
//log.setLevel('debug');
//log.setLevel('info');
//log.setLevel('warn');
log.setLevel('error');
function registerDefaultEntityHandlers(dxfParser) {
// Supported entities here (some entity code is still being refactored into this flow)
dxfParser.registerEntityHandler(Face);
dxfParser.registerEntityHandler(Arc);
dxfParser.registerEntityHandler(AttDef);
dxfParser.registerEntityHandler(Circle);
dxfParser.registerEntityHandler(Dimension);
dxfParser.registerEntityHandler(Ellipse);
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(require('./entities/vertex'));
}
export default class DxfParser {
constructor() {
this._entityHandlers = {};
registerDefaultEntityHandlers(this);
}
parse(source) {
if (typeof source === 'string') {
return this._parse(source);
}
else {
console.error('Cannot read dxf source of type `' + typeof (source));
return null;
}
}
registerEntityHandler(handlerType) {
const instance = new handlerType();
this._entityHandlers[instance.ForEntityName] = instance;
}
parseSync(source) {
return this.parse(source);
}
parseStream(stream) {
let dxfString = "";
const self = this;
return new Promise((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);
});
});
}
_parse(dxfString) {
const dxf = {};
let lastHandle = 0;
const dxfLinesArray = dxfString.split(/\r\n|\r|\n/g);
const scanner = new DxfArrayScanner(dxfLinesArray);
if (!scanner.hasNext())
throw Error('Empty file');
const self = this;
let curr;
function parseAll() {
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0 && curr.value === 'SECTION') {
curr = scanner.next();
// Be sure we are reading a section code
if (curr.code !== 2) {
console.error('Unexpected code %s after 0:SECTION', debugCode(curr));
curr = scanner.next();
continue;
}
if (curr.value === 'HEADER') {
log.debug('> HEADER');
dxf.header = parseHeader();
log.debug('<');
}
else if (curr.value === 'BLOCKS') {
log.debug('> BLOCKS');
dxf.blocks = parseBlocks();
log.debug('<');
}
else if (curr.value === 'ENTITIES') {
log.debug('> ENTITIES');
dxf.entities = parseEntities(false);
log.debug('<');
}
else if (curr.value === 'TABLES') {
log.debug('> TABLES');
dxf.tables = parseTables();
log.debug('<');
}
else if (curr.value === 'EOF') {
log.debug('EOF');
}
else {
log.warn('Skipping section \'%s\'', 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;
let currVarValue = null;
const header = {};
// loop through header variables
curr = scanner.next();
while (true) {
if (groupIs(curr, 0, 'ENDSEC')) {
if (currVarName)
header[currVarName] = currVarValue;
break;
}
else if (curr.code === 9) {
if (currVarName)
header[currVarName] = currVarValue;
currVarName = curr.value;
// Filter here for particular variables we are interested in
}
else {
if (curr.code === 10) {
currVarValue = { x: curr.value };
}
else if (curr.code === 20) {
currVarValue.y = curr.value;
}
else if (curr.code === 30) {
currVarValue.z = curr.value;
}
else {
currVarValue = curr.value;
}
}
curr = scanner.next();
}
// console.log(util.inspect(header, { colors: true, depth: null }));
curr = scanner.next(); // swallow up ENDSEC
return header;
}
/**
*
*/
function parseBlocks() {
const blocks = {};
curr = scanner.next();
while (curr.value !== 'EOF') {
if (groupIs(curr, 0, 'ENDSEC')) {
break;
}
if (groupIs(curr, 0, 'BLOCK')) {
log.debug('block {');
const block = parseBlock();
log.debug('}');
ensureHandle(block);
if (!block.name)
log.error('block with handle "' + block.handle + '" is missing a name.');
else
blocks[block.name] = block;
}
else {
logUnhandledGroup(curr);
curr = scanner.next();
}
}
return blocks;
}
function parseBlock() {
const block = {};
curr = scanner.next();
while (curr.value !== 'EOF') {
switch (curr.code) {
case 1:
block.xrefPath = curr.value;
curr = scanner.next();
break;
case 2:
block.name = curr.value;
curr = scanner.next();
break;
case 3:
block.name2 = curr.value;
curr = scanner.next();
break;
case 5:
block.handle = curr.value;
curr = scanner.next();
break;
case 8:
block.layer = curr.value;
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;
}
curr = scanner.next();
break;
case 100:
// ignore class markers
curr = scanner.next();
break;
case 330:
block.ownerHandle = curr.value;
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 = {};
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];
if (tableDefinition) {
log.debug(curr.value + ' Table {');
tables[tableDefinitions[curr.value].tableName] = parseTable(curr);
log.debug('}');
}
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(group) {
const tableDefinition = tableDefinitions[group.value];
const table = {};
let expectedCount = 0;
curr = scanner.next();
while (!groupIs(curr, 0, END_OF_TABLE_VALUE)) {
switch (curr.code) {
case 5:
table.handle = curr.value;
curr = scanner.next();
break;
case 330:
table.ownerHandle = curr.value;
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;
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 = []; // Multiple table entries may have the same name indicating a multiple viewport configuration
let viewPort = {};
log.debug('ViewPort {');
curr = scanner.next();
while (!groupIs(curr, 0, END_OF_TABLE_VALUE)) {
switch (curr.code) {
case 2: // layer name
viewPort.name = curr.value;
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:
viewPort.center = parsePoint(curr);
curr = scanner.next();
break;
case 13:
viewPort.snapBasePoint = parsePoint(curr);
curr = scanner.next();
break;
case 14:
viewPort.snapSpacing = parsePoint(curr);
curr = scanner.next();
break;
case 15:
viewPort.gridSpacing = parsePoint(curr);
curr = scanner.next();
break;
case 16:
viewPort.viewDirectionFromTarget = parsePoint(curr);
curr = scanner.next();
break;
case 17:
viewPort.viewTarget = parsePoint(curr);
curr = scanner.next();
break;
case 42:
viewPort.lensLength = curr.value;
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;
curr = scanner.next();
break;
case 50:
viewPort.snapRotationAngle = curr.value;
curr = scanner.next();
break;
case 51:
viewPort.viewTwistAngle = curr.value;
curr = scanner.next();
break;
case 79:
viewPort.orthographicType = curr.value;
curr = scanner.next();
break;
case 110:
viewPort.ucsOrigin = parsePoint(curr);
curr = scanner.next();
break;
case 111:
viewPort.ucsXAxis = parsePoint(curr);
curr = scanner.next();
break;
case 112:
viewPort.ucsYAxis = parsePoint(curr);
curr = scanner.next();
break;
case 110:
viewPort.ucsOrigin = parsePoint(curr);
curr = scanner.next();
break;
case 281:
viewPort.renderMode = curr.value;
curr = scanner.next();
break;
case 281:
// 0 is one distant light, 1 is two distant lights
viewPort.defaultLightingType = curr.value;
curr = scanner.next();
break;
case 292:
viewPort.defaultLightingOn = curr.value;
curr = scanner.next();
break;
case 330:
viewPort.ownerHandle = curr.value;
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;
curr = scanner.next();
break;
case 0:
// New ViewPort
if (curr.value === 'VPORT') {
log.debug('}');
viewPorts.push(viewPort);
log.debug('ViewPort {');
viewPort = {};
curr = scanner.next();
}
break;
default:
logUnhandledGroup(curr);
curr = scanner.next();
break;
}
}
// Note: do not call scanner.next() here,
// parseTable() needs the current group
log.debug('}');
viewPorts.push(viewPort);
return viewPorts;
}
function parseLineTypes() {
const ltypes = {};
let ltype = {};
let length = 0;
let ltypeName;
log.debug('LType {');
curr = scanner.next();
while (!groupIs(curr, 0, 'ENDTAB')) {
switch (curr.code) {
case 2:
ltype.name = curr.value;
ltypeName = curr.value;
curr = scanner.next();
break;
case 3:
ltype.description = curr.value;
curr = scanner.next();
break;
case 73: // Number of elements for this line type (dots, dashes, spaces);
length = curr.value;
if (length > 0)
ltype.pattern = [];
curr = scanner.next();
break;
case 40: // total pattern length
ltype.patternLength = curr.value;
curr = scanner.next();
break;
case 49:
ltype.pattern.push(curr.value);
curr = scanner.next();
break;
case 0:
log.debug('}');
if (length > 0 && length !== ltype.pattern.length)
log.warn('lengths do not match on LTYPE pattern');
ltypes[ltypeName] = ltype;
ltype = {};
log.debug('LType {');
curr = scanner.next();
break;
default:
curr = scanner.next();
}
}
log.debug('}');
ltypes[ltypeName] = ltype;
return ltypes;
}
function parseLayers() {
const layers = {};
let layer = {};
let layerName;
log.debug('Layer {');
curr = scanner.next();
while (!groupIs(curr, 0, 'ENDTAB')) {
switch (curr.code) {
case 2: // layer name
layer.name = curr.value;
layerName = curr.value;
curr = scanner.next();
break;
case 62: // color, visibility
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);
layer.color = getAcadColor(layer.colorIndex);
curr = scanner.next();
break;
case 70: // frozen layer
layer.frozen = ((curr.value & 1) != 0 || (curr.value & 2) != 0);
curr = scanner.next();
break;
case 0:
// New Layer
if (curr.value === 'LAYER') {
log.debug('}');
layers[layerName] = layer;
log.debug('Layer {');
layer = {};
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
log.debug('}');
layers[layerName] = layer;
return layers;
}
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
}
};
/**
* Is called after the parser first reads the 0:ENTITIES group. The scanner
* should be on the start of the first entity already.
* @return {Array} the resulting entities
*/
function parseEntities(forBlock) {
const entities = [];
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];
if (handler != null) {
log.debug(curr.value + ' {');
const entity = handler.parseEntity(scanner, curr);
curr = scanner.lastReadGroup;
log.debug('}');
ensureHandle(entity);
entities.push(entity);
}
else {
log.warn('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;
}
/**
* Parses a 2D or 3D point, returning it as an object with x, y, and
* (sometimes) z property if it is 3D. It is assumed the current group
* is x of the point being read in, and scanner.next() will return the
* y. The parser will determine if there is a z point automatically.
* @return {Object} The 2D or 3D point as an object with x, y[, z]
*/
function parsePoint(curr) {
const point = {};
let code = curr.code;
point.x = curr.value;
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;
code += 10;
curr = scanner.next();
if (curr.code != code) {
scanner.rewind();
return point;
}
point.z = curr.value;
return point;
}
function ensureHandle(entity) {
if (!entity)
throw new TypeError('entity cannot be undefined or null');
if (!entity.handle)
entity.handle = lastHandle++;
}
parseAll();
return dxf;
}
}
function groupIs(group, code, value) {
return group.code === code && group.value === value;
}
function logUnhandledGroup(curr) {
log.debug('unhandled group ' + debugCode(curr));
}
function debugCode(curr) {
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) {
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

View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 GDS Storefront Estimating
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -0,0 +1,20 @@
import DxfArrayScanner, { IGroup } from './DxfArrayScanner';
import { IEntity, IPoint } from './entities/geomtry';
/**
* Returns the truecolor value of the given AutoCad color index value
* @return {Number} truecolor value as a number
*/
export declare function getAcadColor(index: number): number;
/**
* Parses the 2D or 3D coordinate, vector, or point. When complete,
* the scanner remains on the last group of the coordinate.
* @param {*} scanner
*/
export declare function parsePoint(scanner: DxfArrayScanner): IPoint;
/**
* Attempts to parse codes common to all entities. Returns true if the group
* was handled by this function.
* @param {*} entity - the entity currently being parsed
* @param {*} curr - the current group being parsed
*/
export declare function checkCommonEntityProperties(entity: IEntity, curr: IGroup, scanner: DxfArrayScanner): boolean;

View File

@@ -0,0 +1,109 @@
import AUTO_CAD_COLOR_INDEX from './AutoCadColorIndex.js';
/**
* Returns the truecolor value of the given AutoCad color index value
* @return {Number} truecolor value as a number
*/
export function getAcadColor(index) {
return AUTO_CAD_COLOR_INDEX[index];
}
/**
* Parses the 2D or 3D coordinate, vector, or point. When complete,
* the scanner remains on the last group of the coordinate.
* @param {*} scanner
*/
export function parsePoint(scanner) {
const point = {};
// Reread group for the first coordinate
scanner.rewind();
let curr = scanner.next();
let code = curr.code;
point.x = curr.value;
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;
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;
return point;
}
/**
* Attempts to parse codes common to all entities. Returns true if the group
* was handled by this function.
* @param {*} entity - the entity currently being parsed
* @param {*} curr - the current group being parsed
*/
export function checkCommonEntityProperties(entity, curr, scanner) {
switch (curr.code) {
case 0:
entity.type = curr.value;
break;
case 5:
entity.handle = curr.value;
break;
case 6:
entity.lineType = curr.value;
break;
case 8: // Layer name
entity.layer = curr.value;
break;
case 48:
entity.lineTypeScale = curr.value;
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;
entity.color = getAcadColor(Math.abs(curr.value));
break;
case 67:
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;
break;
case 347:
entity.materialObjectHandle = curr.value;
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;
break;
case 420: // TrueColor Color
entity.color = curr.value;
break;
case 1000:
entity.extendedData = entity.extendedData || {};
entity.extendedData.customStrings = entity.extendedData.customStrings || [];
entity.extendedData.customStrings.push(curr.value);
break;
case 1001:
entity.extendedData = entity.extendedData || {};
entity.extendedData.applicationName = curr.value;
break;
default:
return false;
}
return true;
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,11 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface I3DfaceEntity extends IEntity {
shape: boolean;
hasContinuousLinetypePattern: boolean;
vertices: IPoint[];
}
export default class ThreeDface implements IGeometry {
ForEntityName: "3DFACE";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): I3DfaceEntity;
}

View File

@@ -0,0 +1,79 @@
import * as helpers from '../ParseHelpers.js';
export default class ThreeDface {
constructor() {
this.ForEntityName = '3DFACE';
}
parseEntity(scanner, curr) {
const entity = { type: curr.value, vertices: [] };
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 & 1) === 1);
entity.hasContinuousLinetypePattern = ((curr.value & 128) === 128);
break;
case 10: // X coordinate of point
entity.vertices = parse3dFaceVertices(scanner, curr);
curr = scanner.lastReadGroup;
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
function parse3dFaceVertices(scanner, curr) {
var vertices = [];
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 = {};
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;
vertexIsStarted = true;
break;
case 20: // Y
case 21:
case 22:
case 23:
vertex.y = curr.value;
break;
case 30: // Z
case 31:
case 32:
case 33:
vertex.z = curr.value;
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;
}
;

View File

@@ -0,0 +1,16 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
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 {
ForEntityName: "ARC";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): IArcEntity;
}

View File

@@ -0,0 +1,43 @@
import * as helpers from '../ParseHelpers.js';
export default class Arc {
constructor() {
this.ForEntityName = 'ARC';
}
parseEntity(scanner, curr) {
const entity = { type: curr.value };
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;
break;
case 50: // start angle
entity.startAngle = Math.PI / 180 * curr.value;
break;
case 51: // end angle
entity.endAngle = Math.PI / 180 * curr.value;
entity.angleLength = entity.endAngle - entity.startAngle; // angleLength is deprecated
break;
case 210:
entity.extrusionDirectionX = curr.value;
break;
case 220:
entity.extrusionDirectionY = curr.value;
break;
case 230:
entity.extrusionDirectionZ = curr.value;
break;
default: // ignored attribute
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}

View File

@@ -0,0 +1,31 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
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 {
ForEntityName: "ATTDEF";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): IAttdefEntity;
}

View File

@@ -0,0 +1,91 @@
import * as helpers from '../ParseHelpers.js';
export default class Attdef {
constructor() {
this.ForEntityName = 'ATTDEF';
}
parseEntity(scanner, curr) {
var entity = {
type: curr.value,
scale: 1,
textStyle: 'STANDARD'
};
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0) {
break;
}
switch (curr.code) {
case 1:
entity.text = curr.value;
break;
case 2:
entity.tag = curr.value;
break;
case 3:
entity.prompt = curr.value;
break;
case 7:
entity.textStyle = curr.value;
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;
break;
case 40:
entity.textHeight = curr.value;
break;
case 41:
entity.scale = curr.value;
break;
case 50:
entity.rotation = curr.value;
break;
case 51:
entity.obliqueAngle = curr.value;
break;
case 70:
entity.invisible = !!(curr.value & 0x01);
entity.constant = !!(curr.value & 0x02);
entity.verificationRequired = !!(curr.value & 0x04);
entity.preset = !!(curr.value & 0x08);
break;
case 71:
entity.backwards = !!(curr.value & 0x02);
entity.mirrored = !!(curr.value & 0x04);
break;
case 72:
// TODO: enum values?
entity.horizontalJustification = curr.value;
break;
case 73:
entity.fieldLength = curr.value;
break;
case 74:
// TODO: enum values?
entity.verticalJustification = curr.value;
break;
case 100:
break;
case 210:
entity.extrusionDirectionX = curr.value;
break;
case 220:
entity.extrusionDirectionY = curr.value;
break;
case 230:
entity.extrusionDirectionZ = curr.value;
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}

View File

@@ -0,0 +1,13 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
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 {
ForEntityName: "CIRCLE";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): ICircleEntity;
}

View File

@@ -0,0 +1,38 @@
import * as helpers from '../ParseHelpers.js';
export default class Circle {
constructor() {
this.ForEntityName = 'CIRCLE';
}
parseEntity(scanner, curr) {
const entity = { type: curr.value };
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;
break;
case 50: // start angle
entity.startAngle = Math.PI / 180 * curr.value;
break;
case 51: // end angle
const endAngle = Math.PI / 180 * curr.value;
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;
}
}

View File

@@ -0,0 +1,21 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
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 {
ForEntityName: "DIMENSION";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): IDimensionEntity;
}

View File

@@ -0,0 +1,60 @@
import * as helpers from '../ParseHelpers.js';
export default class Dimension {
constructor() {
this.ForEntityName = 'DIMENSION';
}
parseEntity(scanner, curr) {
const entity = { type: curr.value };
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0)
break;
switch (curr.code) {
case 2: // Referenced block name
entity.block = curr.value;
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;
break;
case 71: // 5 = Middle center
entity.attachmentPoint = curr.value;
break;
case 42: // Actual measurement
entity.actualMeasurement = curr.value;
break;
case 1: // Text entered by user explicitly
entity.text = curr.value;
break;
case 50: // Angle of rotated, horizontal, or vertical dimensions
entity.angle = curr.value;
break;
default: // check common entity attributes
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}

View File

@@ -0,0 +1,14 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
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 {
ForEntityName: "ELLIPSE";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): IEllipseEntity;
}

View File

@@ -0,0 +1,39 @@
import * as helpers from '../ParseHelpers.js';
export default class Ellipse {
constructor() {
this.ForEntityName = 'ELLIPSE';
}
parseEntity(scanner, curr) {
const entity = { type: curr.value };
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;
break;
case 41:
entity.startAngle = curr.value;
break;
case 42:
entity.endAngle = curr.value;
break;
case 2:
entity.name = curr.value;
break;
default: // check common entity attributes
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}

View File

@@ -0,0 +1,29 @@
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 declare type EntityName = 'POINT' | '3DFACE' | 'ARC' | 'ATTDEF' | 'CIRCLE' | 'DIMENSION' | 'ELLIPSE' | 'INSERT' | 'LINE' | 'LWPOLYLINE' | 'MTEXT' | 'POLYLINE' | 'SOLID' | 'SPLINE' | 'TEXT' | 'VERTEX';
export default interface IGeometry {
ForEntityName: EntityName;
parseEntity(scanner: DxfArrayScanner, curr: IGroup): IEntity;
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,19 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
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 {
ForEntityName: "INSERT";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): IInsertEntity;
}

View File

@@ -0,0 +1,54 @@
import * as helpers from '../ParseHelpers.js';
export default class Insert {
constructor() {
this.ForEntityName = 'INSERT';
}
parseEntity(scanner, curr) {
const entity = { type: curr.value };
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0)
break;
switch (curr.code) {
case 2:
entity.name = curr.value;
break;
case 41:
entity.xScale = curr.value;
break;
case 42:
entity.yScale = curr.value;
break;
case 43:
entity.zScale = curr.value;
break;
case 10:
entity.position = helpers.parsePoint(scanner);
break;
case 50:
entity.rotation = curr.value;
break;
case 70:
entity.columnCount = curr.value;
break;
case 71:
entity.rowCount = curr.value;
break;
case 44:
entity.columnSpacing = curr.value;
break;
case 45:
entity.rowSpacing = curr.value;
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;
}
}

View File

@@ -0,0 +1,10 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface ILineEntity extends IEntity {
vertices: IPoint[];
extrusionDirection: IPoint;
}
export default class Line implements IGeometry {
ForEntityName: "LINE";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): ILineEntity;
}

View File

@@ -0,0 +1,32 @@
import * as helpers from '../ParseHelpers.js';
export default class Line {
constructor() {
this.ForEntityName = 'LINE';
}
parseEntity(scanner, curr) {
const entity = { type: curr.value, vertices: [] };
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;
}
}

View File

@@ -0,0 +1,22 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
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 {
ForEntityName: "LWPOLYLINE";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): ILwpolylineEntity;
}

View File

@@ -0,0 +1,107 @@
import * as helpers from '../ParseHelpers.js';
export default class Lwpolyline {
constructor() {
this.ForEntityName = 'LWPOLYLINE';
}
parseEntity(scanner, curr) {
const entity = { type: curr.value, vertices: [] };
let numberOfVertices = 0;
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0)
break;
switch (curr.code) {
case 38:
entity.elevation = curr.value;
break;
case 39:
entity.depth = curr.value;
break;
case 70: // 1 = Closed shape, 128 = plinegen?, 0 = default
entity.shape = ((curr.value & 1) === 1);
entity.hasContinuousLinetypePattern = ((curr.value & 128) === 128);
break;
case 90:
numberOfVertices = curr.value;
break;
case 10: // X coordinate of point
entity.vertices = parseLWPolylineVertices(numberOfVertices, scanner);
break;
case 43:
if (curr.value !== 0)
entity.width = curr.value;
break;
case 210:
entity.extrusionDirectionX = curr.value;
break;
case 220:
entity.extrusionDirectionY = curr.value;
break;
case 230:
entity.extrusionDirectionZ = curr.value;
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}
function parseLWPolylineVertices(n, scanner) {
if (!n || n <= 0)
throw Error('n must be greater than 0 verticies');
const vertices = [];
let vertexIsStarted = false;
let vertexIsFinished = false;
let curr = scanner.lastReadGroup;
for (let i = 0; i < n; i++) {
const vertex = {};
while (!scanner.isEOF()) {
if (curr.code === 0 || vertexIsFinished)
break;
switch (curr.code) {
case 10: // X
if (vertexIsStarted) {
vertexIsFinished = true;
continue;
}
vertex.x = curr.value;
vertexIsStarted = true;
break;
case 20: // Y
vertex.y = curr.value;
break;
case 30: // Z
vertex.z = curr.value;
break;
case 40: // start width
vertex.startWidth = curr.value;
break;
case 41: // end width
vertex.endWidth = curr.value;
break;
case 42: // bulge
if (curr.value != 0)
vertex.bulge = curr.value;
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;
}

View File

@@ -0,0 +1,16 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
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 {
ForEntityName: "MTEXT";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): IMtextEntity;
}

View File

@@ -0,0 +1,49 @@
import * as helpers from '../ParseHelpers.js';
export default class Mtext {
constructor() {
this.ForEntityName = 'MTEXT';
}
parseEntity(scanner, curr) {
const entity = { type: curr.value };
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;
break;
case 1:
entity.text ? entity.text += curr.value : entity.text = curr.value;
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;
break;
case 41:
entity.width = curr.value;
break;
case 50:
entity.rotation = curr.value;
break;
case 71:
entity.attachmentPoint = curr.value;
break;
case 72:
entity.drawingDirection = curr.value;
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}

View File

@@ -0,0 +1,11 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface IPointEntity extends IEntity {
position: IPoint;
thickness: number;
extrusionDirection: IPoint;
}
export default class Point implements IGeometry {
ForEntityName: "POINT";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): IPointEntity;
}

View File

@@ -0,0 +1,33 @@
import * as helpers from '../ParseHelpers.js';
export default class Point {
constructor() {
this.ForEntityName = 'POINT';
}
parseEntity(scanner, curr) {
const type = curr.value;
const entity = { type };
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;
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;
}
}

View File

@@ -0,0 +1,20 @@
import { 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 {
ForEntityName: "POLYLINE";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): IPolylineEntity;
}

View File

@@ -0,0 +1,88 @@
import * as helpers from '../ParseHelpers.js';
import VertexParser from './vertex.js';
export default class Polyline {
constructor() {
this.ForEntityName = 'POLYLINE';
}
parseEntity(scanner, curr) {
var entity = { type: curr.value, vertices: [] };
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;
break;
case 40: // start width
break;
case 41: // end width
break;
case 70:
entity.shape = (curr.value & 1) !== 0;
entity.includesCurveFitVertices = (curr.value & 2) !== 0;
entity.includesSplineFitVertices = (curr.value & 4) !== 0;
entity.is3dPolyline = (curr.value & 8) !== 0;
entity.is3dPolygonMesh = (curr.value & 16) !== 0;
entity.is3dPolygonMeshClosed = (curr.value & 32) !== 0; // 32 = The polygon mesh is closed in the N direction
entity.isPolyfaceMesh = (curr.value & 64) !== 0;
entity.hasContinuousLinetypePattern = (curr.value & 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, curr) {
const vertexParser = new VertexParser();
const vertices = [];
while (!scanner.isEOF()) {
if (curr.code === 0) {
if (curr.value === 'VERTEX') {
vertices.push(vertexParser.parseEntity(scanner, curr));
curr = scanner.lastReadGroup;
}
else if (curr.value === 'SEQEND') {
parseSeqEnd(scanner, curr);
break;
}
}
}
return vertices;
}
function parseSeqEnd(scanner, curr) {
const entity = { type: curr.value };
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code == 0)
break;
helpers.checkCommonEntityProperties(entity, curr, scanner);
curr = scanner.next();
}
return entity;
}
;

View File

@@ -0,0 +1,10 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface ISolidEntity extends IEntity {
points: IPoint[];
extrusionDirection: IPoint;
}
export default class Solid implements IGeometry {
ForEntityName: "SOLID";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): ISolidEntity;
}

View File

@@ -0,0 +1,36 @@
import * as helpers from '../ParseHelpers.js';
export default class Solid {
constructor() {
this.ForEntityName = 'SOLID';
}
parseEntity(scanner, curr) {
const entity = { type: curr.value, points: [] };
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;
}
}

View File

@@ -0,0 +1,23 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
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 {
ForEntityName: "SPLINE";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): ISplineEntity;
}

View File

@@ -0,0 +1,71 @@
import * as helpers from '../ParseHelpers.js';
export default class Spline {
constructor() {
this.ForEntityName = 'SPLINE';
}
parseEntity(scanner, curr) {
const entity = { type: curr.value };
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);
break;
case 70:
if ((curr.value & 1) != 0)
entity.closed = true;
if ((curr.value & 2) != 0)
entity.periodic = true;
if ((curr.value & 4) != 0)
entity.rational = true;
if ((curr.value & 8) != 0)
entity.planar = true;
if ((curr.value & 16) != 0) {
entity.planar = true;
entity.linear = true;
}
break;
case 71:
entity.degreeOfSplineCurve = curr.value;
break;
case 72:
entity.numberOfKnots = curr.value;
break;
case 73:
entity.numberOfControlPoints = curr.value;
break;
case 74:
entity.numberOfFitPoints = curr.value;
break;
case 210:
entity.normalVector = helpers.parsePoint(scanner);
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}

View File

@@ -0,0 +1,16 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
import IGeometry, { IEntity, IPoint } from './geomtry';
export interface ITextEntity extends IEntity {
startPoint: IPoint;
endPoint: IPoint;
textHeight: number;
xScale: number;
rotation: number;
text: string;
halign: number;
valign: number;
}
export default class Text implements IGeometry {
ForEntityName: "TEXT";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): ITextEntity;
}

View File

@@ -0,0 +1,46 @@
import * as helpers from '../ParseHelpers.js';
export default class Text {
constructor() {
this.ForEntityName = 'TEXT';
}
parseEntity(scanner, curr) {
const entity = { type: curr.value };
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0)
break;
switch (curr.code) {
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 40: // Text height
entity.textHeight = curr.value;
break;
case 41:
entity.xScale = curr.value;
break;
case 50: // Rotation in degrees
entity.rotation = curr.value;
break;
case 1: // Text
entity.text = curr.value;
break;
// NOTE: 72 and 73 are meaningless without 11 (second alignment point)
case 72: // Horizontal alignment
entity.halign = curr.value;
break;
case 73: // Vertical alignment
entity.valign = curr.value;
break;
default: // check common entity attributes
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}

View File

@@ -0,0 +1,20 @@
import DxfArrayScanner, { IGroup } from '../DxfArrayScanner';
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 {
ForEntityName: "VERTEX";
parseEntity(scanner: DxfArrayScanner, curr: IGroup): IVertexEntity;
}

View File

@@ -0,0 +1,61 @@
import * as helpers from '../ParseHelpers.js';
export default class Vertex {
constructor() {
this.ForEntityName = 'VERTEX';
}
parseEntity(scanner, curr) {
var entity = { type: curr.value };
curr = scanner.next();
while (!scanner.isEOF()) {
if (curr.code === 0)
break;
switch (curr.code) {
case 10: // X
entity.x = curr.value;
break;
case 20: // Y
entity.y = curr.value;
break;
case 30: // Z
entity.z = curr.value;
break;
case 40: // start width
break;
case 41: // end width
break;
case 42: // bulge
if (curr.value != 0)
entity.bulge = curr.value;
break;
case 70: // flags
entity.curveFittingVertex = (curr.value & 1) !== 0;
entity.curveFitTangent = (curr.value & 2) !== 0;
entity.splineVertex = (curr.value & 8) !== 0;
entity.splineControlPoint = (curr.value & 16) !== 0;
entity.threeDPolylineVertex = (curr.value & 32) !== 0;
entity.threeDPolylineMesh = (curr.value & 64) !== 0;
entity.polyfaceMeshVertex = (curr.value & 128) !== 0;
break;
case 50: // curve fit tangent direction
break;
case 71: // polyface mesh vertex index
entity.faceA = curr.value;
break;
case 72: // polyface mesh vertex index
entity.faceB = curr.value;
break;
case 73: // polyface mesh vertex index
entity.faceC = curr.value;
break;
case 74: // polyface mesh vertex index
entity.faceD = curr.value;
break;
default:
helpers.checkCommonEntityProperties(entity, curr, scanner);
break;
}
curr = scanner.next();
}
return entity;
}
}

View File

@@ -0,0 +1,21 @@
import DxfParser from './DxfParser';
export { default as DxfParser } from './DxfParser';
export { IDxf, IBlock, ILayerTypesTable, ILayersTable, ITables, IViewPortTable, IBaseTable, ILayer, ILayerTableDefinition, ILineType, ILineTypeTableDefinition, ITable, ITableDefinitions, IViewPort, IViewPortTableDefinition } from './DxfParser';
export { IEntity, IPoint } from './entities/geomtry';
export { I3DfaceEntity } from './entities/3dface';
export { IArcEntity } from './entities/arc';
export { IAttdefEntity } from './entities/attdef';
export { ICircleEntity } from './entities/circle';
export { IDimensionEntity } from './entities/dimension';
export { IEllipseEntity } from './entities/ellipse';
export { IInsertEntity } from './entities/insert';
export { ILineEntity } from './entities/line';
export { ILwpolylineEntity } from './entities/lwpolyline';
export { IMtextEntity } from './entities/mtext';
export { IPointEntity } from './entities/point';
export { IPolylineEntity } from './entities/polyline';
export { ISolidEntity } from './entities/solid';
export { ISplineEntity } from './entities/spline';
export { ITextEntity } from './entities/text';
export { IVertexEntity } from './entities/vertex';
export default DxfParser;

View File

@@ -0,0 +1,3 @@
import DxfParser from './DxfParser.js';
export { default as DxfParser } from './DxfParser.js';
export default DxfParser;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

View File

@@ -0,0 +1,32 @@
/**
* loglevel 的极简替身。
*
* dxf-parser 依赖 loglevel 打调试日志,但它只用到 setLevel/debug/info/warn/error/trace
* 这几个方法。为了让 vendored 的 dxf-parser 原样能在浏览器里跑(不引入打包工具),
* index.html 的 importmap 把 "loglevel" 指到这里。
* 默认只放行 warn 及以上,避免解析大图时刷屏。
*/
const LEVELS = { trace: 0, debug: 1, info: 2, warn: 3, error: 4, silent: 5 }
let level = LEVELS.warn
const emit = (name, consoleFn) => (...args) => {
if (LEVELS[name] < level) return
// dxf-parser 里有 '%s' 风格的格式串console 原生就支持
consoleFn.apply(console, args)
}
const log = {
levels: LEVELS,
setLevel(l) { level = typeof l === 'number' ? l : (LEVELS[String(l).toLowerCase()] ?? LEVELS.warn) },
getLevel() { return level },
getLogger() { return log },
trace: emit('trace', console.debug),
debug: emit('debug', console.debug),
info: emit('info', console.info),
warn: emit('warn', console.warn),
error: emit('error', console.error),
}
log.log = log.debug
export default log