UNPKG

dxf-writer

Version:
593 lines (537 loc) 17.3 kB
const LineType = require("./LineType"); const Layer = require("./Layer"); const Table = require("./Table"); const DimStyleTable = require("./DimStyleTable"); const TextStyle = require("./TextStyle"); const Viewport = require("./Viewport"); const AppId = require("./AppId"); const Block = require("./Block"); const BlockRecord = require("./BlockRecord"); const Dictionary = require("./Dictionary"); const Line = require("./Line"); const Line3d = require("./Line3d"); const Arc = require("./Arc"); const Circle = require("./Circle"); const Cylinder = require("./Cylinder"); const Text = require("./Text"); const Polyline = require("./Polyline"); const Polyline3d = require("./Polyline3d"); const Face = require("./Face"); const Point = require("./Point"); const Spline = require("./Spline"); const Ellipse = require("./Ellipse"); const TagsManager = require("./TagsManager"); const Handle = require("./Handle"); class Drawing { constructor() { this.layers = {}; this.activeLayer = null; this.lineTypes = {}; this.headers = {}; this.tables = {}; this.blocks = {}; this.dictionary = new Dictionary(); this.setUnits("Unitless"); for (const ltype of Drawing.LINE_TYPES) { this.addLineType(ltype.name, ltype.description, ltype.elements); } for (const l of Drawing.LAYERS) { this.addLayer(l.name, l.colorNumber, l.lineTypeName); } this.setActiveLayer("0"); // Must call this function this.generateAutocadExtras(); } /** * @param {string} name * @param {string} description * @param {array} elements - if elem > 0 it is a line, if elem < 0 it is gap, if elem == 0.0 it is a */ addLineType(name, description, elements) { this.lineTypes[name] = new LineType(name, description, elements); return this; } addLayer(name, colorNumber, lineTypeName) { this.layers[name] = new Layer(name, colorNumber, lineTypeName); return this; } setActiveLayer(name) { this.activeLayer = this.layers[name]; return this; } addTable(name) { const table = new Table(name); this.tables[name] = table; return table; } /** * * @param {string} name The name of the block. * @returns {Block} */ addBlock(name) { const block = new Block(name); this.blocks[name] = block; return block; } drawLine(x1, y1, x2, y2) { this.activeLayer.addShape(new Line(x1, y1, x2, y2)); return this; } drawLine3d(x1, y1, z1, x2, y2, z2) { this.activeLayer.addShape(new Line3d(x1, y1, z1, x2, y2, z2)); return this; } drawPoint(x, y) { this.activeLayer.addShape(new Point(x, y)); return this; } drawRect(x1, y1, x2, y2, cornerLength, cornerBulge) { const w = x2 - x1; const h = y2 - y1; cornerBulge = cornerBulge || 0; let p = null; if (!cornerLength) { p = new Polyline( [ [x1, y1], [x1, y1 + h], [x1 + w, y1 + h], [x1 + w, y1], ], true ); } else { p = new Polyline( [ [x1 + w - cornerLength, y1, cornerBulge], // 1 [x1 + w, y1 + cornerLength], // 2 [x1 + w, y1 + h - cornerLength, cornerBulge], // 3 [x1 + w - cornerLength, y1 + h], // 4 [x1 + cornerLength, y1 + h, cornerBulge], // 5 [x1, y1 + h - cornerLength], // 6 [x1, y1 + cornerLength, cornerBulge], // 7 [x1 + cornerLength, y1], // 8 ], true ); } this.activeLayer.addShape(p); return this; } /** * Draw a regular convex polygon as a polyline entity. * * @see [Regular polygon | Wikipedia](https://en.wikipedia.org/wiki/Regular_polygon) * * @param {number} x - The X coordinate of the center of the polygon. * @param {number} y - The Y coordinate of the center of the polygon. * @param {number} numberOfSides - The number of sides. * @param {number} radius - The radius. * @param {number} rotation - The rotation angle (in Degrees) of the polygon. By default 0. * @param {boolean} circumscribed - If `true` is a polygon in which each side is a tangent to a circle. * If `false` is a polygon in which all vertices lie on a circle. By default `false`. * * @returns {Drawing} - The current object of {@link Drawing}. */ drawPolygon( x, y, numberOfSides, radius, rotation = 0, circumscribed = false ) { const angle = (2 * Math.PI) / numberOfSides; const vertices = []; let d = radius; const rotationRad = (rotation * Math.PI) / 180; if (circumscribed) d = radius / Math.cos(Math.PI / numberOfSides); for (let i = 0; i < numberOfSides; i++) { vertices.push([ x + d * Math.sin(rotationRad + i * angle), y + d * Math.cos(rotationRad + i * angle), ]); } this.activeLayer.addShape(new Polyline(vertices, true)); return this; } /** * @param {number} x1 - Center x * @param {number} y1 - Center y * @param {number} r - radius * @param {number} startAngle - degree * @param {number} endAngle - degree */ drawArc(x1, y1, r, startAngle, endAngle) { this.activeLayer.addShape(new Arc(x1, y1, r, startAngle, endAngle)); return this; } /** * @param {number} x1 - Center x * @param {number} y1 - Center y * @param {number} r - radius */ drawCircle(x1, y1, r) { this.activeLayer.addShape(new Circle(x1, y1, r)); return this; } /** * @param {number} x1 - Center x * @param {number} y1 - Center y * @param {number} z1 - Center z * @param {number} r - radius * @param {number} thickness - thickness * @param {number} extrusionDirectionX - Extrusion Direction x * @param {number} extrusionDirectionY - Extrusion Direction y * @param {number} extrusionDirectionZ - Extrusion Direction z */ drawCylinder( x1, y1, z1, r, thickness, extrusionDirectionX, extrusionDirectionY, extrusionDirectionZ ) { this.activeLayer.addShape( new Cylinder( x1, y1, z1, r, thickness, extrusionDirectionX, extrusionDirectionY, extrusionDirectionZ ) ); return this; } /** * @param {number} x1 - x * @param {number} y1 - y * @param {number} height - Text height * @param {number} rotation - Text rotation * @param {string} value - the string itself * @param {string} [horizontalAlignment="left"] left | center | right * @param {string} [verticalAlignment="baseline"] baseline | bottom | middle | top */ drawText( x1, y1, height, rotation, value, horizontalAlignment = "left", verticalAlignment = "baseline" ) { this.activeLayer.addShape( new Text( x1, y1, height, rotation, value, horizontalAlignment, verticalAlignment ) ); return this; } /** * @param {[number, number][]} points - Array of points like [ [x1, y1], [x2, y2]... ] * @param {boolean} closed - Closed polyline flag * @param {number} startWidth - Default start width * @param {number} endWidth - Default end width */ drawPolyline(points, closed = false, startWidth = 0, endWidth = 0) { this.activeLayer.addShape( new Polyline(points, closed, startWidth, endWidth) ); return this; } /** * @param {[number, number, number][]} points - Array of points like [ [x1, y1, z1], [x2, y2, z1]... ] */ drawPolyline3d(points) { points.forEach((point) => { if (point.length !== 3) { throw "Require 3D coordinates"; } }); this.activeLayer.addShape(new Polyline3d(points)); return this; } /** * * @param {number} trueColor - Integer representing the true color, can be passed as an hexadecimal value of the form 0xRRGGBB */ setTrueColor(trueColor) { this.activeLayer.setTrueColor(trueColor); return this; } /** * Draw a spline. * @param {[Array]} controlPoints - Array of control points like [ [x1, y1], [x2, y2]... ] * @param {number} degree - Degree of spline: 2 for quadratic, 3 for cubic. Default is 3 * @param {[number]} knots - Knot vector array. If null, will use a uniform knot vector. Default is null * @param {[number]} weights - Control point weights. If provided, must be one weight for each control point. Default is null * @param {[Array]} fitPoints - Array of fit points like [ [x1, y1], [x2, y2]... ] */ drawSpline( controlPoints, degree = 3, knots = null, weights = null, fitPoints = [] ) { this.activeLayer.addShape( new Spline(controlPoints, degree, knots, weights, fitPoints) ); return this; } /** * Draw an ellipse. * @param {number} x1 - Center x * @param {number} y1 - Center y * @param {number} majorAxisX - Endpoint x of major axis, relative to center * @param {number} majorAxisY - Endpoint y of major axis, relative to center * @param {number} axisRatio - Ratio of minor axis to major axis * @param {number} startAngle - Start angle * @param {number} endAngle - End angle */ drawEllipse( x1, y1, majorAxisX, majorAxisY, axisRatio, startAngle = 0, endAngle = 2 * Math.PI ) { this.activeLayer.addShape( new Ellipse( x1, y1, majorAxisX, majorAxisY, axisRatio, startAngle, endAngle ) ); return this; } /** * @param {number} x1 - x * @param {number} y1 - y * @param {number} z1 - z * @param {number} x2 - x * @param {number} y2 - y * @param {number} z2 - z * @param {number} x3 - x * @param {number} y3 - y * @param {number} z3 - z * @param {number} x4 - x * @param {number} y4 - y * @param {number} z4 - z */ drawFace(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) { this.activeLayer.addShape( new Face(x1, y1, z1, x2, y2, z2, x3, y3, z3, x4, y4, z4) ); return this; } _ltypeTable() { const t = new Table("LTYPE"); const ltypes = Object.values(this.lineTypes); for (const lt of ltypes) t.add(lt); return t; } _layerTable(manager) { const t = new Table("LAYER"); const layers = Object.values(this.layers); for (const l of layers) t.add(l); return t; } /** * @see https://www.autodesk.com/techpubs/autocad/acadr14/dxf/header_section_al_u05_c.htm * @see https://www.autodesk.com/techpubs/autocad/acad2000/dxf/header_section_group_codes_dxf_02.htm * * @param {string} variable * @param {array} values Array of "two elements arrays". [ [value1_GroupCode, value1_value], [value2_GroupCode, value2_value] ] */ header(variable, values) { this.headers[variable] = values; return this; } /** * * @param {string} unit see Drawing.UNITS */ setUnits(unit) { let value = typeof Drawing.UNITS[unit] != "undefined" ? Drawing.UNITS[unit] : Drawing.UNITS["Unitless"]; this.header("INSUNITS", [[70, Drawing.UNITS[unit]]]); return this; } /** Generate additional DXF metadata which are required to successfully open resulted document * in AutoDesk products. Call this method before serializing the drawing to get the most * compatible result. */ generateAutocadExtras() { if (!this.headers["ACADVER"]) { /* AutoCAD 2007 version. */ this.header("ACADVER", [[1, "AC1021"]]); } if (!this.lineTypes["ByBlock"]) { this.addLineType("ByBlock", "", []); } if (!this.lineTypes["ByLayer"]) { this.addLineType("ByLayer", "", []); } let vpTable = this.tables["VPORT"]; if (!vpTable) { vpTable = this.addTable("VPORT"); } let styleTable = this.tables["STYLE"]; if (!styleTable) { styleTable = this.addTable("STYLE"); } if (!this.tables["VIEW"]) { this.addTable("VIEW"); } if (!this.tables["UCS"]) { this.addTable("UCS"); } let appIdTable = this.tables["APPID"]; if (!appIdTable) { appIdTable = this.addTable("APPID"); } if (!this.tables["DIMSTYLE"]) { const t = new DimStyleTable("DIMSTYLE"); this.tables["DIMSTYLE"] = t; } vpTable.add(new Viewport("*ACTIVE", 1000)); /* Non-default text alignment is not applied without this entry. */ styleTable.add(new TextStyle("standard")); appIdTable.add(new AppId("ACAD")); this.modelSpace = this.addBlock("*Model_Space"); this.addBlock("*Paper_Space"); const d = new Dictionary(); this.dictionary.addChildDictionary("ACAD_GROUP", d); } _tagsManager() { const manager = new TagsManager(); // Setup const blockRecordTable = new Table("BLOCK_RECORD"); const blocks = Object.values(this.blocks); for (const b of blocks) { const r = new BlockRecord(b.name); blockRecordTable.add(r); } const ltypeTable = this._ltypeTable(); const layerTable = this._layerTable(); // Header section start. manager.start("HEADER"); manager.addHeaderVariable("HANDSEED", [[5, Handle.peek()]]); const variables = Object.entries(this.headers); for (const v of variables) { const [name, values] = v; manager.addHeaderVariable(name, values); } manager.end(); // Header section end. // Classes section start. manager.start("CLASSES"); // Empty CLASSES section for compatibility manager.end(); // Classes section end. // Tables section start. manager.start("TABLES"); ltypeTable.tags(manager); layerTable.tags(manager); const tables = Object.values(this.tables); for (const t of tables) { t.tags(manager); } blockRecordTable.tags(manager); manager.end(); // Tables section end. // Blocks section start. manager.start("BLOCKS"); for (const b of blocks) { b.tags(manager); } manager.end(); // Blocks section end. // Entities section start. manager.start("ENTITIES"); const layers = Object.values(this.layers); for (const l of layers) { l.shapesTags(this.modelSpace, manager); } manager.end(); // Entities section end. // Objects section start. manager.start("OBJECTS"); this.dictionary.tags(manager); manager.end(); // Objects section end. manager.push(0, "EOF"); return manager; } toDxfString() { return this._tagsManager().toDxfString(); } } //AutoCAD Color Index (ACI) //http://sub-atomic.com/~moses/acadcolors.html Drawing.ACI = { LAYER: 0, RED: 1, YELLOW: 2, GREEN: 3, CYAN: 4, BLUE: 5, MAGENTA: 6, WHITE: 7, }; Drawing.LINE_TYPES = [ { name: "CONTINUOUS", description: "______", elements: [] }, { name: "DASHED", description: "_ _ _ ", elements: [5.0, -5.0] }, { name: "DOTTED", description: ". . . ", elements: [0.0, -5.0] }, ]; Drawing.LAYERS = [ { name: "0", colorNumber: Drawing.ACI.WHITE, lineTypeName: "CONTINUOUS" }, ]; //https://www.autodesk.com/techpubs/autocad/acad2000/dxf/header_section_group_codes_dxf_02.htm Drawing.UNITS = { Unitless: 0, Inches: 1, Feet: 2, Miles: 3, Millimeters: 4, Centimeters: 5, Meters: 6, Kilometers: 7, Microinches: 8, Mils: 9, Yards: 10, Angstroms: 11, Nanometers: 12, Microns: 13, Decimeters: 14, Decameters: 15, Hectometers: 16, Gigameters: 17, "Astronomical units": 18, "Light years": 19, Parsecs: 20, }; module.exports = Drawing;