-
-
Notifications
You must be signed in to change notification settings - Fork 101
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): add tracespace core package
- Loading branch information
Showing
25 changed files
with
1,456 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -22,6 +22,7 @@ | |
"packageManager": "[email protected]", | ||
"dependencies": { | ||
"@tracespace/cli": "workspace:*", | ||
"@tracespace/core": "workspace:*", | ||
"@tracespace/fixtures": "workspace:*", | ||
"@tracespace/identify-layers": "workspace:*", | ||
"@tracespace/parser": "workspace:*", | ||
|
@@ -137,6 +138,7 @@ | |
"@typescript-eslint/consistent-type-assertions": "off", | ||
"@typescript-eslint/consistent-type-imports": "off", | ||
"@typescript-eslint/no-namespace": "off", | ||
"@typescript-eslint/no-unsafe-assignment": "off", | ||
"import/no-extraneous-dependencies": "off", | ||
"max-nested-callbacks": "off", | ||
"unicorn/no-array-for-each": "off", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
# @tracespace/core | ||
|
||
The core tracespace read / parse / plot / render pipeline, built up of the following libraries: | ||
|
||
- [@tracespace/identify-layers][] | ||
- [@tracespace/parser][] | ||
- [@tracespace/plotter][] | ||
- [@tracespace/renderer][] | ||
|
||
Part of the [tracespace][] collection of PCB visualization tools. | ||
|
||
[tracespace]: https://github.com/tracespace/tracespace | ||
[@tracespace/identify-layers]: ../identify-layers | ||
[@tracespace/parser]: ../parser | ||
[@tracespace/plotter]: ../plotter | ||
[@tracespace/renderer]: ../renderer | ||
|
||
## usage | ||
|
||
```js | ||
import fs from 'node:fs/promises | ||
import {read, plot, render} from '@tracespace/parser' | ||
const files = [ | ||
'top-copper.gbr', | ||
'top-solder-mask.gbr', | ||
'top-silk-screen.gbr', | ||
'bottom-copper.gbr', | ||
'bottom-solder-mask.gbr', | ||
'outline.gbr', | ||
'drill.xnc', | ||
] | ||
const readResult = await read(files) | ||
const plotResult = plot(readResult) | ||
const renderResult = plot(plotResult) | ||
await Promise.all([ | ||
fs.writeFile('top.svg', renderResult.top.svg) | ||
fs.writeFile('bottom.svg', renderResult.bottom.svg) | ||
]) | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
{ | ||
"name": "@tracespace/core", | ||
"publishConfig": { | ||
"access": "public" | ||
}, | ||
"version": "5.0.0-next.0", | ||
"description": "Core tracespace render pipeline", | ||
"types": "./lib/index.d.ts", | ||
"exports": { | ||
"types": "./lib/index.d.ts", | ||
"source": "./src/index.ts", | ||
"import": "./dist/tracespace-core.es.js", | ||
"require": "./dist/tracespace-core.umd.cjs" | ||
}, | ||
"files": [ | ||
"dist", | ||
"lib", | ||
"src", | ||
"!**/__tests__/**" | ||
], | ||
"type": "module", | ||
"sideEffects": false, | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/tracespace/tracespace.git", | ||
"directory": "packages/core" | ||
}, | ||
"scripts": { | ||
"build": "vite build" | ||
}, | ||
"keywords": [ | ||
"gerber", | ||
"excellon", | ||
"pcb", | ||
"circuit", | ||
"hardware", | ||
"electronics" | ||
], | ||
"contributors": [ | ||
"Mike Cousins <[email protected]> (https://mike.cousins.io)" | ||
], | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/tracespace/tracespace/issues" | ||
}, | ||
"homepage": "https://github.com/tracespace/tracespace#readme", | ||
"dependencies": { | ||
"@tracespace/identify-layers": "workspace:*", | ||
"@tracespace/parser": "workspace:*", | ||
"@tracespace/plotter": "workspace:*", | ||
"@tracespace/renderer": "workspace:*", | ||
"@tracespace/xml-id": "workspace:*" | ||
}, | ||
"devDependencies": { | ||
"@types/lodash-es": "^4.17.6", | ||
"hast-util-to-html": "^8.0.3", | ||
"lodash-es": "^4.17.21" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import {describe, beforeEach, afterEach, it, expect} from 'vitest' | ||
import {replaceEsm, reset} from 'testdouble-vitest' | ||
import * as td from 'testdouble' | ||
|
||
import {MM} from '@tracespace/parser' | ||
|
||
import type {ImageTree} from '@tracespace/plotter' | ||
|
||
describe('calculate board size', () => { | ||
let plotter: typeof import('@tracespace/plotter') | ||
let subject: typeof import('../calculate-size') | ||
|
||
beforeEach(async () => { | ||
plotter = await replaceEsm('@tracespace/plotter') | ||
subject = await import('../calculate-size') | ||
}) | ||
|
||
afterEach(() => { | ||
reset() | ||
}) | ||
|
||
it('should return an empty size if given no input', () => { | ||
td.when(plotter.BoundingBox.sum()).thenReturn([]) | ||
|
||
const result = subject.calculateSize([]) | ||
|
||
expect(result).to.eql([]) | ||
}) | ||
|
||
it('should sum up all layers if no outline', () => { | ||
const plotTree1: ImageTree = { | ||
type: plotter.IMAGE, | ||
units: MM, | ||
children: [ | ||
{type: plotter.IMAGE_LAYER, size: [0.1, 0.2, 0.3, 0.4], children: []}, | ||
], | ||
} | ||
|
||
const plotTree2: ImageTree = { | ||
type: plotter.IMAGE, | ||
units: MM, | ||
children: [ | ||
{type: plotter.IMAGE_LAYER, size: [0.4, 0.3, 0.2, 0.1], children: []}, | ||
], | ||
} | ||
|
||
td.when( | ||
plotter.BoundingBox.sum([0.1, 0.2, 0.3, 0.4], [0.4, 0.3, 0.2, 0.1]) | ||
).thenReturn([1, 2, 3, 4]) | ||
|
||
const result = subject.calculateSize([plotTree1, plotTree2]) | ||
|
||
expect(result).to.eql([1, 2, 3, 4]) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,209 @@ | ||
// @vitest-environment jsdom | ||
import {describe, beforeEach, afterEach, it, expect} from 'vitest' | ||
import {replaceEsm, reset} from 'testdouble-vitest' | ||
import * as td from 'testdouble' | ||
|
||
import { | ||
TYPE_COPPER, | ||
TYPE_OUTLINE, | ||
SIDE_ALL, | ||
SIDE_TOP, | ||
} from '@tracespace/identify-layers' | ||
|
||
import type {GerberTree} from '@tracespace/parser' | ||
import type {ImageTree} from '@tracespace/plotter' | ||
import type {SvgElement} from '@tracespace/renderer' | ||
import type {LayerIdentity} from '@tracespace/identify-layers' | ||
|
||
import type {ReadResult, PlotResult} from '..' | ||
import type {OutlineRender} from '../render-outline' | ||
|
||
describe('tracespace core', () => { | ||
let parser: typeof import('@tracespace/parser') | ||
let plotter: typeof import('@tracespace/plotter') | ||
let renderer: typeof import('@tracespace/renderer') | ||
let xmlId: typeof import('@tracespace/xml-id') | ||
let fileReader: typeof import('../read-file') | ||
let sizeCalculator: typeof import('../calculate-size') | ||
let layerTypeDeterminer: typeof import('../determine-layer-types') | ||
let outlineRenderer: typeof import('../render-outline') | ||
let svgStringifier: typeof import('../stringify-svg') | ||
let layerSorter: typeof import('../sort-layers') | ||
let subject: typeof import('..') | ||
|
||
beforeEach(async () => { | ||
parser = await replaceEsm('@tracespace/parser') | ||
plotter = await replaceEsm('@tracespace/plotter') | ||
renderer = await replaceEsm('@tracespace/renderer') | ||
xmlId = await replaceEsm('@tracespace/xml-id') | ||
fileReader = await replaceEsm('../read-file') | ||
sizeCalculator = await replaceEsm('../calculate-size') | ||
layerTypeDeterminer = await replaceEsm('../determine-layer-types') | ||
outlineRenderer = await replaceEsm('../render-outline') | ||
svgStringifier = await replaceEsm('../stringify-svg') | ||
layerSorter = await replaceEsm('../sort-layers') | ||
subject = await import('..') | ||
}) | ||
|
||
afterEach(() => { | ||
reset() | ||
}) | ||
|
||
it('should read a set of files', async () => { | ||
const files = [new File(['foo'], 'foo.gbr'), new File(['bar'], 'bar.gbr')] | ||
|
||
const parseTreeFoo = { | ||
type: parser.ROOT, | ||
filetype: parser.GERBER, | ||
} as GerberTree | ||
|
||
const parseTreeBar = { | ||
type: parser.ROOT, | ||
filetype: parser.DRILL, | ||
} as GerberTree | ||
|
||
const layerTypes: Record<string, LayerIdentity> = { | ||
'id-foo': {type: TYPE_COPPER, side: SIDE_TOP}, | ||
'id-bar': {type: TYPE_OUTLINE, side: SIDE_ALL}, | ||
} | ||
|
||
td.when(xmlId.random()).thenReturn('id-foo', 'id-bar', '') | ||
td.when(fileReader.readFile(files[0])).thenResolve('hello from') | ||
td.when(fileReader.readFile(files[1])).thenResolve('the other side') | ||
td.when(parser.parse('hello from')).thenReturn(parseTreeFoo) | ||
td.when(parser.parse('the other side')).thenReturn(parseTreeBar) | ||
td.when( | ||
layerTypeDeterminer.determineLayerTypes([ | ||
{id: 'id-foo', filename: 'foo.gbr', parseTree: parseTreeFoo}, | ||
{id: 'id-bar', filename: 'bar.gbr', parseTree: parseTreeBar}, | ||
]) | ||
).thenReturn(layerTypes) | ||
|
||
const result = await subject.read(files) | ||
|
||
expect(result).to.eql({ | ||
layers: [ | ||
{id: 'id-foo', filename: 'foo.gbr', type: TYPE_COPPER, side: SIDE_TOP}, | ||
{id: 'id-bar', filename: 'bar.gbr', type: TYPE_OUTLINE, side: SIDE_ALL}, | ||
], | ||
parseTreesById: {'id-foo': parseTreeFoo, 'id-bar': parseTreeBar}, | ||
}) | ||
}) | ||
|
||
it('should plot a set of read and parsed layers', () => { | ||
const parseTreeFoo = { | ||
type: parser.ROOT, | ||
filetype: parser.GERBER, | ||
} as GerberTree | ||
|
||
const parseTreeBar = { | ||
type: parser.ROOT, | ||
filetype: parser.DRILL, | ||
} as GerberTree | ||
|
||
const readResult: ReadResult = { | ||
layers: [ | ||
{id: 'id-foo', filename: 'foo.gbr', type: TYPE_COPPER, side: SIDE_TOP}, | ||
{id: 'id-bar', filename: 'bar.gbr', type: TYPE_OUTLINE, side: SIDE_ALL}, | ||
], | ||
parseTreesById: {'id-foo': parseTreeFoo, 'id-bar': parseTreeBar}, | ||
} | ||
|
||
const plotTreeFoo = {type: plotter.IMAGE, units: parser.MM} as ImageTree | ||
const plotTreeBar = {type: plotter.IMAGE, units: parser.IN} as ImageTree | ||
|
||
td.when(plotter.plot(parseTreeFoo)).thenReturn(plotTreeFoo) | ||
td.when(plotter.plot(parseTreeBar)).thenReturn(plotTreeBar) | ||
td.when( | ||
sizeCalculator.calculateSize([plotTreeFoo, plotTreeBar]) | ||
).thenReturn([1, 2, 3, 4]) | ||
|
||
const result = subject.plot(readResult) | ||
|
||
expect(result).to.eql({ | ||
layers: [ | ||
{id: 'id-foo', filename: 'foo.gbr', type: TYPE_COPPER, side: SIDE_TOP}, | ||
{id: 'id-bar', filename: 'bar.gbr', type: TYPE_OUTLINE, side: SIDE_ALL}, | ||
], | ||
size: [1, 2, 3, 4], | ||
plotTreesById: {'id-foo': plotTreeFoo, 'id-bar': plotTreeBar}, | ||
}) | ||
}) | ||
|
||
it('should render a set of plotted layers', () => { | ||
const plotTreeFoo = {type: plotter.IMAGE, units: parser.MM} as ImageTree | ||
const plotTreeBar = {type: plotter.IMAGE, units: parser.IN} as ImageTree | ||
const plotResult: PlotResult = { | ||
layers: [ | ||
{id: 'id-foo', filename: 'foo.gbr', type: TYPE_COPPER, side: SIDE_TOP}, | ||
{id: 'id-bar', filename: 'bar.gbr', type: TYPE_OUTLINE, side: SIDE_ALL}, | ||
], | ||
size: [1, 2, 3, 4], | ||
plotTreesById: {'id-foo': plotTreeFoo, 'id-bar': plotTreeBar}, | ||
} | ||
|
||
const svgTreeFoo: SvgElement = { | ||
type: 'element', | ||
tagName: 'foo', | ||
children: [], | ||
} | ||
const svgTreeBar: SvgElement = { | ||
type: 'element', | ||
tagName: 'bar', | ||
children: [], | ||
} | ||
|
||
const mechanicalLayers = { | ||
drill: ['id-foo'], | ||
outline: 'id-bar', | ||
} | ||
const topLayers = { | ||
copper: ['id-foo'], | ||
solderMask: ['id-foo'], | ||
silkScreen: ['id-foo'], | ||
solderPaste: ['id-foo'], | ||
} | ||
const bottomLayers = { | ||
copper: ['id-bar'], | ||
solderMask: ['id-bar'], | ||
silkScreen: ['id-bar'], | ||
solderPaste: ['id-bar'], | ||
} | ||
|
||
const outlineRender: OutlineRender = { | ||
svgFragment: '<g id="outline"/>', | ||
viewBox: [5, 6, 7, 8], | ||
} | ||
|
||
td.when(renderer.renderFragment(plotTreeFoo)).thenReturn(svgTreeFoo) | ||
td.when(renderer.renderFragment(plotTreeBar)).thenReturn(svgTreeBar) | ||
td.when(svgStringifier.stringifySvg(svgTreeFoo)).thenReturn('<g id="foo"/>') | ||
td.when(svgStringifier.stringifySvg(svgTreeBar)).thenReturn('<g id="bar"/>') | ||
td.when(layerSorter.getMechanicalLayers(plotResult.layers)).thenReturn( | ||
mechanicalLayers | ||
) | ||
td.when(layerSorter.getSideLayers('top', plotResult.layers)).thenReturn( | ||
topLayers | ||
) | ||
td.when(layerSorter.getSideLayers('bottom', plotResult.layers)).thenReturn( | ||
bottomLayers | ||
) | ||
td.when( | ||
outlineRenderer.renderOutline(plotTreeBar, [1, 2, 3, 4], 0.02) | ||
).thenReturn(outlineRender) | ||
|
||
const result = subject.renderFragments(plotResult) | ||
|
||
expect(result).to.eql({ | ||
layers: plotResult.layers, | ||
topLayers, | ||
bottomLayers, | ||
mechanicalLayers, | ||
outlineRender, | ||
svgFragmentsById: { | ||
'id-foo': '<g id="foo"/>', | ||
'id-bar': '<g id="bar"/>', | ||
}, | ||
}) | ||
}) | ||
}) |
Oops, something went wrong.