diff --git a/src/entityToPolyline.js b/src/entityToPolyline.js index 07a24d3..55a728f 100644 --- a/src/entityToPolyline.js +++ b/src/entityToPolyline.js @@ -72,7 +72,7 @@ const interpolateEllipse = (cx, cy, rx, ry, start, end, rotationAngle) => { * @param knots the knot vector * @returns the polyline */ -const interpolateBSpline = (controlPoints, degree, knots, interpolationsPerSplineSegment) => { +export const interpolateBSpline = (controlPoints, degree, knots, interpolationsPerSplineSegment) => { const polyline = [] const controlPointsForLib = controlPoints.map(function (p) { return [p.x, p.y] diff --git a/src/toSVG.js b/src/toSVG.js index 964e229..4472483 100644 --- a/src/toSVG.js +++ b/src/toSVG.js @@ -7,6 +7,7 @@ import getRGBForEntity from './getRGBForEntity' import logger from './util/logger' import rotate from './util/rotate' import rgbToColorAttribute from './util/rgbToColorAttribute' +import toPiecewiseBezier from './util/toPiecewiseBezier' import transformBoundingBoxAndElement from './transformBoundingBoxAndElement' const addFlipXIfApplicable = (entity, { bbox, element }) => { @@ -136,6 +137,32 @@ const arc = (entity) => { return transformBoundingBoxAndElement(bbox, element, entity.transforms) } +export const piecewiseToPaths = (k, controlPoints) => { + const nSegments = (controlPoints.length - 1) / (k - 1) + const paths = [] + for (let i = 0; i < nSegments; ++i) { + const cp = controlPoints.slice(i * (k - 1)) + if (k === 4) { + paths.push(``) + } else if (k === 3) { + paths.push(``) + } + } + return paths +} + +const bezier = (entity) => { + let bbox = new Box2() + entity.controlPoints.forEach(p => { + bbox = bbox.expandByPoint(p) + }) + const k = entity.degree + 1 + const piecewise = toPiecewiseBezier(k, entity.controlPoints, entity.knots) + const paths = piecewiseToPaths(k, piecewise.controlPoints) + let element = `${paths.join('')}` + return transformBoundingBoxAndElement(bbox, element, entity.transforms) +} + /** * Switcth the appropriate function on entity type. CIRCLE, ARC and ELLIPSE * produce native SVG elements, the rest produce interpolated polylines. @@ -150,7 +177,17 @@ const entityToBoundsAndElement = (entity) => { return arc(entity) case 'LINE': case 'LWPOLYLINE': - case 'SPLINE': + case 'SPLINE': { + if ((entity.degree === 2) || (entity.degree === 3)) { + try { + return bezier(entity) + } catch (err) { + return polyline(entity) + } + } else { + return polyline(entity) + } + } case 'POLYLINE': { return polyline(entity) } diff --git a/src/util/insertKnot.js b/src/util/insertKnot.js new file mode 100644 index 0000000..c22dd57 --- /dev/null +++ b/src/util/insertKnot.js @@ -0,0 +1,62 @@ +/** + * Knot insertion is known as "Boehm's algorithm" + * + * https://math.stackexchange.com/questions/417859/convert-a-b-spline-into-bezier-curves + * code adapted from http://preserve.mactech.com/articles/develop/issue_25/schneider.html + */ +export default (k, controlPoints, knots, newKnot) => { + const x = knots + const b = controlPoints + const n = controlPoints.length + let i = 0 + let foundIndex = false + for (let j = 0; j < n + k; j++) { + if (newKnot > x[j] && newKnot <= x[j + 1]) { + i = j + foundIndex = true + break + } + } + if (!foundIndex) { + throw new Error('invalid new knot') + } + + const xHat = [] + for (let j = 0; j < n + k + 1; j++) { + if (j <= i) { + xHat[j] = x[j] + } else if (j === i + 1) { + xHat[j] = newKnot + } else { + xHat[j] = x[j - 1] + } + } + + let alpha + const bHat = [] + for (let j = 0; j < n + 1; j++) { + if (j <= i - k + 1) { + alpha = 1 + } else if (i - k + 2 <= j && j <= i) { + if (x[j + k - 1] - x[j] === 0) { + alpha = 0 + } else { + alpha = (newKnot - x[j]) / (x[j + k - 1] - x[j]) + } + } else { + alpha = 0 + } + + if (alpha === 0) { + bHat[j] = b[j - 1] + } else if (alpha === 1) { + bHat[j] = b[j] + } else { + bHat[j] = { + x: (1 - alpha) * b[j - 1].x + alpha * b[j].x, + y: (1 - alpha) * b[j - 1].y + alpha * b[j].y + } + } + } + return { controlPoints: bHat, knots: xHat } +} diff --git a/src/util/toPiecewiseBezier.js b/src/util/toPiecewiseBezier.js new file mode 100644 index 0000000..9647fd2 --- /dev/null +++ b/src/util/toPiecewiseBezier.js @@ -0,0 +1,62 @@ +import insertKnot from './insertKnot' + +/** + * For a pinned spline, the knots have to be repeated k times + * (where k is the order), at both the beginning and the end + */ +export const checkPinned = (k, knots) => { + // Pinned at the start + for (let i = 1; i < k; ++i) { + if (knots[i] !== knots[0]) { + throw Error(`not pinned. order: ${k} knots: ${knots}`) + } + } + // Pinned at the end + for (let i = knots.length - 2; i > knots.length - k - 1; --i) { + if (knots[i] !== knots[knots.length - 1]) { + throw Error(`not pinned. order: ${k} knots: ${knots}`) + } + } +} + +const multiplicity = (knots, index) => { + let m = 1 + for (let i = index + 1; i < knots.length; ++i) { + if (knots[i] === knots[index]) { + ++m + } else { + break + } + } + return m +} + +/** + * https://saccade.com/writing/graphics/KnotVectors.pdf + * A quadratic piecewise Bézier knot vector with seven control points + * will look like this [0 0 0 1 1 2 2 3 3 3]. In general, in a + * piecewise Bézier knot vector the first k knots are the same, + * then each subsequent group of k-1 knots is the same, + * until you get to the end. + */ +export const computeInsertions = (k, knots) => { + const inserts = [] + let i = k + while (i < knots.length - k) { + const knot = knots[i] + const m = multiplicity(knots, i) + for (let j = 0; j < k - m - 1; ++j) { + inserts.push(knot) + } + i = i + m + } + return inserts +} + +export default (k, controlPoints, knots) => { + checkPinned(k, knots) + const insertions = computeInsertions(k, knots) + return insertions.reduce((acc, tNew) => { + return insertKnot(k, acc.controlPoints, acc.knots, tNew) + }, { controlPoints, knots }) +} diff --git a/test/functional/toBezier.html b/test/functional/toBezier.html new file mode 100644 index 0000000..e6c6412 --- /dev/null +++ b/test/functional/toBezier.html @@ -0,0 +1,16 @@ + + + + + + + + +
+ + + diff --git a/test/functional/toBezier.test.js b/test/functional/toBezier.test.js new file mode 100644 index 0000000..00b171b --- /dev/null +++ b/test/functional/toBezier.test.js @@ -0,0 +1,79 @@ +import React from 'react' +import { render } from 'react-dom' +import { HashRouter } from 'react-router-dom' + +import { interpolateBSpline } from '../../src/entityToPolyline' +import toPiecewiseBezier from '../../src/util/toPiecewiseBezier' +import { piecewiseToPaths } from '../../src/toSVG' + +const controlPoints = [ + { x: 0, y: 0 }, + { x: 10, y: 0 }, + { x: 10, y: 10 }, + { x: 0, y: 10 }, + { x: 0, y: 20 }, + { x: 10, y: 20 } +] +const k = 4 +const knots = [0, 0, 0, 0, 1, 2, 3, 3, 3, 3] +const viewBox = '-1 -21 12 22' + +// const controlPoints = [ +// { x: 0, y: 0 }, +// { x: 122.4178296875701, y: -38.53600688262475 }, +// { x: 77.52934654015353, y: 149.4771453152231 }, +// { x: 200, y: 100 } +// ] +// const k = 3 +// const knots = [0, 0, 0, 0.5, 1, 1, 1] +// const viewBox = '-1 -160 200 200' + +const interpolated0 = interpolateBSpline(controlPoints, k - 1, knots) + +const polylineToPath = (polyline) => { + const d = polyline.reduce(function (acc, point, i) { + acc += (i === 0) ? 'M' : 'L' + acc += point[0] + ',' + point[1] + return acc + }, '') + return +} + +const result = toPiecewiseBezier(k, controlPoints, knots) +const interpolated1 = interpolateBSpline(result.controlPoints, k - 1, result.knots) +const paths = piecewiseToPaths(k, result.controlPoints, k.knots) + +render( +
+ + + {polylineToPath(interpolated0)} + + + + + {polylineToPath(interpolated1)} + + + + + +
+
, document.getElementById('contents')) diff --git a/test/functional/toPolylines.test.js b/test/functional/toPolylines.test.js index be09e2a..e2aa270 100644 --- a/test/functional/toPolylines.test.js +++ b/test/functional/toPolylines.test.js @@ -74,7 +74,8 @@ const names = [ 'issue28', 'issue29', 'issue39', - 'issue42' + 'issue42', + 'splineA' ] const dxfs = names.map(name => require(`../resources/${name}.dxf`)) const svgs = dxfs.map(contents => toSVG(new Helper(contents).toPolylines())) diff --git a/test/functional/toSVG.test.js b/test/functional/toSVG.test.js index fdf2151..24161fd 100644 --- a/test/functional/toSVG.test.js +++ b/test/functional/toSVG.test.js @@ -29,7 +29,8 @@ const names = [ 'issue28', 'issue29', 'issue39', - 'issue42' + 'issue42', + 'splineA' ] const dxfs = names.map(name => require(`../resources/${name}.dxf`)) const svgs = dxfs.map(contents => new Helper(contents).toSVG()) diff --git a/test/functional/webpack.config.js b/test/functional/webpack.config.js index 36f69c7..5da6d98 100644 --- a/test/functional/webpack.config.js +++ b/test/functional/webpack.config.js @@ -17,6 +17,11 @@ module.exports = { `webpack-dev-server/client?http://localhost:${port}`, 'webpack/hot/dev-server', path.resolve(__dirname, 'toSVG.test.js') + ], + 'toBezier.test': [ + `webpack-dev-server/client?http://localhost:${port}`, + 'webpack/hot/dev-server', + path.resolve(__dirname, 'toBezier.test.js') ] }, output: { diff --git a/test/resources/splineA.dxf b/test/resources/splineA.dxf new file mode 100644 index 0000000..b86254a --- /dev/null +++ b/test/resources/splineA.dxf @@ -0,0 +1,2758 @@ +999 +dxfrw 0.6.3 + 0 +SECTION + 2 +HEADER + 9 +$ACADVER + 1 +AC1021 + 9 +$DWGCODEPAGE + 3 +ANSI_1252 + 9 +$INSBASE + 10 +0 + 20 +0 + 30 +0 + 9 +$EXTMIN + 10 +-40.01848471883363 + 20 +-12.5 + 30 +0 + 9 +$EXTMAX + 10 +312.5 + 20 +211.7119175361894 + 30 +0 + 9 +$LIMMIN + 10 +0 + 20 +0 + 9 +$LIMMAX + 10 +420 + 20 +297 + 9 +$ORTHOMODE + 70 + 0 + 9 +$REGENMODE + 70 + 1 + 9 +$FILLMODE + 70 + 1 + 9 +$QTEXTMODE + 70 + 0 + 9 +$MIRRTEXT + 70 + 0 + 9 +$LTSCALE + 40 +1 + 9 +$ATTMODE + 70 + 0 + 9 +$TEXTSIZE + 40 +2.5 + 9 +$TRACEWID + 40 +15.68 + 9 +$TEXTSTYLE + 7 +STANDARD + 9 +$CLAYER + 8 +0 + 9 +$CELTYPE + 6 +BYLAYER + 9 +$CECOLOR + 62 + 256 + 9 +$CELTSCALE + 40 +1 + 9 +$DISPSILH + 70 + 0 + 9 +$DIMSCALE + 40 +2.5 + 9 +$DIMASZ + 40 +2.5 + 9 +$DIMEXO + 40 +0.625 + 9 +$DIMDLI + 40 +3.75 + 9 +$DIMRND + 40 +0 + 9 +$DIMDLE + 40 +0 + 9 +$DIMEXE + 40 +1.25 + 9 +$DIMTP + 40 +0 + 9 +$DIMTM + 40 +0 + 9 +$DIMTXT + 40 +2.5 + 9 +$DIMCEN + 40 +2.5 + 9 +$DIMTSZ + 40 +0 + 9 +$DIMTOL + 70 + 0 + 9 +$DIMLIM + 70 + 0 + 9 +$DIMTIH + 70 + 0 + 9 +$DIMTOH + 70 + 0 + 9 +$DIMSE1 + 70 + 0 + 9 +$DIMSE2 + 70 + 0 + 9 +$DIMTAD + 70 + 1 + 9 +$DIMZIN + 70 + 8 + 9 +$DIMBLK + 1 + + 9 +$DIMASO + 70 + 1 + 9 +$DIMSHO + 70 + 1 + 9 +$DIMPOST + 1 + + 9 +$DIMAPOST + 1 + + 9 +$DIMALT + 70 + 0 + 9 +$DIMALTD + 70 + 3 + 9 +$DIMALTF + 40 +0.03937 + 9 +$DIMLFAC + 40 +1 + 9 +$DIMTOFL + 70 + 1 + 9 +$DIMTVP + 40 +0 + 9 +$DIMTIX + 70 + 0 + 9 +$DIMSOXD + 70 + 0 + 9 +$DIMSAH + 70 + 0 + 9 +$DIMBLK1 + 1 + + 9 +$DIMBLK2 + 1 + + 9 +$DIMSTYLE + 2 +STANDARD + 9 +$DIMCLRD + 70 + 0 + 9 +$DIMCLRE + 70 + 0 + 9 +$DIMCLRT + 70 + 0 + 9 +$DIMTFAC + 40 +1 + 9 +$DIMGAP + 40 +0.625 + 9 +$DIMJUST + 70 + 0 + 9 +$DIMSD1 + 70 + 0 + 9 +$DIMSD2 + 70 + 0 + 9 +$DIMTOLJ + 70 + 0 + 9 +$DIMTZIN + 70 + 8 + 9 +$DIMALTZ + 70 + 0 + 9 +$DIMALTTZ + 70 + 0 + 9 +$DIMUPT + 70 + 0 + 9 +$DIMDEC + 70 + 2 + 9 +$DIMTDEC + 70 + 2 + 9 +$DIMALTU + 70 + 2 + 9 +$DIMALTTD + 70 + 3 + 9 +$DIMTXSTY + 7 +STANDARD + 9 +$DIMAUNIT + 70 + 0 + 9 +$DIMADEC + 70 + 0 + 9 +$DIMALTRND + 40 +0 + 9 +$DIMAZIN + 70 + 0 + 9 +$DIMDSEP + 70 + 44 + 9 +$DIMATFIT + 70 + 3 + 9 +$DIMFRAC + 70 + 0 + 9 +$DIMLDRBLK + 1 +STANDARD + 9 +$DIMLUNIT + 70 + 2 + 9 +$DIMLWD + 70 + -2 + 9 +$DIMLWE + 70 + -2 + 9 +$DIMTMOVE + 70 + 0 + 9 +$DIMFXL + 40 +1 + 9 +$DIMFXLON + 70 + 0 + 9 +$DIMJOGANG + 40 +0.7854 + 9 +$DIMTFILL + 70 + 0 + 9 +$DIMTFILLCLR + 70 + 0 + 9 +$DIMARCSYM + 70 + 0 + 9 +$DIMLTYPE + 6 + + 9 +$DIMLTEX1 + 6 + + 9 +$DIMLTEX2 + 6 + + 9 +$LUNITS + 70 + 2 + 9 +$LUPREC + 70 + 4 + 9 +$SKETCHINC + 40 +1 + 9 +$FILLETRAD + 40 +0 + 9 +$AUNITS + 70 + 0 + 9 +$AUPREC + 70 + 2 + 9 +$MENU + 1 +. + 9 +$ELEVATION + 40 +0 + 9 +$PELEVATION + 40 +0 + 9 +$THICKNESS + 40 +0 + 9 +$LIMCHECK + 70 + 0 + 9 +$CHAMFERA + 40 +0 + 9 +$CHAMFERB + 40 +0 + 9 +$CHAMFERC + 40 +0 + 9 +$CHAMFERD + 40 +0 + 9 +$SKPOLY + 70 + 0 + 9 +$USRTIMER + 70 + 1 + 9 +$ANGBASE + 50 +0 + 9 +$ANGDIR + 70 + 0 + 9 +$PDMODE + 70 + 34 + 9 +$PDSIZE + 40 +0 + 9 +$PLINEWID + 40 +0 + 9 +$SPLFRAME + 70 + 0 + 9 +$SPLINETYPE + 70 + 2 + 9 +$SPLINESEGS + 70 + 8 + 9 +$HANDSEED + 5 +20000 + 9 +$SURFTAB1 + 70 + 6 + 9 +$SURFTAB2 + 70 + 6 + 9 +$SURFTYPE + 70 + 6 + 9 +$SURFU + 70 + 6 + 9 +$SURFV + 70 + 6 + 9 +$UCSBASE + 2 + + 9 +$UCSNAME + 2 + + 9 +$UCSORG + 10 +0 + 20 +0 + 30 +0 + 9 +$UCSXDIR + 10 +1 + 20 +0 + 30 +0 + 9 +$UCSYDIR + 10 +0 + 20 +1 + 30 +0 + 9 +$UCSORTHOREF + 2 + + 9 +$UCSORTHOVIEW + 70 + 0 + 9 +$UCSORGTOP + 10 +0 + 20 +0 + 30 +0 + 9 +$UCSORGBOTTOM + 10 +0 + 20 +0 + 30 +0 + 9 +$UCSORGLEFT + 10 +0 + 20 +0 + 30 +0 + 9 +$UCSORGRIGHT + 10 +0 + 20 +0 + 30 +0 + 9 +$UCSORGFRONT + 10 +0 + 20 +0 + 30 +0 + 9 +$UCSORGBACK + 10 +0 + 20 +0 + 30 +0 + 9 +$PUCSBASE + 2 + + 9 +$PUCSNAME + 2 + + 9 +$PUCSORG + 10 +0 + 20 +0 + 30 +0 + 9 +$PUCSXDIR + 10 +1 + 20 +0 + 30 +0 + 9 +$PUCSYDIR + 10 +0 + 20 +1 + 30 +0 + 9 +$PUCSORTHOREF + 2 + + 9 +$PUCSORTHOVIEW + 70 + 0 + 9 +$PUCSORGTOP + 10 +0 + 20 +0 + 30 +0 + 9 +$PUCSORGBOTTOM + 10 +0 + 20 +0 + 30 +0 + 9 +$PUCSORGLEFT + 10 +0 + 20 +0 + 30 +0 + 9 +$PUCSORGRIGHT + 10 +0 + 20 +0 + 30 +0 + 9 +$PUCSORGFRONT + 10 +0 + 20 +0 + 30 +0 + 9 +$PUCSORGBACK + 10 +0 + 20 +0 + 30 +0 + 9 +$USERI1 + 70 + 0 + 9 +$USERI2 + 70 + 0 + 9 +$USERI3 + 70 + 0 + 9 +$USERI4 + 70 + 0 + 9 +$USERI5 + 70 + 0 + 9 +$USERR1 + 40 +0 + 9 +$USERR2 + 40 +0 + 9 +$USERR3 + 40 +0 + 9 +$USERR4 + 40 +0 + 9 +$USERR5 + 40 +0 + 9 +$WORLDVIEW + 70 + 1 + 9 +$SHADEDGE + 70 + 3 + 9 +$SHADEDIF + 70 + 70 + 9 +$TILEMODE + 70 + 1 + 9 +$MAXACTVP + 70 + 64 + 9 +$PINSBASE + 10 +0 + 20 +0 + 30 +0 + 9 +$PLIMCHECK + 70 + 0 + 9 +$PEXTMIN + 10 +0 + 20 +0 + 30 +0 + 9 +$PEXTMAX + 10 +0 + 20 +0 + 30 +0 + 9 +$GRIDMODE + 70 + 1 + 9 +$SNAPSTYLE + 70 + 0 + 9 +$PLIMMIN + 10 +0 + 20 +0 + 9 +$PLIMMAX + 10 +210 + 20 +297 + 9 +$UNITMODE + 70 + 0 + 9 +$VISRETAIN + 70 + 1 + 9 +$PLINEGEN + 70 + 0 + 9 +$PSLTSCALE + 70 + 1 + 9 +$TREEDEPTH + 70 + 3020 + 9 +$CMLSTYLE + 2 +Standard + 9 +$CMLJUST + 70 + 0 + 9 +$CMLSCALE + 40 +20 + 9 +$PROXYGRAPHICS + 70 + 1 + 9 +$MEASUREMENT + 70 + 1 + 9 +$CELWEIGHT +370 + -1 + 9 +$ENDCAPS +280 + 0 + 9 +$JOINSTYLE +280 + 0 + 9 +$LWDISPLAY +290 + 0 + 9 +$INSUNITS + 70 + 4 + 9 +$HYPERLINKBASE + 1 + + 9 +$STYLESHEET + 1 + + 9 +$XEDIT +290 + 1 + 9 +$CEPSNTYPE +380 + 0 + 9 +$PSTYLEMODE +290 + 1 + 9 +$EXTNAMES +290 + 1 + 9 +$PSVPSCALE + 40 +1 + 9 +$OLESTARTUP +290 + 0 + 9 +$SORTENTS +280 + 127 + 9 +$INDEXCTL +280 + 0 + 9 +$HIDETEXT +280 + 1 + 9 +$XCLIPFRAME +290 + 0 + 9 +$HALOGAP +280 + 0 + 9 +$OBSCOLOR + 70 + 257 + 9 +$OBSLTYPE +280 + 0 + 9 +$INTERSECTIONDISPLAY +280 + 0 + 9 +$INTERSECTIONCOLOR + 70 + 257 + 9 +$DIMASSOC +280 + 1 + 9 +$PROJECTNAME + 1 + + 9 +$CAMERADISPLAY +290 + 0 + 9 +$LENSLENGTH + 40 +50 + 9 +$CAMERAHEIGHT + 40 +0 + 9 +$STEPSPERSEC + 40 +2 + 9 +$STEPSIZE + 40 +50 + 9 +$3DDWFPREC + 40 +2 + 9 +$PSOLWIDTH + 40 +5 + 9 +$PSOLHEIGHT + 40 +80 + 9 +$LOFTANG1 + 40 +1.570796326794897 + 9 +$LOFTANG2 + 40 +1.570796326794897 + 9 +$LOFTMAG1 + 40 +0 + 9 +$LOFTMAG2 + 40 +0 + 9 +$LOFTPARAM + 70 + 7 + 9 +$LOFTNORMALS +280 + 1 + 9 +$LATITUDE + 40 +1 + 9 +$LONGITUDE + 40 +1 + 9 +$NORTHDIRECTION + 40 +0 + 9 +$TIMEZONE + 70 +-8000 + 9 +$LIGHTGLYPHDISPLAY +280 + 1 + 9 +$TILEMODELIGHTSYNCH +280 + 1 + 9 +$SOLIDHIST +280 + 1 + 9 +$SHOWHIST +280 + 1 + 9 +$DWFFRAME +280 + 2 + 9 +$DGNFRAME +280 + 0 + 9 +$REALWORLDSCALE +290 + 1 + 9 +$INTERFERECOLOR + 62 + 1 + 9 +$CSHADOW +280 + 0 + 9 +$SHADOWPLANELOCATION + 40 +0 + 0 +ENDSEC + 0 +SECTION + 2 +CLASSES + 0 +ENDSEC + 0 +SECTION + 2 +TABLES + 0 +TABLE + 2 +VPORT + 5 +8 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +VPORT + 5 +31 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbViewportTableRecord + 2 +*ACTIVE + 70 + 0 + 10 +0 + 20 +0 + 11 +1 + 21 +1 + 12 +212.457870755768 + 22 +101.319633546261 + 13 +0 + 23 +0 + 14 +10 + 24 +10 + 15 +10 + 25 +10 + 16 +0 + 26 +0 + 36 +1 + 17 +0 + 27 +0 + 37 +0 + 40 +373.524752125185 + 41 +1.756152125279642 + 42 +50 + 43 +0 + 44 +0 + 50 +0 + 51 +0 + 71 + 0 + 72 + 100 + 73 + 1 + 74 + 3 + 75 + 0 + 76 + 1 + 77 + 0 + 78 + 0 +281 + 0 + 65 + 1 +110 +0 +120 +0 +130 +0 +111 +1 +121 +0 +131 +0 +112 +0 +122 +1 +132 +0 + 79 + 0 +146 +0 +348 +10020 + 60 + 7 + 61 + 5 +292 +1 +282 + 1 +141 +0 +142 +0 + 63 + 250 +421 +3358443 + 0 +ENDTAB + 0 +TABLE + 2 +LTYPE + 5 +5 +330 +0 +100 +AcDbSymbolTable + 70 + 4 + 0 +LTYPE + 5 +14 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByBlock + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0 + 0 +LTYPE + 5 +15 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +ByLayer + 70 + 0 + 3 + + 72 + 65 + 73 + 0 + 40 +0 + 0 +LTYPE + 5 +16 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +Continuous + 70 + 0 + 3 +Solid line + 72 + 65 + 73 + 0 + 40 +0 + 0 +LTYPE + 5 +32 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DOT + 70 + 0 + 3 +Dot . . . . . . . . . . . . . . . . . . . . . . + 72 + 65 + 73 + 2 + 40 +6.35 + 49 +0 + 74 + 0 + 49 +-6.35 + 74 + 0 + 0 +LTYPE + 5 +33 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DOTTINY + 70 + 0 + 3 +Dot (.15x) ..................................... + 72 + 65 + 73 + 2 + 40 +0.9525 + 49 +0 + 74 + 0 + 49 +-0.9525 + 74 + 0 + 0 +LTYPE + 5 +34 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DOT2 + 70 + 0 + 3 +Dot (.5x) ..................................... + 72 + 65 + 73 + 2 + 40 +3.175 + 49 +0 + 74 + 0 + 49 +-3.175 + 74 + 0 + 0 +LTYPE + 5 +35 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DOTX2 + 70 + 0 + 3 +Dot (2x) . . . . . . . . . . . . . + 72 + 65 + 73 + 2 + 40 +12.7 + 49 +0 + 74 + 0 + 49 +-12.7 + 74 + 0 + 0 +LTYPE + 5 +36 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHED + 70 + 0 + 3 +Dashed _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + 72 + 65 + 73 + 2 + 40 +19.05 + 49 +12.7 + 74 + 0 + 49 +-6.35 + 74 + 0 + 0 +LTYPE + 5 +37 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHEDTINY + 70 + 0 + 3 +Dashed (.15x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + 72 + 65 + 73 + 2 + 40 +2.8575 + 49 +1.905 + 74 + 0 + 49 +-0.9525 + 74 + 0 + 0 +LTYPE + 5 +38 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHED2 + 70 + 0 + 3 +Dashed (.5x) _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + 72 + 65 + 73 + 2 + 40 +9.524999999999999 + 49 +6.35 + 74 + 0 + 49 +-3.175 + 74 + 0 + 0 +LTYPE + 5 +39 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHEDX2 + 70 + 0 + 3 +Dashed (2x) ____ ____ ____ ____ ____ ___ + 72 + 65 + 73 + 2 + 40 +38.09999999999999 + 49 +25.4 + 74 + 0 + 49 +-12.7 + 74 + 0 + 0 +LTYPE + 5 +3A +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHDOT + 70 + 0 + 3 +Dash dot __ . __ . __ . __ . __ . __ . __ . __ + 72 + 65 + 73 + 4 + 40 +25.4 + 49 +12.7 + 74 + 0 + 49 +-6.35 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-6.35 + 74 + 0 + 0 +LTYPE + 5 +3B +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHDOTTINY + 70 + 0 + 3 +Dash dot (.15x) _._._._._._._._._._._._._._._. + 72 + 65 + 73 + 4 + 40 +3.81 + 49 +1.905 + 74 + 0 + 49 +-0.9525 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-0.9525 + 74 + 0 + 0 +LTYPE + 5 +3C +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHDOT2 + 70 + 0 + 3 +Dash dot (.5x) _._._._._._._._._._._._._._._. + 72 + 65 + 73 + 4 + 40 +12.7 + 49 +6.35 + 74 + 0 + 49 +-3.175 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-3.175 + 74 + 0 + 0 +LTYPE + 5 +3D +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DASHDOTX2 + 70 + 0 + 3 +Dash dot (2x) ____ . ____ . ____ . ___ + 72 + 65 + 73 + 4 + 40 +50.8 + 49 +25.4 + 74 + 0 + 49 +-12.7 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-12.7 + 74 + 0 + 0 +LTYPE + 5 +3E +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DIVIDE + 70 + 0 + 3 +Divide ____ . . ____ . . ____ . . ____ . . ____ + 72 + 65 + 73 + 6 + 40 +31.75 + 49 +12.7 + 74 + 0 + 49 +-6.35 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-6.35 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-6.35 + 74 + 0 + 0 +LTYPE + 5 +3F +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DIVIDETINY + 70 + 0 + 3 +Divide (.15x) __..__..__..__..__..__..__..__.._ + 72 + 65 + 73 + 6 + 40 +4.7625 + 49 +1.905 + 74 + 0 + 49 +-0.9525 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-0.9525 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-0.9525 + 74 + 0 + 0 +LTYPE + 5 +40 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DIVIDE2 + 70 + 0 + 3 +Divide (.5x) __..__..__..__..__..__..__..__.._ + 72 + 65 + 73 + 6 + 40 +15.875 + 49 +6.35 + 74 + 0 + 49 +-3.175 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-3.175 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-3.175 + 74 + 0 + 0 +LTYPE + 5 +41 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +DIVIDEX2 + 70 + 0 + 3 +Divide (2x) ________ . . ________ . . _ + 72 + 65 + 73 + 6 + 40 +63.5 + 49 +25.4 + 74 + 0 + 49 +-12.7 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-12.7 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-12.7 + 74 + 0 + 0 +LTYPE + 5 +42 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BORDER + 70 + 0 + 3 +Border __ __ . __ __ . __ __ . __ __ . __ __ . + 72 + 65 + 73 + 6 + 40 +44.45 + 49 +12.7 + 74 + 0 + 49 +-6.35 + 74 + 0 + 49 +12.7 + 74 + 0 + 49 +-6.35 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-6.35 + 74 + 0 + 0 +LTYPE + 5 +43 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BORDERTINY + 70 + 0 + 3 +Border (.15x) __.__.__.__.__.__.__.__.__.__.__. + 72 + 65 + 73 + 6 + 40 +6.6675 + 49 +1.905 + 74 + 0 + 49 +-0.9525 + 74 + 0 + 49 +1.905 + 74 + 0 + 49 +-0.9525 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-0.9525 + 74 + 0 + 0 +LTYPE + 5 +44 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BORDER2 + 70 + 0 + 3 +Border (.5x) __.__.__.__.__.__.__.__.__.__.__. + 72 + 65 + 73 + 6 + 40 +22.225 + 49 +6.35 + 74 + 0 + 49 +-3.175 + 74 + 0 + 49 +6.35 + 74 + 0 + 49 +-3.175 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-3.175 + 74 + 0 + 0 +LTYPE + 5 +45 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +BORDERX2 + 70 + 0 + 3 +Border (2x) ____ ____ . ____ ____ . ___ + 72 + 65 + 73 + 6 + 40 +88.89999999999999 + 49 +25.4 + 74 + 0 + 49 +-12.7 + 74 + 0 + 49 +25.4 + 74 + 0 + 49 +-12.7 + 74 + 0 + 49 +0 + 74 + 0 + 49 +-12.7 + 74 + 0 + 0 +LTYPE + 5 +46 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CENTER + 70 + 0 + 3 +Center ____ _ ____ _ ____ _ ____ _ ____ _ ____ + 72 + 65 + 73 + 4 + 40 +50.8 + 49 +31.75 + 74 + 0 + 49 +-6.35 + 74 + 0 + 49 +6.35 + 74 + 0 + 49 +-6.35 + 74 + 0 + 0 +LTYPE + 5 +47 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CENTERTINY + 70 + 0 + 3 +Center (.15x) ___ _ ___ _ ___ _ ___ _ ___ _ ___ + 72 + 65 + 73 + 4 + 40 +7.619999999999999 + 49 +4.7625 + 74 + 0 + 49 +-0.9525 + 74 + 0 + 49 +0.9525 + 74 + 0 + 49 +-0.9525 + 74 + 0 + 0 +LTYPE + 5 +48 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CENTER2 + 70 + 0 + 3 +Center (.5x) ___ _ ___ _ ___ _ ___ _ ___ _ ___ + 72 + 65 + 73 + 4 + 40 +28.575 + 49 +19.05 + 74 + 0 + 49 +-3.175 + 74 + 0 + 49 +3.175 + 74 + 0 + 49 +-3.175 + 74 + 0 + 0 +LTYPE + 5 +49 +330 +5 +100 +AcDbSymbolTableRecord +100 +AcDbLinetypeTableRecord + 2 +CENTERX2 + 70 + 0 + 3 +Center (2x) ________ __ ________ __ _____ + 72 + 65 + 73 + 4 + 40 +101.6 + 49 +63.5 + 74 + 0 + 49 +-12.7 + 74 + 0 + 49 +12.7 + 74 + 0 + 49 +-12.7 + 74 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +LAYER + 5 +2 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +LAYER + 5 +10 +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbLayerTableRecord + 2 +0 + 70 + 0 + 62 + 7 + 6 +CONTINUOUS +370 + 0 +390 +F + 0 +ENDTAB + 0 +TABLE + 2 +STYLE + 5 +3 +330 +0 +100 +AcDbSymbolTable + 70 + 3 + 0 +STYLE + 5 +4A +330 +2 +100 +AcDbSymbolTableRecord +100 +AcDbTextStyleTableRecord + 2 +Standard + 70 + 0 + 40 +0 + 41 +1 + 50 +0 + 71 + 0 + 42 +1 + 3 +txt + 4 + + 0 +ENDTAB + 0 +TABLE + 2 +VIEW + 5 +6 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +UCS + 5 +7 +330 +0 +100 +AcDbSymbolTable + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +APPID + 5 +9 +330 +0 +100 +AcDbSymbolTable + 70 + 1 + 0 +APPID + 5 +12 +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +ACAD + 70 + 0 + 0 +APPID + 5 +4B +330 +9 +100 +AcDbSymbolTableRecord +100 +AcDbRegAppTableRecord + 2 +LibreCad + 70 + 0 + 0 +ENDTAB + 0 +TABLE + 2 +DIMSTYLE + 5 +A +330 +0 +100 +AcDbSymbolTable + 70 + 1 +100 +AcDbDimStyleTable + 71 + 1 + 0 +DIMSTYLE +105 +4C +330 +A +100 +AcDbSymbolTableRecord +100 +AcDbDimStyleTableRecord + 2 +Standard + 70 + 0 + 40 +1 + 41 +2.5 + 42 +0.625 + 43 +0.38 + 44 +1.25 + 45 +0 + 46 +0 + 47 +0 + 48 +0 + 49 +1 +140 +2.5 +141 +0.09 +142 +2.5 +143 +25.4 +144 +1 +145 +0 +146 +1 +147 +0.625 +148 +0 + 71 + 0 + 72 + 0 + 73 + 0 + 74 + 1 + 75 + 0 + 76 + 0 + 77 + 0 + 78 + 1 + 79 + 0 +170 + 0 +171 + 2 +172 + 0 +173 + 0 +174 + 0 +175 + 0 +176 + 0 +177 + 0 +178 + 0 +179 + 0 +271 + 2 +272 + 4 +273 + 2 +274 + 2 +275 + 0 +276 + 0 +277 + 2 +278 + 0 +279 + 0 +280 + 0 +281 + 0 +282 + 0 +283 + 1 +284 + 0 +285 + 0 +286 + 0 +288 + 0 +289 + 3 +340 +standard +341 + +371 + -2 +372 + -2 + 0 +ENDTAB + 0 +TABLE + 2 +BLOCK_RECORD + 5 +1 +330 +0 +100 +AcDbSymbolTable + 70 + 2 + 0 +BLOCK_RECORD + 5 +1F +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Model_Space + 70 + 0 +280 + 1 +281 + 0 + 0 +BLOCK_RECORD + 5 +1E +330 +1 +100 +AcDbSymbolTableRecord +100 +AcDbBlockTableRecord + 2 +*Paper_Space + 70 + 0 +280 + 1 +281 + 0 + 0 +ENDTAB + 0 +ENDSEC + 0 +SECTION + 2 +BLOCKS + 0 +BLOCK + 5 +20 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Model_Space + 70 + 0 + 10 +0 + 20 +0 + 30 +0 + 3 +*Model_Space + 1 + + 0 +ENDBLK + 5 +21 +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +BLOCK + 5 +1C +330 +1B +100 +AcDbEntity + 8 +0 +100 +AcDbBlockBegin + 2 +*Paper_Space + 70 + 0 + 10 +0 + 20 +0 + 30 +0 + 3 +*Paper_Space + 1 + + 0 +ENDBLK + 5 +1D +330 +1F +100 +AcDbEntity + 8 +0 +100 +AcDbBlockEnd + 0 +ENDSEC + 0 +SECTION + 2 +ENTITIES + 0 +SPLINE + 5 +4D +100 +AcDbEntity + 8 +0 + 6 +ByLayer + 62 + 256 +370 + -1 +100 +AcDbSpline +210 +0 +220 +0 +230 +0 + 70 + 8 + 71 + 2 + 72 + 7 + 73 + 4 + 74 + 0 + 42 +1e-07 + 43 +1e-07 + 40 +0 + 40 +0 + 40 +0 + 40 +0.5 + 40 +1 + 40 +1 + 40 +1 + 10 +0 + 20 +0 + 30 +0 + 10 +122.4178296875701 + 20 +-38.53600688262475 + 30 +0 + 10 +77.52934654015353 + 20 +149.4771453152231 + 30 +0 + 10 +200 + 20 +100 + 30 +0 + 0 +ENDSEC + 0 +SECTION + 2 +OBJECTS + 0 +DICTIONARY + 5 +C +330 +0 +100 +AcDbDictionary +281 + 1 + 3 +ACAD_GROUP +350 +D + 0 +DICTIONARY + 5 +D +330 +C +100 +AcDbDictionary +281 + 1 + 0 +ENDSEC + 0 +EOF diff --git a/test/unit/insertKnot.test.js b/test/unit/insertKnot.test.js new file mode 100644 index 0000000..9c5199a --- /dev/null +++ b/test/unit/insertKnot.test.js @@ -0,0 +1,35 @@ +import expect from 'expect' + +import insertKnot from '../../src/util/insertKnot' + +describe('Insert knot', () => { + it('throws error if knot is invalid', () => { + const controlPoints = [ + { x: 0, y: 0 }, + { x: 10, y: 0 }, + { x: 10, y: 10 }, + { x: 0, y: 10 }, + { x: 0, y: 20 }, + { x: 10, y: 20 } + ] + const k = 4 + const knots = [0, 0, 0, 0, 1, 2, 3, 3, 3, 3] + expect(() => { + insertKnot(k, controlPoints, knots, 4) + }).toThrow(/^invalid new knot$/) + expect(() => { + insertKnot(k, controlPoints, knots, -1) + }).toThrow(/^invalid new knot$/) + const newSpline = insertKnot(k, controlPoints, knots, 1) + expect(newSpline.controlPoints).toEqual([ + { x: 0, y: 0 }, + { x: 10, y: 0 }, + { x: 10, y: 5 }, + { x: 6.666666666666668, y: 10 }, + { x: 0, y: 10 }, + { x: 0, y: 20 }, + { x: 10, y: 20 } + ]) + expect(newSpline.knots).toEqual([0, 0, 0, 0, 1, 1, 2, 3, 3, 3, 3]) + }) +}) diff --git a/test/unit/toPiecewiseBezier.test.js b/test/unit/toPiecewiseBezier.test.js new file mode 100644 index 0000000..319d171 --- /dev/null +++ b/test/unit/toPiecewiseBezier.test.js @@ -0,0 +1,47 @@ +import expect from 'expect' + +import { checkPinned, computeInsertions } from '../../src/util/toPiecewiseBezier' + +describe('Spline conversion to piecewise bezier', () => { + it('checks that the spline is pinned', () => { + expect(checkPinned(4, [0, 0, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 1, 1])) + expect(() => { + checkPinned(4, [0, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 1, 1]) + }).toThrow(/^not pinned. order: 4 knots: 0,0,0,0.2,0.4,0.6000000000000001,0.8,1,1,1,1$/) + expect(() => { + checkPinned(4, [0, 1, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 1, 1]) + }).toThrow(/^not pinned. order: 4 knots: 0,1,0,0,0.2,0.4,0.6000000000000001,0.8,1,1,1,1$/) + expect(() => { + checkPinned(4, [0, 0, 1, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 1, 1]) + }).toThrow(/^not pinned. order: 4 knots: 0,0,1,0,0.2,0.4,0.6000000000000001,0.8,1,1,1,1$/) + expect(() => { + checkPinned(4, [0, 0, 0, 1, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 1, 1]) + }).toThrow(/^not pinned. order: 4 knots: 0,0,0,1,0.2,0.4,0.6000000000000001,0.8,1,1,1,1$/) + + expect(() => { + checkPinned(4, [0, 0, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 0.9, 1, 1, 1]) + }).toThrow(/^not pinned. order: 4 knots: 0,0,0,0,0.2,0.4,0.6000000000000001,0.8,0.9,1,1,1$/) + expect(() => { + checkPinned(4, [0, 0, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 0.9, 1, 1]) + }).toThrow(/^not pinned. order: 4 knots: 0,0,0,0,0.2,0.4,0.6000000000000001,0.8,1,0.9,1,1$/) + expect(() => { + checkPinned(4, [0, 0, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 0.9, 1]) + }).toThrow(/^not pinned. order: 4 knots: 0,0,0,0,0.2,0.4,0.6000000000000001,0.8,1,1,0.9,1$/) + expect(() => { + checkPinned(4, [0, 0, 0, 0, 0.2, 0.4, 0.6000000000000001, 0.8, 1, 1, 1, 0.9]) + }).toThrow(/^not pinned. order: 4 knots: 0,0,0,0,0.2,0.4,0.6000000000000001,0.8,1,1,1,0.9$/) + }) + + it('computes the knots to be inserted for a piecewise bezier', () => { + expect(computeInsertions(4, [0, 0, 0, 0, 1, 2, 2, 2, 2])) + .toEqual([1, 1]) + expect(computeInsertions(4, [0, 0, 0, 0, 0.5, 2, 2, 2, 2])) + .toEqual([0.5, 0.5]) + expect(computeInsertions(4, [0, 0, 0, 0, 0.5, 0.5, 0.5, 2, 2, 2, 2])) + .toEqual([]) + expect(computeInsertions(3, [0, 0, 0, 1, 1, 2, 2, 2])) + .toEqual([]) + expect(computeInsertions(3, [0, 0, 0, 1, 2, 2, 2])) + .toEqual([1]) + }) +})