├── boardui ├── packages │ ├── .gitkeep │ ├── boardui-demo │ │ ├── src │ │ │ ├── assets │ │ │ │ ├── .gitkeep │ │ │ │ ├── sax-wasm.wasm │ │ │ │ ├── normschrift.woff2 │ │ │ │ ├── logo-boardui-varianta2.svg │ │ │ │ └── logo-boardui.svg │ │ │ ├── environment │ │ │ │ ├── index.ts │ │ │ │ ├── environment.ts │ │ │ │ └── environment.production.ts │ │ │ ├── test-setup.ts │ │ │ ├── favicon.ico │ │ │ ├── main.ts │ │ │ ├── app │ │ │ │ ├── error-dialog.component.ts │ │ │ │ ├── element-dialog.component.ts │ │ │ │ ├── app.module.ts │ │ │ │ ├── app.component.spec.ts │ │ │ │ ├── layers-dialog.component.ts │ │ │ │ └── app.component.scss │ │ │ ├── styles.scss │ │ │ └── index.html │ │ ├── tsconfig.editor.json │ │ ├── tsconfig.app.json │ │ ├── tsconfig.spec.json │ │ ├── jest.config.ts │ │ ├── tsconfig.json │ │ ├── eslint.config.mjs │ │ └── project.json │ ├── boardui-renderer │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── element-id-provider.ts │ │ │ │ ├── bounds.ts │ │ │ │ ├── element-type.ts │ │ │ │ ├── renderer.ts │ │ │ │ ├── extensions │ │ │ │ ├── color.extensions.ts │ │ │ │ ├── line-desc.extensions.ts │ │ │ │ ├── xform.extensions.ts │ │ │ │ ├── fill-desc.extensions.ts │ │ │ │ └── polygon.extensions.ts │ │ │ │ ├── color.helper.ts │ │ │ │ ├── index.ts │ │ │ │ ├── renderers │ │ │ │ ├── index.ts │ │ │ │ ├── profile-contour-renderer.ts │ │ │ │ ├── polyline-renderer.ts │ │ │ │ ├── hole-renderer.ts │ │ │ │ ├── line-renderer.ts │ │ │ │ ├── contour-renderer.ts │ │ │ │ ├── pin-renderer.ts │ │ │ │ ├── renderer-base.ts │ │ │ │ ├── pad-renderer.ts │ │ │ │ ├── circle-renderer.ts │ │ │ │ ├── rect-center-renderer.ts │ │ │ │ ├── oval-renderer.ts │ │ │ │ └── component-renderer.ts │ │ │ │ ├── render-context.ts │ │ │ │ ├── element-map.ts │ │ │ │ ├── layers │ │ │ │ ├── layer-base.ts │ │ │ │ ├── component-layer.ts │ │ │ │ ├── profile-layer.ts │ │ │ │ ├── conductor-layer.ts │ │ │ │ ├── drill-layer.ts │ │ │ │ └── silkscreen-layer.ts │ │ │ │ ├── reusables-provider.ts │ │ │ │ ├── renderer-provider.ts │ │ │ │ ├── reusables-repository.ts │ │ │ │ └── svg-pcb-renderer.ts │ │ ├── package.json │ │ ├── test │ │ │ ├── element-id-provider.mock.ts │ │ │ ├── render-properties.mock.ts │ │ │ ├── polygonUtils.ts │ │ │ ├── reusables-provider.mock.ts │ │ │ ├── line-renderer.test.ts │ │ │ ├── component-renderer.test.ts │ │ │ ├── circle-renderer.test.ts │ │ │ ├── contour-renderer.test.ts │ │ │ └── oval-renderer.test.ts │ │ ├── README.md │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ ├── jest.config.ts │ │ ├── tsconfig.json │ │ ├── eslint.config.mjs │ │ └── project.json │ ├── boardui-parser │ │ ├── src │ │ │ ├── index.ts │ │ │ └── lib │ │ │ │ ├── ipc │ │ │ │ ├── hole-type.ts │ │ │ │ ├── polarity.ts │ │ │ │ ├── line-end.ts │ │ │ │ ├── bom-ref.ts │ │ │ │ ├── color-ref.ts │ │ │ │ ├── layer-ref.ts │ │ │ │ ├── step-ref.ts │ │ │ │ ├── fill-desc-ref.ts │ │ │ │ ├── line-desc-ref.ts │ │ │ │ ├── function-mode.ts │ │ │ │ ├── side.ts │ │ │ │ ├── fill-property.ts │ │ │ │ ├── location.ts │ │ │ │ ├── plating-status.ts │ │ │ │ ├── poly-begin.ts │ │ │ │ ├── standard-primitive-ref.ts │ │ │ │ ├── span.ts │ │ │ │ ├── color.ts │ │ │ │ ├── dictionary-entry.ts │ │ │ │ ├── poly-step-segment.ts │ │ │ │ ├── bom-category.ts │ │ │ │ ├── color-group.ts │ │ │ │ ├── ecad.ts │ │ │ │ ├── layer-feature.ts │ │ │ │ ├── line-property.ts │ │ │ │ ├── pin-ref.ts │ │ │ │ ├── contour.ts │ │ │ │ ├── dictionary.ts │ │ │ │ ├── fill-desc-group.ts │ │ │ │ ├── line-desc-group.ts │ │ │ │ ├── cad-data.ts │ │ │ │ ├── ref-des.ts │ │ │ │ ├── features.ts │ │ │ │ ├── marking-usage.ts │ │ │ │ ├── nonstandard-attribute.ts │ │ │ │ ├── textual.ts │ │ │ │ ├── assembly-drawing.ts │ │ │ │ ├── enumerated.ts │ │ │ │ ├── outline.ts │ │ │ │ ├── bom.ts │ │ │ │ ├── mount.ts │ │ │ │ ├── poly-step-curve.ts │ │ │ │ ├── marking.ts │ │ │ │ ├── bom-header.ts │ │ │ │ ├── standard-primitive-group.ts │ │ │ │ ├── line-desc.ts │ │ │ │ ├── line.ts │ │ │ │ ├── standard-primitive.ts │ │ │ │ ├── ipc2581.ts │ │ │ │ ├── pin-one-orientation.ts │ │ │ │ ├── x-form.ts │ │ │ │ ├── pin.ts │ │ │ │ ├── circle.ts │ │ │ │ ├── measured.ts │ │ │ │ ├── oval.ts │ │ │ │ ├── rect-center.ts │ │ │ │ ├── step.ts │ │ │ │ ├── fill-desc.ts │ │ │ │ ├── bom-item.ts │ │ │ │ ├── pad.ts │ │ │ │ ├── ranged.ts │ │ │ │ ├── hole.ts │ │ │ │ ├── layer.ts │ │ │ │ ├── set.ts │ │ │ │ ├── characteristics.ts │ │ │ │ ├── polyline.ts │ │ │ │ ├── polygon.ts │ │ │ │ ├── silk-screen.ts │ │ │ │ ├── package.ts │ │ │ │ ├── component.ts │ │ │ │ ├── content.ts │ │ │ │ ├── layer-function.ts │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ ├── xml-to-js-mapping.ts │ │ │ │ ├── ipc2581-parser.ts │ │ │ │ ├── sax-wasm-helper.ts │ │ │ │ ├── xml-to-js-parser.ts │ │ │ │ └── default-ipc-mappings.ts │ │ ├── package.json │ │ ├── README.md │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ ├── jest.config.ts │ │ ├── tsconfig.json │ │ ├── eslint.config.mjs │ │ ├── project.json │ │ └── test │ │ │ └── xml-to-js-parser.test.ts │ ├── boardui-angular │ │ ├── src │ │ │ ├── test-setup.ts │ │ │ ├── lib │ │ │ │ ├── events │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── element-event.ts │ │ │ │ │ └── render-done-event.ts │ │ │ │ ├── template │ │ │ │ │ ├── template.ts │ │ │ │ │ ├── template-collection.ts │ │ │ │ │ └── template.directive.ts │ │ │ │ ├── viewer.component │ │ │ │ │ ├── viewer.component.html │ │ │ │ │ ├── viewer.component.scss │ │ │ │ │ └── viewer.component.spec.ts │ │ │ │ └── boardui.module.ts │ │ │ └── index.ts │ │ ├── README.md │ │ ├── ng-package.json │ │ ├── tsconfig.lib.prod.json │ │ ├── package.json │ │ ├── tsconfig.spec.json │ │ ├── tsconfig.lib.json │ │ ├── jest.config.ts │ │ ├── tsconfig.json │ │ ├── eslint.config.mjs │ │ └── project.json │ ├── boardui-core │ │ ├── src │ │ │ ├── lib │ │ │ │ ├── side.ts │ │ │ │ ├── color.ts │ │ │ │ ├── buffer-helper.ts │ │ │ │ ├── layer-type-render-properties.ts │ │ │ │ ├── render-context.ts │ │ │ │ ├── component-render-properties.ts │ │ │ │ ├── layer.ts │ │ │ │ ├── render-properties.ts │ │ │ │ ├── layer-type.ts │ │ │ │ └── default-render-properties.ts │ │ │ └── index.ts │ │ ├── package.json │ │ ├── README.md │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ ├── jest.config.ts │ │ ├── eslint.config.mjs │ │ ├── tsconfig.json │ │ └── project.json │ └── boardui-demo-e2e │ │ ├── src │ │ ├── fixtures │ │ │ └── example.json │ │ ├── support │ │ │ ├── app.po.ts │ │ │ ├── e2e.ts │ │ │ └── commands.ts │ │ └── e2e │ │ │ └── app.cy.ts │ │ ├── cypress.config.ts │ │ ├── tsconfig.json │ │ ├── eslint.config.mjs │ │ └── project.json ├── .prettierrc ├── .nx │ ├── workspace-data │ │ ├── lockfile.hash │ │ ├── d │ │ │ └── server-process.json │ │ ├── nx_files.nxt │ │ └── fc7aef9e-3b60-411e-9cb9-de629c904c0b.db │ └── cache │ │ ├── terminalOutputs │ │ ├── 8450067152267059188 │ │ ├── 399739307338957698 │ │ ├── 14170607879427057801 │ │ ├── 1026447515684006065 │ │ └── 4436582216557151009 │ │ └── run.json ├── .prettierignore ├── jest.config.ts ├── jest.preset.js ├── .editorconfig ├── tools │ └── tsconfig.tools.json ├── .vscode │ └── launch.json ├── README.md ├── .gitignore ├── tsconfig.base.json ├── eslint.config.mjs ├── nx.json └── package.json ├── boardui-screenshot-1.webp ├── .vscode └── extensions.json ├── .github └── workflows │ └── ci.yaml ├── LICENSE └── logo.svg /boardui/packages/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /boardui/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true 3 | } 4 | -------------------------------------------------------------------------------- /boardui/.nx/workspace-data/lockfile.hash: -------------------------------------------------------------------------------- 1 | 17325250618785105264 -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib'; -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib'; 2 | -------------------------------------------------------------------------------- /boardui/.nx/workspace-data/d/server-process.json: -------------------------------------------------------------------------------- 1 | { 2 | "processId": 29636 3 | } 4 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/environment/index.ts: -------------------------------------------------------------------------------- 1 | export * from './environment'; 2 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/src/test-setup.ts: -------------------------------------------------------------------------------- 1 | import 'jest-preset-angular/setup-jest'; 2 | -------------------------------------------------------------------------------- /boardui-screenshot-1.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midub/boardui/HEAD/boardui-screenshot-1.webp -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/hole-type.ts: -------------------------------------------------------------------------------- 1 | export type HoleType = 'CIRCLE' | 'SQUARE'; 2 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/polarity.ts: -------------------------------------------------------------------------------- 1 | export type Polarity = 'POSITIVE' | 'NEGATIVE'; 2 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/src/lib/side.ts: -------------------------------------------------------------------------------- 1 | /** Side of PCB. */ 2 | export type Side = 'TOP' | 'BOTTOM'; 3 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/line-end.ts: -------------------------------------------------------------------------------- 1 | export type LineEnd = 'ROUND' | 'SQUARE' | 'NONE'; 2 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/bom-ref.ts: -------------------------------------------------------------------------------- 1 | export class BomRef { 2 | name: string = null!; 3 | } 4 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/color-ref.ts: -------------------------------------------------------------------------------- 1 | export class ColorRef { 2 | id: string = null!; 3 | } 4 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/layer-ref.ts: -------------------------------------------------------------------------------- 1 | export class LayerRef { 2 | name: string = null!; 3 | } 4 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/step-ref.ts: -------------------------------------------------------------------------------- 1 | export class StepRef { 2 | name: string = null!; 3 | } 4 | -------------------------------------------------------------------------------- /boardui/.prettierignore: -------------------------------------------------------------------------------- 1 | # Add files here to ignore them from prettier formatting 2 | /dist 3 | /coverage 4 | .angular 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/fill-desc-ref.ts: -------------------------------------------------------------------------------- 1 | export class FillDescRef { 2 | id: string = null!; 3 | } 4 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/line-desc-ref.ts: -------------------------------------------------------------------------------- 1 | export class LineDescRef { 2 | id: string = null!; 3 | } 4 | -------------------------------------------------------------------------------- /boardui/.nx/workspace-data/nx_files.nxt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midub/boardui/HEAD/boardui/.nx/workspace-data/nx_files.nxt -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/environment/environment.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: false, 3 | }; 4 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/function-mode.ts: -------------------------------------------------------------------------------- 1 | export class FunctionMode { 2 | mode: string = null!; 3 | } 4 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/src/lib/color.ts: -------------------------------------------------------------------------------- 1 | export interface Color { 2 | r: number; 3 | g: number; 4 | b: number; 5 | } 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/side.ts: -------------------------------------------------------------------------------- 1 | export type Side = 'TOP' | 'BOTTOM' | 'BOTH' | 'INTERNAL' | 'ALL' | 'NONE'; 2 | -------------------------------------------------------------------------------- /boardui/jest.config.ts: -------------------------------------------------------------------------------- 1 | import { getJestProjects } from '@nx/jest'; 2 | 3 | export default { 4 | projects: getJestProjects(), 5 | }; 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/src/lib/events/index.ts: -------------------------------------------------------------------------------- 1 | export * from './element-event'; 2 | export * from './render-done-event'; 3 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boardui-core", 3 | "version": "1.0.0", 4 | "type": "commonjs" 5 | } 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/environment/environment.production.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true, 3 | }; 4 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/fill-property.ts: -------------------------------------------------------------------------------- 1 | export type FillProperty = 'HOLLOW' | 'HATCH' | 'MESH' | 'FILL' | 'VOID'; 2 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midub/boardui/HEAD/boardui/packages/boardui-demo/src/favicon.ico -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/location.ts: -------------------------------------------------------------------------------- 1 | export class Location { 2 | x: number = null!; 3 | y: number = null!; 4 | } 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/plating-status.ts: -------------------------------------------------------------------------------- 1 | export type platingStatus = 'PLATED' | 'NONPLATED' | 'VIA' | 'VIA_CAPPED'; 2 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/poly-begin.ts: -------------------------------------------------------------------------------- 1 | export class PolyBegin { 2 | x: number = null!; 3 | y: number = null!; 4 | } 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/standard-primitive-ref.ts: -------------------------------------------------------------------------------- 1 | export class StandardPrimitiveRef { 2 | id: string = null!; 3 | } 4 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boardui-renderer", 3 | "version": "1.0.0", 4 | "type": "commonjs" 5 | } 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/span.ts: -------------------------------------------------------------------------------- 1 | export class Span { 2 | fromLayer: string = null!; 3 | toLayer: string = null!; 4 | } 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/element-id-provider.ts: -------------------------------------------------------------------------------- 1 | export interface ElementIdProvider { 2 | getElementId(element: any): number; 3 | } 4 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo-e2e/src/fixtures/example.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Using fixtures to represent data", 3 | "email": "hello@cypress.io" 4 | } 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/assets/sax-wasm.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midub/boardui/HEAD/boardui/packages/boardui-demo/src/assets/sax-wasm.wasm -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/color.ts: -------------------------------------------------------------------------------- 1 | export class Color { 2 | r: number = null!; 3 | g: number = null!; 4 | b: number = null!; 5 | } 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/src/lib/events/element-event.ts: -------------------------------------------------------------------------------- 1 | export interface ElementEvent { 2 | originalEvent: MouseEvent; 3 | element: any; 4 | } 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/dictionary-entry.ts: -------------------------------------------------------------------------------- 1 | export class DictionaryEntry { 2 | id: string = null!; 3 | content: T = null!; 4 | } 5 | -------------------------------------------------------------------------------- /boardui/.nx/cache/terminalOutputs/8450067152267059188: -------------------------------------------------------------------------------- 1 | 2 |  NX  Required property 'buildTarget' is missing 3 | 4 | 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/src/lib/template/template.ts: -------------------------------------------------------------------------------- 1 | export interface Template { 2 | name: string; 3 | render(target: any, data: any): any[]; 4 | } 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/assets/normschrift.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midub/boardui/HEAD/boardui/packages/boardui-demo/src/assets/normschrift.woff2 -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/poly-step-segment.ts: -------------------------------------------------------------------------------- 1 | import { PolyBegin } from './poly-begin'; 2 | 3 | export class PolyStepSegment extends PolyBegin {} 4 | -------------------------------------------------------------------------------- /boardui/.nx/cache/terminalOutputs/399739307338957698: -------------------------------------------------------------------------------- 1 | Compiling TypeScript files for project "boardui-core"... 2 | Done compiling TypeScript files for project "boardui-core". 3 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/bounds.ts: -------------------------------------------------------------------------------- 1 | export interface Bounds { 2 | offsetX: number; 3 | offsetY: number; 4 | width: number; 5 | height: number; 6 | } 7 | -------------------------------------------------------------------------------- /boardui/.nx/cache/terminalOutputs/14170607879427057801: -------------------------------------------------------------------------------- 1 | Compiling TypeScript files for project "boardui-parser"... 2 | Done compiling TypeScript files for project "boardui-parser". 3 | -------------------------------------------------------------------------------- /boardui/.nx/workspace-data/fc7aef9e-3b60-411e-9cb9-de629c904c0b.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/midub/boardui/HEAD/boardui/.nx/workspace-data/fc7aef9e-3b60-411e-9cb9-de629c904c0b.db -------------------------------------------------------------------------------- /boardui/jest.preset.js: -------------------------------------------------------------------------------- 1 | const nxPreset = require('@nx/jest/preset').default; 2 | 3 | module.exports = { 4 | ...nxPreset, 5 | testEnvironment: 'jest-fixed-jsdom' 6 | }; 7 | -------------------------------------------------------------------------------- /boardui/.nx/cache/terminalOutputs/1026447515684006065: -------------------------------------------------------------------------------- 1 | Compiling TypeScript files for project "boardui-renderer"... 2 | Done compiling TypeScript files for project "boardui-renderer". 3 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/bom-category.ts: -------------------------------------------------------------------------------- 1 | export type BomCategory = 2 | | 'ELECTRICAL' 3 | | 'PROGRAMMABLE' 4 | | 'MECHANICAL' 5 | | 'MATERIAL' 6 | | 'DOCUMENT'; 7 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/color-group.ts: -------------------------------------------------------------------------------- 1 | import { Color } from './color'; 2 | import { ColorRef } from './color-ref'; 3 | 4 | export type ColorGroup = ColorRef | Color; 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/ecad.ts: -------------------------------------------------------------------------------- 1 | import { CadData } from './cad-data'; 2 | 3 | export class Ecad { 4 | name: string = null!; 5 | 6 | cadData: CadData = null!; 7 | } 8 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/layer-feature.ts: -------------------------------------------------------------------------------- 1 | import { Set } from './set'; 2 | 3 | export class LayerFeature { 4 | layerRef: string = null!; 5 | sets: Set[] = []; 6 | } 7 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/line-property.ts: -------------------------------------------------------------------------------- 1 | export type LineProperty = 2 | | 'SOLID' 3 | | 'DOTTED' 4 | | 'DASHED' 5 | | 'CENTER' 6 | | 'PHANTOM' 7 | | 'ERASE'; 8 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/pin-ref.ts: -------------------------------------------------------------------------------- 1 | export class PinRef { 2 | componentRef: string | null = null; 3 | pin: string = null!; 4 | title: string | null = null; 5 | } 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/contour.ts: -------------------------------------------------------------------------------- 1 | import { Polygon } from './polygon'; 2 | 3 | export class Contour { 4 | polygon: Polygon = null!; 5 | cutouts: Polygon[] = []; 6 | } 7 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/tsconfig.editor.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["src/**/*.ts"], 4 | "compilerOptions": { 5 | "types": ["jest", "node"] 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boardui-parser", 3 | "version": "1.0.0", 4 | "type": "commonjs", 5 | "dependencies": { 6 | "sax-wasm": "^2.0.0" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/dictionary.ts: -------------------------------------------------------------------------------- 1 | import { DictionaryEntry } from './dictionary-entry'; 2 | 3 | export class Dictionary { 4 | entries: DictionaryEntry[] = []; 5 | } 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/element-type.ts: -------------------------------------------------------------------------------- 1 | export enum ElementType { 2 | COMPONENT = 'COMPONENT', 3 | PIN = 'PIN', 4 | PAD = 'PAD', 5 | LAYER = 'LAYER', 6 | STEP = 'STEP', 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "nrwl.angular-console", 4 | "esbenp.prettier-vscode", 5 | "dbaeumer.vscode-eslint", 6 | "firsttris.vscode-jest-runner" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/fill-desc-group.ts: -------------------------------------------------------------------------------- 1 | import { FillDesc } from './fill-desc'; 2 | import { FillDescRef } from './fill-desc-ref'; 3 | 4 | export type FillDescGroup = FillDesc | FillDescRef; 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/line-desc-group.ts: -------------------------------------------------------------------------------- 1 | import { LineDesc } from './line-desc'; 2 | import { LineDescRef } from './line-desc-ref'; 3 | 4 | export type LineDescGroup = LineDesc | LineDescRef; 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/src/lib/viewer.component/viewer.component.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/cad-data.ts: -------------------------------------------------------------------------------- 1 | import { Layer } from './layer'; 2 | import { Step } from './step'; 3 | 4 | export class CadData { 5 | layers: Layer[] = []; 6 | steps: Step[] = []; 7 | } 8 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderer.ts: -------------------------------------------------------------------------------- 1 | import { RenderContext } from './render-context'; 2 | 3 | export interface Renderer { 4 | render(part: T, target: Node, context: RenderContext): void; 5 | } 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/README.md: -------------------------------------------------------------------------------- 1 | # boardui-angular 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Running unit tests 6 | 7 | Run `nx test boardui-angular` to execute the unit tests. 8 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/ref-des.ts: -------------------------------------------------------------------------------- 1 | export class RefDes { 2 | name: string = null!; 3 | packageRef: string | null = null; 4 | populate: string | null = null; 5 | layerRef: string | null = null; 6 | } 7 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/boardui.module'; 2 | export * from './lib/viewer.component/viewer.component'; 3 | export * from './lib/template/template.directive'; 4 | export * from './lib/events'; 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './ipc'; 2 | export * from './default-ipc-mappings'; 3 | export * from './ipc2581-parser'; 4 | export * from './xml-to-js-parser'; 5 | export * from './sax-wasm-helper'; 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/features.ts: -------------------------------------------------------------------------------- 1 | import { StandardPrimitive } from './standard-primitive'; 2 | 3 | export class Features { 4 | features: StandardPrimitive[] = []; 5 | location: Location | null = null; 6 | } 7 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/marking-usage.ts: -------------------------------------------------------------------------------- 1 | export type MarkingUsage = 2 | | 'REFDES' 3 | | 'PARTNAME' 4 | | 'TARGET' 5 | | 'POLARITY_MARKING' 6 | | 'ATTRIBUTE_GRAPHICS' 7 | | 'PIN_ONE' 8 | | 'NONE'; 9 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/nonstandard-attribute.ts: -------------------------------------------------------------------------------- 1 | export class NonstandardAttribute { 2 | name: string = null!; 3 | type: 'BOOLEAN' | 'DOUBLE' | 'INTEGER' | 'STRING' = null!; 4 | value: string = null!; 5 | } 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/extensions/color.extensions.ts: -------------------------------------------------------------------------------- 1 | import { Color } from 'boardui-parser'; 2 | 3 | export function getSvgColor(color: Color): string { 4 | return `rgb(${color.r},${color.g},${color.b})`; 5 | } 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo-e2e/src/support/app.po.ts: -------------------------------------------------------------------------------- 1 | export const getPCBSvg = () => cy.get('bui-viewer svg'); 2 | export const getBOMTable = () => cy.get('table'); 3 | export const getFileChooser = () => cy.get('input[type="file"]'); 4 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/xml-to-js-mapping.ts: -------------------------------------------------------------------------------- 1 | /** Mapping of tag from XML to JS object definition. */ 2 | export type XMLtoJSMapping = [ 3 | tagName: string, 4 | prototype: any, 5 | possibleProperties: string[] 6 | ]; 7 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/test/element-id-provider.mock.ts: -------------------------------------------------------------------------------- 1 | import { ElementIdProvider } from '../src/lib'; 2 | 3 | export const ElementIdProviderMock: ElementIdProvider = { 4 | getElementId: (element: any): number => 1, 5 | }; 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/packages/boardui-angular", 4 | "lib": { 5 | "entryFile": "src/index.ts" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo-e2e/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'cypress'; 2 | import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; 3 | 4 | export default defineConfig({ 5 | e2e: nxE2EPreset(__dirname), 6 | }); 7 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/textual.ts: -------------------------------------------------------------------------------- 1 | export class Textual { 2 | definitionSource: string | null = null; 3 | textualCharacteristicName: string | null = null; 4 | textualCharacteristicValue: string | null = null; 5 | } 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/color.helper.ts: -------------------------------------------------------------------------------- 1 | import { Color } from 'boardui-core'; 2 | 3 | export class ColorHelper { 4 | static getSVGColor(color: Color) { 5 | return `rgb(${color.r},${color.g},${color.b})`; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/assembly-drawing.ts: -------------------------------------------------------------------------------- 1 | import { Marking } from './marking'; 2 | import { Outline } from './outline'; 3 | 4 | export class AssemblyDrawing { 5 | outline: Outline = null!; 6 | marking: Marking = null!; 7 | } 8 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/enumerated.ts: -------------------------------------------------------------------------------- 1 | export class Enumerated { 2 | definitionSource: string | null = null; 3 | enumeratedCharacteristicName: string | null = null; 4 | enumeratedCharacteristicValue: string | null = null; 5 | } 6 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/outline.ts: -------------------------------------------------------------------------------- 1 | import { LineDescGroup } from './line-desc-group'; 2 | import { Polygon } from './polygon'; 3 | 4 | export class Outline { 5 | polygon: Polygon = null!; 6 | lineDesc: LineDescGroup = null!; 7 | } 8 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "compilerOptions": { 4 | "declarationMap": false 5 | }, 6 | "angularCompilerOptions": { 7 | "compilationMode": "partial" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/bom.ts: -------------------------------------------------------------------------------- 1 | import { BomHeader } from './bom-header'; 2 | import { BomItem } from './bom-item'; 3 | 4 | export class Bom { 5 | name: string = null!; 6 | bomHeader: BomHeader = null!; 7 | bomItems: BomItem[] = []; 8 | } 9 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/mount.ts: -------------------------------------------------------------------------------- 1 | export type Mount = 2 | | 'SMT' 3 | | 'THMT' 4 | | 'EMBEDDED' 5 | | 'PRESSFIT' 6 | | 'WIRE_BONDED' 7 | | 'GLUED' 8 | | 'CLAMPED' 9 | | 'SOCKETED' 10 | | 'FORMED' 11 | | 'OTHER'; 12 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/poly-step-curve.ts: -------------------------------------------------------------------------------- 1 | import { PolyBegin } from './poly-begin'; 2 | 3 | export class PolyStepCurve extends PolyBegin { 4 | centerX: number = null!; 5 | centerY: number = null!; 6 | clockwise: string = null!; 7 | } 8 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/src/lib/buffer-helper.ts: -------------------------------------------------------------------------------- 1 | export const arrayBufferToStream = (ab: ArrayBuffer) => new ReadableStream({ 2 | start(controller) { 3 | controller.enqueue(new Uint8Array(ab)); 4 | controller.close(); 5 | }, 6 | }); -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/marking.ts: -------------------------------------------------------------------------------- 1 | import { StandardPrimitive } from './standard-primitive'; 2 | 3 | export class Marking { 4 | location: Location | null = null; 5 | markingUsage: string = null!; 6 | features: StandardPrimitive[] = []; 7 | } 8 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/bom-header.ts: -------------------------------------------------------------------------------- 1 | import { StepRef } from './step-ref'; 2 | 3 | export class BomHeader { 4 | assembly: string = null!; 5 | revision: string = null!; 6 | affecting: string | null = null; 7 | stepRefs: StepRef[] = []; 8 | } 9 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/src/lib/layer-type-render-properties.ts: -------------------------------------------------------------------------------- 1 | import { Color } from './color'; 2 | import { LayerType } from './layer-type'; 3 | 4 | export interface LayerTypeRenderProperties { 5 | layerType: LayerType; 6 | color?: Color; 7 | invisible?: boolean; 8 | } 9 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/standard-primitive-group.ts: -------------------------------------------------------------------------------- 1 | import { StandardPrimitive } from './standard-primitive'; 2 | import { StandardPrimitiveRef } from './standard-primitive-ref'; 3 | 4 | export type StandardPrimitiveGroup = StandardPrimitive | StandardPrimitiveRef; 5 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/line-desc.ts: -------------------------------------------------------------------------------- 1 | import { LineEnd } from './line-end'; 2 | import { LineProperty } from './line-property'; 3 | 4 | export class LineDesc { 5 | lineEnd: LineEnd = null!; 6 | lineWidth: string = null!; 7 | lineProperty: LineProperty | null = null!; 8 | } 9 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/src/lib/render-context.ts: -------------------------------------------------------------------------------- 1 | import { RenderProperties } from './render-properties'; 2 | import { Side } from './side'; 3 | 4 | export interface BoardViewContext { 5 | pcb: any; 6 | step: string; 7 | side: Side; 8 | renderProperties: RenderProperties; 9 | } 10 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/line.ts: -------------------------------------------------------------------------------- 1 | import { LineDescGroup } from './line-desc-group'; 2 | 3 | export class Line { 4 | startX: number = null!; 5 | startY: number = null!; 6 | endX: number = null!; 7 | endY: number = null!; 8 | lineDesc: LineDescGroup = null!; 9 | } 10 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/standard-primitive.ts: -------------------------------------------------------------------------------- 1 | import { Circle } from './circle'; 2 | import { Contour } from './contour'; 3 | import { Polyline } from './polyline'; 4 | import { RectCenter } from './rect-center'; 5 | 6 | export type StandardPrimitive = Contour | Polyline | Circle | RectCenter; 7 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boardui-angular", 3 | "version": "19.0.0", 4 | "peerDependencies": { 5 | "@angular/common": "^19.0.0", 6 | "@angular/core": "^19.0.0" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.3.0" 10 | }, 11 | "sideEffects": false 12 | } 13 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/ipc2581.ts: -------------------------------------------------------------------------------- 1 | import { Bom } from './bom'; 2 | import { Content } from './content'; 3 | import { Ecad } from './ecad'; 4 | 5 | export class IPC2581 { 6 | revision: string = null!; 7 | content: Content = null!; 8 | ecad: Ecad = null!; 9 | bom: Bom = null!; 10 | } 11 | -------------------------------------------------------------------------------- /boardui/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/README.md: -------------------------------------------------------------------------------- 1 | # boardui-core 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build boardui-core` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test boardui-core` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/src/lib/component-render-properties.ts: -------------------------------------------------------------------------------- 1 | import { Color } from './color'; 2 | 3 | export interface ComponentRenderProperties { 4 | selectors: ['refDes' | 'packageRef' | 'part' | 'mountType', string][]; 5 | 6 | fillColor?: Color; 7 | outlineColor?: Color; 8 | visibility?: boolean; 9 | } 10 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/pin-one-orientation.ts: -------------------------------------------------------------------------------- 1 | export type PinOneOrientation = 2 | | 'LOWER_LEFT' 3 | | 'LEFT' 4 | | 'LEFT_CENTER' 5 | | 'UPPER_LEFT' 6 | | 'UPPER_CENTER' 7 | | 'UPPER_RIGHT' 8 | | 'RIGHT' 9 | | 'RIGHT_CENTER' 10 | | 'LOWER_RIGHT' 11 | | 'LOWER_CENTER' 12 | | 'OTHER'; 13 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "types": [] 6 | }, 7 | "files": ["src/main.ts"], 8 | "include": ["src/**/*.d.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.test.ts", "src/**/*.spec.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/README.md: -------------------------------------------------------------------------------- 1 | # boardui-parser 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build boardui-parser` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test boardui-parser` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /boardui/tools/tsconfig.tools.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "../dist/out-tsc/tools", 5 | "rootDir": ".", 6 | "module": "commonjs", 7 | "target": "es5", 8 | "types": ["node"], 9 | "importHelpers": false 10 | }, 11 | "include": ["**/*.ts"] 12 | } 13 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/README.md: -------------------------------------------------------------------------------- 1 | # boardui-renderer 2 | 3 | This library was generated with [Nx](https://nx.dev). 4 | 5 | ## Building 6 | 7 | Run `nx build boardui-renderer` to build the library. 8 | 9 | ## Running unit tests 10 | 11 | Run `nx test boardui-renderer` to execute the unit tests via [Jest](https://jestjs.io). 12 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "types": ["node"] 7 | }, 8 | "include": ["src/**/*.ts"], 9 | "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/x-form.ts: -------------------------------------------------------------------------------- 1 | export class XForm { 2 | xOffset: number | null = null; 3 | yOffset: number | null = null; 4 | rotation: number | null = null; 5 | mirror: boolean | null = null; 6 | scale: string | null = null; // TODO: Handle correctly as described in IPC-2581. 7 | faceUp: boolean | null = null; 8 | } 9 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './bounds'; 2 | export * from './element-id-provider'; 3 | export * from './element-map'; 4 | export * from './element-type'; 5 | export * from './render-context'; 6 | export * from './svg-pcb-renderer'; 7 | export * from './reusables-repository'; 8 | export * from './reusables-provider'; 9 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/src/lib/layer.ts: -------------------------------------------------------------------------------- 1 | import { LayerType } from './layer-type'; 2 | import { Side } from './side'; 3 | 4 | /** PCB layer. */ 5 | export interface Layer { 6 | /** Layer name. */ 7 | name: string; 8 | 9 | /** Layer type. */ 10 | type: LayerType; 11 | 12 | /** Side on which is layer located. */ 13 | side: Side; 14 | } 15 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/pin.ts: -------------------------------------------------------------------------------- 1 | import { StandardPrimitiveRef } from './standard-primitive-ref'; 2 | import { Location } from './location'; 3 | import { XForm } from './x-form'; 4 | 5 | export class Pin { 6 | location: Location = null!; 7 | xform: XForm | null = null; 8 | standardPrimitiveRef: StandardPrimitiveRef = null!; 9 | } 10 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/test/render-properties.mock.ts: -------------------------------------------------------------------------------- 1 | import { RenderProperties } from 'boardui-core/src'; 2 | 3 | export const RenderPropertiesMock: RenderProperties = { 4 | padding: 1, 5 | dropShadow: { 6 | dx: 0, 7 | dy: 0, 8 | stdDeviation: 1, 9 | }, 10 | layerTypesRenderProperties: [], 11 | componentRenderProperties: [], 12 | }; 13 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'boardui-core', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 7 | }, 8 | moduleFileExtensions: ['ts', 'js', 'html'], 9 | coverageDirectory: '../../coverage/packages/boardui-core', 10 | }; 11 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/circle.ts: -------------------------------------------------------------------------------- 1 | import { FillDescGroup } from './fill-desc-group'; 2 | import { LineDescGroup } from './line-desc-group'; 3 | import { XForm } from './x-form'; 4 | 5 | export class Circle { 6 | diameter: number = null!; 7 | lineDesc: LineDescGroup | null = null; 8 | fillDesc: FillDescGroup | null = null; 9 | xform: XForm | null = null; 10 | } 11 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'boardui-parser', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 7 | }, 8 | moduleFileExtensions: ['ts', 'js', 'html'], 9 | coverageDirectory: '../../coverage/packages/boardui-parser', 10 | }; 11 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/measured.ts: -------------------------------------------------------------------------------- 1 | export class Measured { 2 | definitionSource: string | null = null; 3 | measuredCharacteristicName: string | null = null; 4 | measuredCharacteristicValue: string | null = null; 5 | engineeringUnitOfMeasure: string | null = null; 6 | engineeringNegativeTolerance: string | null = null; 7 | engineeringPositiveTolerance: string | null = null; 8 | } 9 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "types": ["jest", "node", "@testing-library/jest-dom"] 7 | }, 8 | "include": [ 9 | "jest.config.ts", 10 | "src/**/*.test.ts", 11 | "src/**/*.spec.ts", 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'boardui-renderer', 4 | preset: '../../jest.preset.js', 5 | transform: { 6 | '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], 7 | }, 8 | moduleFileExtensions: ['ts', 'js', 'html'], 9 | coverageDirectory: '../../coverage/packages/boardui-renderer', 10 | }; 11 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/src/lib/boardui.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { TemplateDirective } from './template/template.directive'; 3 | import { ViewerComponent } from './viewer.component/viewer.component'; 4 | 5 | @NgModule({ 6 | imports: [ViewerComponent, TemplateDirective], 7 | exports: [ViewerComponent, TemplateDirective], 8 | }) 9 | export class BoardUIModule {} 10 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/oval.ts: -------------------------------------------------------------------------------- 1 | import { FillDescGroup } from './fill-desc-group'; 2 | import { LineDescGroup } from './line-desc-group'; 3 | import { XForm } from './x-form'; 4 | 5 | export class Oval { 6 | width: number = null!; 7 | height: number = null!; 8 | lineDesc: LineDescGroup | null = null; 9 | fillDesc: FillDescGroup | null = null; 10 | xform: XForm | null = null; 11 | } 12 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/src/lib/events/render-done-event.ts: -------------------------------------------------------------------------------- 1 | import { RenderProperties, Side } from 'boardui-core'; 2 | import { IPC2581 } from 'boardui-parser'; 3 | import { ElementMap } from 'boardui-renderer'; 4 | 5 | export interface RenderDoneEvent { 6 | pcb: IPC2581; 7 | side: Side; 8 | step: string; 9 | elementMap: ElementMap; 10 | renderProperties: RenderProperties; 11 | error?: any; 12 | } 13 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/rect-center.ts: -------------------------------------------------------------------------------- 1 | import { FillDescGroup } from './fill-desc-group'; 2 | import { LineDescGroup } from './line-desc-group'; 3 | import { XForm } from './x-form'; 4 | 5 | export class RectCenter { 6 | width: number = null!; 7 | height: number = null!; 8 | lineDesc: LineDescGroup | null = null; 9 | fillDesc: FillDescGroup | null = null; 10 | xform: XForm | null = null; 11 | } 12 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "target": "es2016", 7 | "types": ["jest", "node"] 8 | }, 9 | "files": ["src/test-setup.ts"], 10 | "include": [ 11 | "jest.config.ts", 12 | "src/**/*.test.ts", 13 | "src/**/*.spec.ts", 14 | "src/**/*.d.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "module": "commonjs", 6 | "target": "es2016", 7 | "types": ["jest", "node"] 8 | }, 9 | "files": ["src/test-setup.ts"], 10 | "include": [ 11 | "jest.config.ts", 12 | "src/**/*.test.ts", 13 | "src/**/*.spec.ts", 14 | "src/**/*.d.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/step.ts: -------------------------------------------------------------------------------- 1 | import { Component } from './component'; 2 | import { Contour } from './contour'; 3 | import { LayerFeature } from './layer-feature'; 4 | import { Package } from './package'; 5 | 6 | export class Step { 7 | name: string = null!; 8 | profile: Contour | null = null; 9 | layerFeatures: LayerFeature[] = []; 10 | packages: Package[] = []; 11 | components: Component[] = []; 12 | } 13 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/index.ts: -------------------------------------------------------------------------------- 1 | export * from './circle-renderer'; 2 | export * from './component-renderer'; 3 | export * from './contour-renderer'; 4 | export * from './hole-renderer'; 5 | export * from './pin-renderer'; 6 | export * from './polyline-renderer'; 7 | export * from './profile-contour-renderer'; 8 | export * from './rect-center-renderer'; 9 | export * from './oval-renderer'; 10 | export * from './line-renderer'; -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/fill-desc.ts: -------------------------------------------------------------------------------- 1 | import { ColorGroup } from './color-group'; 2 | import { FillProperty } from './fill-property'; 3 | 4 | export class FillDesc { 5 | fillProperty: FillProperty = null!; 6 | lineWidth: number | null = null; 7 | pitch1: number | null = null; 8 | pitch2: number | null = null; 9 | angle1: number | null = null; 10 | angle2: number | null = null; 11 | color: ColorGroup | null = null; 12 | } 13 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../dist/out-tsc", 5 | "declaration": true, 6 | "declarationMap": true, 7 | "inlineSources": true, 8 | "types": [] 9 | }, 10 | "exclude": [ 11 | "src/**/*.spec.ts", 12 | "src/test-setup.ts", 13 | "jest.config.ts", 14 | "src/**/*.test.ts" 15 | ], 16 | "include": ["src/**/*.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/color'; 2 | export * from './lib/component-render-properties'; 3 | export * from './lib/layer'; 4 | export * from './lib/layer-type'; 5 | export * from './lib/layer-type-render-properties'; 6 | export * from './lib/render-context'; 7 | export * from './lib/render-properties'; 8 | export * from './lib/side'; 9 | export * from './lib/default-render-properties'; 10 | export * from './lib/buffer-helper'; -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/bom-item.ts: -------------------------------------------------------------------------------- 1 | import { BomCategory } from './bom-category'; 2 | import { Characteristics } from './characteristics'; 3 | import { RefDes } from './ref-des'; 4 | 5 | export class BomItem { 6 | OEMDesignNumberRef: string = null!; 7 | quantity: string = null!; 8 | pinCount: string | null = null; 9 | category: BomCategory = null!; 10 | refDes: RefDes[] = []; 11 | characteristics: Characteristics = null!; 12 | } 13 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/pad.ts: -------------------------------------------------------------------------------- 1 | import { XForm } from './x-form'; 2 | import { PinRef } from './pin-ref'; 3 | import { StandardPrimitiveGroup } from './standard-primitive-group'; 4 | import { Location } from './location'; 5 | 6 | export class Pad { 7 | padstackDefRef: string | null = null; 8 | xform: XForm | null = null; 9 | location: Location = null!; 10 | feature: StandardPrimitiveGroup = null!; 11 | pinRefs: PinRef[] = []; 12 | } 13 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/ranged.ts: -------------------------------------------------------------------------------- 1 | export class Ranged { 2 | definitionSource: string | null = null; 3 | rangedCharacteristicName: string | null = null; 4 | rangedCharacteristicLowerValue: string | null = null; 5 | rangedCharacteristicUpperValue: string | null = null; 6 | engineeringUnitOfMeasure: string | null = null; 7 | engineeringNegativeTolerance: string | null = null; 8 | engineeringPositiveTolerance: string | null = null; 9 | } 10 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/hole.ts: -------------------------------------------------------------------------------- 1 | import { HoleType } from './hole-type'; 2 | import { platingStatus } from './plating-status'; 3 | 4 | export class Hole { 5 | name: string = null!; 6 | type: HoleType = 'CIRCLE'; 7 | diameter: number = null!; 8 | platingStatus: platingStatus = null!; 9 | plusTol: number = null!; 10 | minusTol: number = null!; 11 | x: number = null!; 12 | y: number = null!; 13 | // TODO: Add specification reference. 14 | } 15 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/layer.ts: -------------------------------------------------------------------------------- 1 | import { Contour } from './contour'; 2 | import { LayerFunction } from './layer-function'; 3 | import { Polarity } from './polarity'; 4 | import { Side } from './side'; 5 | import { Span } from './span'; 6 | 7 | export class Layer { 8 | name: string = null!; 9 | layerFunction: LayerFunction = null!; 10 | side: Side = null!; 11 | polarity: Polarity = null!; 12 | span: Span | null = null; 13 | profiles: Contour[] = []; 14 | } 15 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/set.ts: -------------------------------------------------------------------------------- 1 | import { NonstandardAttribute } from './nonstandard-attribute'; 2 | import { Pad } from './pad'; 3 | import { ColorGroup } from './color-group'; 4 | import { Features } from './features'; 5 | import { Hole } from './hole'; 6 | 7 | export class Set { 8 | features: Features[] = []; 9 | colorGroups: ColorGroup[] = []; 10 | holes: Hole[] = []; 11 | pads: Pad[] = []; 12 | nonstandardAttributes: NonstandardAttribute[] = []; 13 | } 14 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/characteristics.ts: -------------------------------------------------------------------------------- 1 | import { BomCategory } from './bom-category'; 2 | import { Enumerated } from './enumerated'; 3 | import { Measured } from './measured'; 4 | import { Ranged } from './ranged'; 5 | import { Textual } from './textual'; 6 | 7 | export class Characteristics { 8 | category: BomCategory = null!; 9 | measured: Measured[] = []; 10 | ranged: Ranged[] = []; 11 | enumerated: Enumerated[] = []; 12 | textual: Textual[] = []; 13 | } 14 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/polyline.ts: -------------------------------------------------------------------------------- 1 | import { FillDescGroup } from './fill-desc-group'; 2 | import { LineDescGroup } from './line-desc-group'; 3 | import { PolyBegin } from './poly-begin'; 4 | import { PolyStepCurve } from './poly-step-curve'; 5 | import { PolyStepSegment } from './poly-step-segment'; 6 | 7 | export class Polyline { 8 | polyBegin: PolyBegin = null!; 9 | polySteps: (PolyStepSegment | PolyStepCurve)[] = []; 10 | lineDesc: LineDescGroup | null = null!; 11 | } 12 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/src/lib/render-properties.ts: -------------------------------------------------------------------------------- 1 | import { ComponentRenderProperties } from './component-render-properties'; 2 | import { LayerTypeRenderProperties } from './layer-type-render-properties'; 3 | 4 | export interface RenderProperties { 5 | padding: number; 6 | dropShadow?: { 7 | dx: number; 8 | dy: number; 9 | stdDeviation: number; 10 | }; 11 | layerTypesRenderProperties: LayerTypeRenderProperties[]; 12 | componentRenderProperties: ComponentRenderProperties[]; 13 | } 14 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/polygon.ts: -------------------------------------------------------------------------------- 1 | import { FillDescGroup } from './fill-desc-group'; 2 | import { PolyBegin } from './poly-begin'; 3 | import { PolyStepCurve } from './poly-step-curve'; 4 | import { PolyStepSegment } from './poly-step-segment'; 5 | import { XForm } from './x-form'; 6 | 7 | export class Polygon { 8 | polyBegin: PolyBegin = null!; 9 | polySteps: (PolyStepSegment | PolyStepCurve)[] = []; 10 | fillDesc: FillDescGroup | null = null; 11 | xform: XForm | null = null; 12 | } 13 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationRef, enableProdMode, importProvidersFrom } from '@angular/core'; 2 | 3 | import { AppModule } from './app/app.module'; 4 | import { environment } from './environment'; 5 | import { bootstrapApplication } from '@angular/platform-browser'; 6 | import { AppComponent } from './app/app.component'; 7 | 8 | if (environment.production) { 9 | enableProdMode(); 10 | } 11 | 12 | bootstrapApplication(AppComponent, { 13 | providers: [ 14 | importProvidersFrom(AppModule) 15 | ] 16 | }); -------------------------------------------------------------------------------- /boardui/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "chrome", 9 | "request": "launch", 10 | "name": "Launch Chrome against localhost", 11 | "url": "http://localhost:4200", 12 | "webRoot": "${workspaceFolder}" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/render-context.ts: -------------------------------------------------------------------------------- 1 | import { RenderProperties } from 'boardui-core'; 2 | import { ElementIdProvider } from './element-id-provider'; 3 | import { Renderer } from './renderer'; 4 | import { ReusablesProvider } from './reusables-provider'; 5 | 6 | export class RenderContext { 7 | constructor( 8 | public reusablesProvider: ReusablesProvider, 9 | public elementIdProvider: ElementIdProvider, 10 | public renderProperties: RenderProperties, 11 | public getRenderer: (part: any) => Renderer 12 | ) {} 13 | } 14 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import baseConfig from '../../eslint.config.mjs'; 2 | 3 | export default [ 4 | { 5 | ignores: ['**/dist'], 6 | }, 7 | ...baseConfig, 8 | { 9 | files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], 10 | // Override or add rules here 11 | rules: {}, 12 | }, 13 | { 14 | files: ['**/*.ts', '**/*.tsx'], 15 | // Override or add rules here 16 | rules: {}, 17 | }, 18 | { 19 | files: ['**/*.js', '**/*.jsx'], 20 | // Override or add rules here 21 | rules: {}, 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/src/lib/template/template-collection.ts: -------------------------------------------------------------------------------- 1 | import { Template } from './template'; 2 | 3 | export class TemplateCollection { 4 | private _templates = new Map(); 5 | 6 | /** Gets template by name. */ 7 | get(name: string) { 8 | return this._templates.get(name); 9 | } 10 | 11 | /** Adds template. Template must have unique name. */ 12 | add(template: Template) { 13 | this._templates.set(template.name, template); 14 | } 15 | 16 | remove(name: string) { 17 | this._templates.delete(name); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo-e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "sourceMap": false, 5 | "outDir": "../../dist/out-tsc", 6 | "allowJs": true, 7 | "types": ["cypress", "node"], 8 | "forceConsistentCasingInFileNames": true, 9 | "strict": true, 10 | "noImplicitOverride": true, 11 | "noPropertyAccessFromIndexSignature": true, 12 | "noImplicitReturns": true, 13 | "noFallthroughCasesInSwitch": true 14 | }, 15 | "include": ["src/**/*.ts", "src/**/*.js", "cypress.config.ts"] 16 | } 17 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/src/lib/layer-type.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Declares types of layers. 3 | */ 4 | export type LayerType = 5 | /** Profile/Outline layer. */ 6 | | 'PROFILE' 7 | 8 | /** Signal (Conductive) layer. */ 9 | | 'SIGNAL' 10 | 11 | /** Silkscreen layer. */ 12 | | 'SILKSCREEN' 13 | 14 | /** Documentation layer. */ 15 | | 'DOCUMENTATION' 16 | 17 | /** Drill layer. */ 18 | | 'DRILL' 19 | 20 | /** Component layer. */ 21 | | 'COMPONENT'; 22 | 23 | export const LAYER_TYPES: LayerType[] = ['PROFILE', 'SIGNAL', 'SILKSCREEN', 'DOCUMENTATION', 'DRILL', 'COMPONENT']; 24 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/silk-screen.ts: -------------------------------------------------------------------------------- 1 | import { Marking } from './marking'; 2 | import { MarkingUsage } from './marking-usage'; 3 | import { Outline } from './outline'; 4 | import { Location } from './location'; 5 | import { XForm } from './x-form'; 6 | import { StandardPrimitiveGroup } from './standard-primitive-group'; 7 | 8 | export class SilkScreen { 9 | outline: Outline[] = []; 10 | marking: Marking[] = []; 11 | markingUsage: MarkingUsage = null!; 12 | xform: XForm | null = null; 13 | location: Location = null!; 14 | features: StandardPrimitiveGroup[] = []; 15 | } 16 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc2581-parser.ts: -------------------------------------------------------------------------------- 1 | import { SAXParser } from 'sax-wasm'; 2 | import { IPC2581 } from './ipc/ipc2581'; 3 | import { XMLtoJSMapping } from './xml-to-js-mapping'; 4 | import { DEFAULT_IPC_MAPPINGS } from './default-ipc-mappings'; 5 | import { XmlToJsParser } from './xml-to-js-parser'; 6 | 7 | export class IPC2581Parser extends XmlToJsParser { 8 | constructor(saxParser: SAXParser, mappings?: XMLtoJSMapping[]) { 9 | super( 10 | mappings ? mappings : DEFAULT_IPC_MAPPINGS, 11 | IPC2581.prototype, 12 | saxParser 13 | ); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/test/polygonUtils.ts: -------------------------------------------------------------------------------- 1 | import { FillDescGroup, Polygon, XForm } from 'boardui-parser'; 2 | 3 | export function getPolygon( 4 | segments: [number, number][], 5 | fillDesc?: FillDescGroup, 6 | xform?: XForm 7 | ): Polygon { 8 | const polygon = new Polygon(); 9 | polygon.polyBegin = { 10 | x: segments[0][0], 11 | y: segments[0][1], 12 | }; 13 | polygon.polySteps = segments 14 | .slice(1) 15 | .map((segment) => ({ x: segment[0], y: segment[1] })); 16 | polygon.fillDesc = fillDesc ?? null; 17 | polygon.xform = xform ?? null; 18 | return polygon; 19 | } 20 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import baseConfig from '../../eslint.config.mjs'; 2 | 3 | export default [ 4 | { 5 | ignores: ['**/dist'], 6 | }, 7 | ...baseConfig, 8 | { 9 | files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], 10 | // Override or add rules here 11 | rules: {}, 12 | }, 13 | { 14 | files: ['**/*.ts', '**/*.tsx'], 15 | // Override or add rules here 16 | rules: {}, 17 | }, 18 | { 19 | files: ['**/*.js', '**/*.jsx'], 20 | // Override or add rules here 21 | rules: {}, 22 | }, 23 | { 24 | ignores: ['test/**'], 25 | }, 26 | ]; 27 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import baseConfig from '../../eslint.config.mjs'; 2 | 3 | export default [ 4 | { 5 | ignores: ['**/dist'], 6 | }, 7 | ...baseConfig, 8 | { 9 | files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], 10 | // Override or add rules here 11 | rules: {}, 12 | }, 13 | { 14 | files: ['**/*.ts', '**/*.tsx'], 15 | // Override or add rules here 16 | rules: {}, 17 | }, 18 | { 19 | files: ['**/*.js', '**/*.jsx'], 20 | // Override or add rules here 21 | rules: {}, 22 | }, 23 | { 24 | ignores: ['test/**'], 25 | }, 26 | ]; 27 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/extensions/line-desc.extensions.ts: -------------------------------------------------------------------------------- 1 | import { LineDesc } from 'boardui-parser'; 2 | 3 | export function getLineDescSVGAttributes( 4 | lineDesc: LineDesc 5 | ): [string, string][] { 6 | let strokeLinecap; 7 | if (lineDesc.lineEnd === 'ROUND') { 8 | strokeLinecap = 'round'; 9 | } else if (lineDesc.lineEnd === 'SQUARE') { 10 | strokeLinecap = 'square'; 11 | } else { 12 | strokeLinecap = 'square'; 13 | } 14 | 15 | return [ 16 | ['stroke-linecap', strokeLinecap], 17 | ['stroke-width', lineDesc.lineWidth], 18 | // TODO: Add lineProperty support. 19 | ]; 20 | } 21 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/package.ts: -------------------------------------------------------------------------------- 1 | import { AssemblyDrawing } from './assembly-drawing'; 2 | import { Outline } from './outline'; 3 | import { Pin } from './pin'; 4 | import { PinOneOrientation } from './pin-one-orientation'; 5 | import { SilkScreen } from './silk-screen'; 6 | 7 | export class Package { 8 | name: string = null!; 9 | type: string = null!; 10 | pinOne: string = null!; 11 | pinOneOrientation: PinOneOrientation = null!; 12 | height: number = null!; 13 | outline: Outline = null!; 14 | pins: Pin[] = []; 15 | assemblyDrawing: AssemblyDrawing = null!; 16 | silkScreen: SilkScreen | null = null; 17 | } 18 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/sax-wasm-helper.ts: -------------------------------------------------------------------------------- 1 | import { SaxEventType, SAXParser } from 'sax-wasm'; 2 | 3 | export async function createSAXParser(saxWasmUrl: string): Promise { 4 | const saxWasmResponse = await fetch(saxWasmUrl); 5 | const saxWasmbuffer = await saxWasmResponse.arrayBuffer(); 6 | const parser = new SAXParser( 7 | SaxEventType.Attribute | SaxEventType.OpenTagStart | SaxEventType.CloseTag, 8 | ); 9 | 10 | const ready = parser.prepareWasm(new Uint8Array(saxWasmbuffer)); 11 | if (ready) { 12 | return await ready.then(() => parser); 13 | } else throw new Error('Cannot initialize sax-wasm.'); 14 | } 15 | -------------------------------------------------------------------------------- /boardui/README.md: -------------------------------------------------------------------------------- 1 | # Boardui 2 | 3 | 4 | 5 | ✨ **This workspace has been generated by [Nx, a Smart, fast and extensible build system.](https://nx.dev)** ✨ 6 | 7 | ## Understand this workspace 8 | 9 | Run `nx graph` to see a diagram of the dependencies of the projects. 10 | 11 | ## Remote caching 12 | 13 | Run `npx nx connect-to-nx-cloud` to enable [remote caching](https://nx.app) and make CI faster. 14 | 15 | ## Further help 16 | 17 | Visit the [Nx Documentation](https://nx.dev) to learn more. 18 | -------------------------------------------------------------------------------- /.github/workflows/ci.yaml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - master 6 | pull_request: 7 | 8 | jobs: 9 | main: 10 | runs-on: ubuntu-latest 11 | name: CI 12 | permissions: 13 | contents: 'read' 14 | actions: 'read' 15 | defaults: 16 | run: 17 | working-directory: './boardui' 18 | steps: 19 | - uses: actions/checkout@v2 20 | 21 | - run: npm ci 22 | 23 | - run: npx nx format:check 24 | - run: npx nx run-many --target=lint --parallel=3 25 | - run: npx nx run-many --target=test --parallel=3 --configuration=ci 26 | - run: npx nx run-many --target=build --parallel=3 27 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/profile-contour-renderer.ts: -------------------------------------------------------------------------------- 1 | import { Contour } from 'boardui-parser'; 2 | import '../extensions/polygon.extensions'; 3 | import { RendererBase } from './renderer-base'; 4 | import { getPolygonPath } from '../extensions/polygon.extensions'; 5 | 6 | export class ProfileContourRenderer extends RendererBase { 7 | constructor() { 8 | super('path'); 9 | } 10 | 11 | protected renderPart(part: Contour, partElement: SVGElement): void { 12 | const path = getPolygonPath(part.polygon, part.cutouts); 13 | partElement.setAttribute('d', path); 14 | partElement.setAttribute('stroke-width', '0'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo-e2e/src/support/e2e.ts: -------------------------------------------------------------------------------- 1 | // *********************************************************** 2 | // This example support/index.js is processed and 3 | // loaded automatically before your test files. 4 | // 5 | // This is a great place to put global configuration and 6 | // behavior that modifies Cypress. 7 | // 8 | // You can change the location of this file or turn off 9 | // automatically serving support files with the 10 | // 'supportFile' configuration option. 11 | // 12 | // You can read more here: 13 | // https://on.cypress.io/configuration 14 | // *********************************************************** 15 | 16 | // Import commands.js using ES2015 syntax: 17 | import './commands'; 18 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo-e2e/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { FlatCompat } from '@eslint/eslintrc'; 2 | import { dirname } from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | import js from '@eslint/js'; 5 | import baseConfig from '../../eslint.config.mjs'; 6 | 7 | const compat = new FlatCompat({ 8 | baseDirectory: dirname(fileURLToPath(import.meta.url)), 9 | recommendedConfig: js.configs.recommended, 10 | }); 11 | 12 | export default [ 13 | { 14 | ignores: ['**/dist'], 15 | }, 16 | ...baseConfig, 17 | ...compat.extends('plugin:cypress/recommended'), 18 | { 19 | files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], 20 | // Override or add rules here 21 | rules: {}, 22 | }, 23 | ]; 24 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/element-map.ts: -------------------------------------------------------------------------------- 1 | import { ElementIdProvider } from './element-id-provider'; 2 | 3 | export class ElementMap implements ElementIdProvider { 4 | private _elementMap: Map = new Map(); 5 | private _lastId = 0; 6 | 7 | /** Adds element to map and returns its identifier. */ 8 | addElement(element: any): number { 9 | const newId = ++this._lastId; 10 | this._elementMap.set(newId, element); 11 | return newId; 12 | } 13 | 14 | /** Gets element by its identifier. */ 15 | getElement(id: number): any | null { 16 | return this._elementMap.get(id); 17 | } 18 | 19 | getElementId(element: any): number { 20 | return this.addElement(element); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /boardui/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | dist 5 | tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.sass-cache 29 | /connect.lock 30 | /coverage 31 | /libpeerconnection.log 32 | npm-debug.log 33 | yarn-error.log 34 | testem.log 35 | /typings 36 | 37 | # System Files 38 | .DS_Store 39 | Thumbs.db 40 | 41 | .angular 42 | .nx/cache 43 | .nx/workspace-data 44 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/extensions/xform.extensions.ts: -------------------------------------------------------------------------------- 1 | import { XForm } from 'boardui-parser'; 2 | 3 | declare module 'boardui-parser' { 4 | interface XForm { 5 | getSVGTransformation(): string; 6 | } 7 | } 8 | 9 | XForm.prototype.getSVGTransformation = function (): string { 10 | const transformations = []; 11 | 12 | if (this.mirror) { 13 | if (this.scale) { 14 | transformations.push(`scale(-${this.scale},${this.scale})`); 15 | } else { 16 | transformations.push('scale(-1,1)'); 17 | } 18 | } 19 | 20 | if (this.rotation) { 21 | transformations.push(`rotate(${this.rotation})`); 22 | } 23 | // TODO: Implement more transformation, check if all can be done using transform attribute. 24 | 25 | return transformations.join(' '); 26 | }; 27 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/src/lib/viewer.component/viewer.component.scss: -------------------------------------------------------------------------------- 1 | ::ng-deep bui-viewer { 2 | display: block; 3 | 4 | .toolbar-container { 5 | position: absolute; 6 | z-index: 999; 7 | padding: 0.5rem; 8 | } 9 | 10 | .viewer-container { 11 | position: relative; 12 | touch-action: none; 13 | 14 | svg > g[layerType='SIGNAL'] *[id]:hover, 15 | svg > g[layerType='COMPONENT'] *[id]:hover { 16 | filter: invert(1) contrast(1.25); 17 | cursor: pointer; 18 | } 19 | 20 | svg g[component]:hover { 21 | *[outline] { 22 | stroke-width: 0.1%; 23 | } 24 | } 25 | } 26 | } 27 | 28 | /* Basic component tooltip style. */ 29 | ::ng-deep .bui-tooltip { 30 | position: absolute; 31 | top: 0; 32 | left: 0; 33 | z-index: 1; 34 | } 35 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'boardui-demo', 4 | preset: '../../jest.preset.js', 5 | setupFilesAfterEnv: ['/src/test-setup.ts'], 6 | coverageDirectory: '../../coverage/packages/boardui-demo', 7 | transform: { 8 | '^.+\\.(ts|mjs|js|html)$': [ 9 | 'jest-preset-angular', 10 | { 11 | tsconfig: '/tsconfig.spec.json', 12 | stringifyContentPathRegex: '\\.(html|svg)$', 13 | }, 14 | ], 15 | }, 16 | transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], 17 | snapshotSerializers: [ 18 | 'jest-preset-angular/build/serializers/no-ng-attributes', 19 | 'jest-preset-angular/build/serializers/ng-snapshot', 20 | 'jest-preset-angular/build/serializers/html-comment', 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | export default { 3 | displayName: 'boardui-angular', 4 | preset: '../../jest.preset.js', 5 | setupFilesAfterEnv: ['/src/test-setup.ts'], 6 | coverageDirectory: '../../coverage/packages/boardui-angular', 7 | transform: { 8 | '^.+\\.(ts|mjs|js|html)$': [ 9 | 'jest-preset-angular', 10 | { 11 | tsconfig: '/tsconfig.spec.json', 12 | stringifyContentPathRegex: '\\.(html|svg)$', 13 | }, 14 | ], 15 | }, 16 | transformIgnorePatterns: ['node_modules/(?!.*\\.mjs$)'], 17 | snapshotSerializers: [ 18 | 'jest-preset-angular/build/serializers/no-ng-attributes', 19 | 'jest-preset-angular/build/serializers/ng-snapshot', 20 | 'jest-preset-angular/build/serializers/html-comment', 21 | ], 22 | }; 23 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "useDefineForClassFields": false, 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.lib.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | } 21 | ], 22 | "extends": "../../tsconfig.base.json", 23 | "angularCompilerOptions": { 24 | "enableI18nLegacyMessageIdFormat": false, 25 | "strictInjectionParameters": true, 26 | "strictInputAccessModifiers": true, 27 | "strictTemplates": true 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/component.ts: -------------------------------------------------------------------------------- 1 | import { Location } from './location'; 2 | import { Mount } from './mount'; 3 | import { NonstandardAttribute } from './nonstandard-attribute'; 4 | import { XForm } from './x-form'; 5 | 6 | export class Component { 7 | refDes: string | null = null; 8 | matDes: string | null = null; 9 | packageRef: string | null = null; 10 | part: string = null!; 11 | layerRef: string = null!; 12 | layerRefTopside: string | null = null; 13 | mountType: Mount = null!; 14 | modelRef: string | null = null; 15 | weight: number | null = null!; 16 | height: number | null = null!; 17 | standoff: number | null = null!; 18 | location: Location = null!; 19 | xform: XForm | null = null; 20 | nonstandardAttributes: NonstandardAttribute[] = []; 21 | // TODO: More properties, IDK if usable :) 22 | } 23 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/extensions/fill-desc.extensions.ts: -------------------------------------------------------------------------------- 1 | import { Color, ColorRef, FillDesc } from 'boardui-parser'; 2 | import { ReusablesProvider } from '../reusables-provider'; 3 | import { getSvgColor } from './color.extensions'; 4 | 5 | export function getFillDescSVGAttributes( 6 | fillDesc: FillDesc, 7 | reusablesProvider: ReusablesProvider 8 | ): [string, string][] { 9 | const attributes: [string, string][] = []; 10 | 11 | if (fillDesc.fillProperty === 'VOID') { 12 | attributes.push(['fill', 'none']); 13 | } else { 14 | const fillColor = 15 | fillDesc.color instanceof ColorRef 16 | ? reusablesProvider.getColorById(fillDesc.color.id) 17 | : fillDesc.color; 18 | if (fillColor) { 19 | attributes.push(['fill', getSvgColor(fillColor)]); 20 | } 21 | } 22 | 23 | return attributes; 24 | } 25 | -------------------------------------------------------------------------------- /boardui/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "rootDir": ".", 5 | "sourceMap": true, 6 | "declaration": false, 7 | "moduleResolution": "node", 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "importHelpers": true, 11 | "target": "es2015", 12 | "module": "esnext", 13 | "lib": ["es2022", "dom"], 14 | "skipLibCheck": true, 15 | "skipDefaultLibCheck": true, 16 | "baseUrl": ".", 17 | "paths": { 18 | "boardui-angular": ["packages/boardui-angular/src/index.ts"], 19 | "boardui-core": ["packages/boardui-core/src/index.ts"], 20 | "boardui-parser": ["packages/boardui-parser/src/index.ts"], 21 | "boardui-renderer": ["packages/boardui-renderer/src/index.ts"] 22 | } 23 | }, 24 | "exclude": ["node_modules", "tmp"] 25 | } 26 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/content.ts: -------------------------------------------------------------------------------- 1 | import { BomRef } from './bom-ref'; 2 | import { Color } from './color'; 3 | import { Dictionary } from './dictionary'; 4 | import { FillDesc } from './fill-desc'; 5 | import { FunctionMode } from './function-mode'; 6 | import { LayerRef } from './layer-ref'; 7 | import { LineDesc } from './line-desc'; 8 | import { StandardPrimitive } from './standard-primitive'; 9 | import { StepRef } from './step-ref'; 10 | 11 | export class Content { 12 | functionMode: FunctionMode = null!; 13 | roleRef: string = null!; 14 | stepRefs: StepRef[] = []; 15 | layerRefs: LayerRef[] = []; 16 | bomRefs: BomRef[] = []; 17 | 18 | dictionaryColor: Dictionary = null!; 19 | dictionaryLineDesc: Dictionary = null!; 20 | dictionaryFillDesc: Dictionary = null!; 21 | dictionaryStandard: Dictionary = null!; 22 | } 23 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2022", 4 | "useDefineForClassFields": false, 5 | "forceConsistentCasingInFileNames": true, 6 | "strict": true, 7 | "noImplicitOverride": true, 8 | "noPropertyAccessFromIndexSignature": true, 9 | "noImplicitReturns": true, 10 | "noFallthroughCasesInSwitch": true 11 | }, 12 | "files": [], 13 | "include": [], 14 | "references": [ 15 | { 16 | "path": "./tsconfig.app.json" 17 | }, 18 | { 19 | "path": "./tsconfig.spec.json" 20 | }, 21 | { 22 | "path": "./tsconfig.editor.json" 23 | } 24 | ], 25 | "extends": "../../tsconfig.base.json", 26 | "angularCompilerOptions": { 27 | "enableI18nLegacyMessageIdFormat": false, 28 | "strictInjectionParameters": true, 29 | "strictInputAccessModifiers": true, 30 | "strictTemplates": true 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo-e2e/src/e2e/app.cy.ts: -------------------------------------------------------------------------------- 1 | import { getBOMTable, getFileChooser, getPCBSvg } from '../support/app.po'; 2 | 3 | describe('boardui-demo', () => { 4 | beforeEach(() => cy.visit('/')); 5 | 6 | it('should display PCB', () => { 7 | getPCBSvg().should('exist'); 8 | getPCBSvg().should('be.visible'); 9 | }); 10 | 11 | it('should display BOM', () => { 12 | getBOMTable().should('exist'); 13 | getBOMTable().find('tbody').first().children().should('have.length', 7); 14 | }); 15 | 16 | it('should change file', () => { 17 | getPCBSvg().should('exist'); 18 | getPCBSvg().invoke('attr', 'step').as('previousStep'); 19 | 20 | getFileChooser().selectFile(['src/assets/testcase3-RevC-Assembly.xml'], { 21 | force: true, 22 | }); 23 | 24 | cy.get('@previousStep').then((step) => { 25 | getPCBSvg().should('not.have.attr', 'step', step); 26 | getPCBSvg().should('be.visible'); 27 | }); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/src/lib/default-render-properties.ts: -------------------------------------------------------------------------------- 1 | import { RenderProperties } from "./render-properties"; 2 | 3 | export const DEFAULT_RENDER_PROPERTIES: RenderProperties = { 4 | padding: 0.5, 5 | dropShadow: { 6 | dx: 0, 7 | dy: 0, 8 | stdDeviation: 0.02, 9 | }, 10 | layerTypesRenderProperties: [ 11 | { 12 | layerType: 'PROFILE', 13 | color: { 14 | r: 26, 15 | g: 80, 16 | b: 26, 17 | }, 18 | }, 19 | { 20 | layerType: 'SIGNAL', 21 | color: { 22 | r: 55, 23 | g: 105, 24 | b: 48, 25 | }, 26 | }, 27 | { 28 | layerType: 'COMPONENT', 29 | color: { 30 | r: 200, 31 | g: 140, 32 | b: 48, 33 | }, 34 | }, 35 | { 36 | layerType: 'SILKSCREEN', 37 | color: { 38 | r: 255, 39 | g: 255, 40 | b: 255, 41 | }, 42 | }, 43 | { 44 | layerType: 'DRILL', 45 | }, 46 | ], 47 | componentRenderProperties: [] 48 | }; -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/layers/layer-base.ts: -------------------------------------------------------------------------------- 1 | import { Layer, Step } from 'boardui-parser'; 2 | import { LayerType, LayerTypeRenderProperties } from 'boardui-core'; 3 | import { RenderContext } from '../render-context'; 4 | 5 | export abstract class LayerBase { 6 | protected _layerElement!: SVGElement; 7 | protected _renderProperties: LayerTypeRenderProperties | null = null; 8 | 9 | protected get name(): string { 10 | return this._layer.name; 11 | } 12 | 13 | public get layer(): Layer { 14 | return this._layer; 15 | } 16 | 17 | constructor( 18 | protected _layer: Layer, 19 | protected _step: Step, 20 | protected _renderContext: RenderContext, 21 | protected _layerType: LayerType 22 | ) { 23 | this._renderProperties = 24 | this._renderContext.renderProperties.layerTypesRenderProperties.find( 25 | (layerTypeRenderProperty) => 26 | layerTypeRenderProperty.layerType === _layerType 27 | ) ?? null; 28 | } 29 | 30 | abstract render(): SVGElement; 31 | } 32 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo-e2e/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boardui-demo-e2e", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/boardui-demo-e2e/src", 5 | "projectType": "application", 6 | "tags": [], 7 | "implicitDependencies": ["boardui-demo"], 8 | "targets": { 9 | "e2e": { 10 | "executor": "@nx/cypress:cypress", 11 | "options": { 12 | "cypressConfig": "packages/boardui-demo-e2e/cypress.config.ts", 13 | "devServerTarget": "boardui-demo:serve:development", 14 | "testingType": "e2e" 15 | }, 16 | "configurations": { 17 | "production": { 18 | "devServerTarget": "boardui-demo:serve:production" 19 | }, 20 | "ci": { 21 | "devServerTarget": "boardui-demo:serve-static" 22 | } 23 | } 24 | }, 25 | "lint": { 26 | "executor": "@nx/eslint:lint", 27 | "outputs": ["{options.outputFile}"], 28 | "options": { 29 | "lintFilePatterns": ["packages/boardui-demo-e2e/**/*.{js,ts}"] 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/layer-function.ts: -------------------------------------------------------------------------------- 1 | export type LayerFunction = 2 | | 'ASSEMBLY' 3 | | 'BOARDFAB' 4 | | 'BOARD_OUTLINE' 5 | | 'CAPACITIVE' 6 | | 'COATINGCOND' 7 | | 'COATINGNONCOND' 8 | | 'COMPONENT' 9 | | 'COMPONENT_BOTTOM' 10 | | 'COMPONENT_TOP' 11 | | 'COMPONENT_EMBEDDED' 12 | | 'COMPONENT_FORMED' 13 | | 'CONDFILM' 14 | | 'CONDFOIL' 15 | | 'CONDUCTIVE_ADHESIVE' 16 | | 'CONDUCTOR' 17 | | 'COURTYARD' 18 | | 'DIELBASE' 19 | | 'DIELCORE' 20 | | 'DIELPREG' 21 | | 'DIELADHV' 22 | | 'DIELBONDPLY' 23 | | 'DIELCOVERLAY' 24 | | 'DOCUMENT' 25 | | 'DRILL' 26 | | 'FIXTURE' 27 | | 'GLUE' 28 | | 'GRAPHIC' 29 | | 'HOLEFILL' 30 | | 'SOLDERBUMP' 31 | | 'PASTEMASK' 32 | | 'LANDPATTERN' 33 | | 'LEGEND' 34 | | 'MIXED' 35 | | 'OTHER' 36 | | 'PIN' 37 | | 'PLANE' 38 | | 'PROBE' 39 | | 'RESISTIVE' 40 | | 'SIGNAL' 41 | | 'SILKSCREEN' 42 | | 'SOLDERMASK' 43 | | 'SOLDERPASTE' 44 | | 'STACKUP_COMPOSITE' 45 | | 'REWORK' 46 | | 'ROUT' 47 | | 'V_CUT' 48 | | 'EDGE_CHAMFER' 49 | | 'EDGE_PLATING' 50 | | 'THIEVING_KEEP_INOUT' 51 | | 'STIFFENER'; 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Michal Dub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/reusables-provider.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LineDesc, 3 | FillDesc, 4 | Color, 5 | StandardPrimitive, 6 | Package, 7 | Layer, 8 | } from 'boardui-parser'; 9 | 10 | export interface ReusablesProvider { 11 | /** Provides layer element. 12 | * @param name Layer name. 13 | */ 14 | getLayerByName(name: string): Layer; 15 | 16 | /** Provides color element. 17 | * @param colorId Color identifier. 18 | */ 19 | getColorById(colorId: string): Color; 20 | 21 | /** Provides line desctiptor element. 22 | * @param lineDescId line descriptor identifier. 23 | */ 24 | getLineDescById(lineDescId: string): LineDesc; 25 | 26 | /** Provides fill desctiptor element. 27 | * @param fillDescId fill descriptor identifier. 28 | */ 29 | getFillDescById(fillDescId: string): FillDesc; 30 | 31 | /** Provides predefined primitive. 32 | * @param primitiveId Primitive identifier. 33 | */ 34 | getPrimitiveById(primitiveId: string): StandardPrimitive; 35 | 36 | /** Provides package element. 37 | * @param name Package name. 38 | */ 39 | getPackageByName(name: string): Package; 40 | } 41 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/polyline-renderer.ts: -------------------------------------------------------------------------------- 1 | import { Polyline, LineDescRef, LineDesc } from 'boardui-parser'; 2 | import { RendererBase } from './renderer-base'; 3 | import { ReusablesProvider } from '../reusables-provider'; 4 | import { getLineDescSVGAttributes } from '../extensions/line-desc.extensions'; 5 | import { getPolygonPath } from '../extensions/polygon.extensions'; 6 | 7 | export class PolylineRenderer extends RendererBase { 8 | constructor() { 9 | super('path'); 10 | } 11 | 12 | protected renderPart( 13 | part: Polyline, 14 | partElement: SVGElement, 15 | reusablesProvider: ReusablesProvider 16 | ): void { 17 | partElement.setAttribute('d', getPolygonPath(part, [])); 18 | 19 | if (part.lineDesc) { 20 | const lineDesc = 21 | part.lineDesc instanceof LineDescRef 22 | ? reusablesProvider.getLineDescById(part.lineDesc.id) 23 | : (part.lineDesc as LineDesc); 24 | 25 | for (const lineDescAttribute of getLineDescSVGAttributes(lineDesc)) { 26 | partElement.setAttribute(...lineDescAttribute); 27 | } 28 | } 29 | 30 | partElement.setAttribute('fill', 'none'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /boardui/.nx/cache/terminalOutputs/4436582216557151009: -------------------------------------------------------------------------------- 1 | Building Angular Package 2 |  3 | ------------------------------------------------------------------------------ 4 | Building entry point 'boardui-angular' 5 | ------------------------------------------------------------------------------ 6 | - Compiling with Angular sources in Ivy partial compilation mode. 7 | ✔ Compiling with Angular sources in Ivy partial compilation mode. 8 | ✔ Generating FESM bundles 9 | - Copying assets 10 | ✔ Copying assets 11 | - Writing package manifest 12 | ✔ Writing package manifest 13 | ✔ Built boardui-angular 14 |  15 | ------------------------------------------------------------------------------ 16 | Built Angular Package 17 |  - from: D:/repos/boardui/boardui/boardui/packages/boardui-angular 18 |  - to: D:/repos/boardui/boardui/boardui/dist/packages/boardui-angular 19 | ------------------------------------------------------------------------------ 20 |  21 | Build at: 2025-03-03T22:11:56.950Z - Time: 965ms 22 |  23 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/src/lib/viewer.component/viewer.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { ComponentFixture, TestBed } from '@angular/core/testing'; 2 | 3 | import { ViewerComponent } from './viewer.component'; 4 | 5 | describe('PcbViewerComponent', () => { 6 | let component: ViewerComponent; 7 | let fixture: ComponentFixture; 8 | 9 | beforeEach(async () => { 10 | await TestBed.configureTestingModule({ 11 | imports: [ViewerComponent], 12 | }).compileComponents(); 13 | 14 | fixture = TestBed.createComponent(ViewerComponent); 15 | component = fixture.componentInstance; 16 | fixture.detectChanges(); 17 | }); 18 | 19 | it('should create', () => { 20 | expect(component).toBeTruthy(); 21 | }); 22 | 23 | /*it('should render pcb', () => { 24 | component.pcb = { revision: 'C', content: null!, ecad: null!, bom: null! }; 25 | component.step = "testStep"; 26 | component.side = "TOP"; 27 | component.renderProperties = { 28 | padding: 10, 29 | dropShadow: { 30 | dx: 1, 31 | dy: 2, 32 | stdDeviation: 3 33 | }, 34 | layerTypesRenderProperties: [], 35 | componentRenderProperties: [], 36 | }; 37 | 38 | expect(component).toBeTruthy(); 39 | });*/ 40 | }); 41 | -------------------------------------------------------------------------------- /boardui/packages/boardui-core/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boardui-core", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/boardui-core/src", 5 | "projectType": "library", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/js:tsc", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/packages/boardui-core", 13 | "main": "packages/boardui-core/src/index.ts", 14 | "tsConfig": "packages/boardui-core/tsconfig.lib.json", 15 | "assets": ["packages/boardui-core/*.md"] 16 | } 17 | }, 18 | "lint": { 19 | "executor": "@nx/eslint:lint", 20 | "outputs": ["{options.outputFile}"], 21 | "options": { 22 | "lintFilePatterns": ["packages/boardui-core/**/*.ts"] 23 | } 24 | }, 25 | "test": { 26 | "executor": "@nx/jest:jest", 27 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 28 | "options": { 29 | "jestConfig": "packages/boardui-core/jest.config.ts", 30 | "passWithNoTests": true 31 | }, 32 | "configurations": { 33 | "ci": { 34 | "ci": true, 35 | "codeCoverage": true 36 | } 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/hole-renderer.ts: -------------------------------------------------------------------------------- 1 | import { Hole } from 'boardui-parser'; 2 | import { RendererBase } from './renderer-base'; 3 | 4 | export class HoleRenderer extends RendererBase { 5 | constructor() { 6 | super('g'); 7 | } 8 | 9 | protected renderPart(part: Hole, partElement: SVGElement): void { 10 | if (part.type === 'CIRCLE') { 11 | partElement.setAttribute('r', (part.diameter / 2).toString()); 12 | partElement.setAttribute('cx', part.x.toString()); 13 | partElement.setAttribute('cy', part.y.toString()); 14 | partElement.setAttribute('fill', 'black'); 15 | } 16 | } 17 | 18 | private _cloneableCirclePartElement = document.createElementNS( 19 | 'http://www.w3.org/2000/svg', 20 | 'circle' 21 | ); 22 | private _cloneableRectPartElement = document.createElementNS( 23 | 'http://www.w3.org/2000/svg', 24 | 'rect' 25 | ); 26 | protected override getPartElement(part: Hole): SVGElement { 27 | if (part.type === 'CIRCLE') { 28 | return this._cloneableCirclePartElement.cloneNode() as SVGElement; 29 | } else if (part.type === 'SQUARE') { 30 | return this._cloneableRectPartElement.cloneNode() as SVGElement; 31 | } else { 32 | throw new Error('Not supported.'); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo-e2e/src/support/commands.ts: -------------------------------------------------------------------------------- 1 | // *********************************************** 2 | // This example commands.js shows you how to 3 | // create various custom commands and overwrite 4 | // existing commands. 5 | // 6 | // For more comprehensive examples of custom 7 | // commands please read more here: 8 | // https://on.cypress.io/custom-commands 9 | // *********************************************** 10 | 11 | // eslint-disable-next-line @typescript-eslint/no-namespace 12 | declare namespace Cypress { 13 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 14 | interface Chainable { 15 | login(email: string, password: string): void; 16 | } 17 | } 18 | // 19 | // -- This is a parent command -- 20 | Cypress.Commands.add('login', (email, password) => { 21 | console.log('Custom command example: Login', email, password); 22 | }); 23 | // 24 | // -- This is a child command -- 25 | // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) 26 | // 27 | // 28 | // -- This is a dual command -- 29 | // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) 30 | // 31 | // 32 | // -- This will overwrite an existing command -- 33 | // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) 34 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/app/error-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { MatButtonModule } from '@angular/material/button'; 4 | import { 5 | MatDialogModule, 6 | MatDialogRef, 7 | MAT_DIALOG_DATA, 8 | } from '@angular/material/dialog'; 9 | import { MatFormFieldModule } from '@angular/material/form-field'; 10 | import { MatInputModule } from '@angular/material/input'; 11 | 12 | export interface ErrorDialogData { 13 | title: string; 14 | message: string; 15 | } 16 | 17 | @Component({ 18 | selector: 'app-error-dialog', 19 | template: ` 20 |

{{ data.title }}

21 |
22 | {{ data.message }}
Details are available in console. 23 |
24 |
25 | 26 |
27 | `, 28 | standalone: true, 29 | imports: [ 30 | MatDialogModule, 31 | MatFormFieldModule, 32 | MatInputModule, 33 | FormsModule, 34 | MatButtonModule, 35 | ], 36 | }) 37 | export class ErrorDialogComponent { 38 | constructor( 39 | public dialogRef: MatDialogRef, 40 | @Inject(MAT_DIALOG_DATA) public data: ErrorDialogData 41 | ) {} 42 | } 43 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/line-renderer.ts: -------------------------------------------------------------------------------- 1 | import { LineDescRef, Line } from 'boardui-parser'; 2 | import '../extensions/fill-desc.extensions'; 3 | import { RendererBase } from './renderer-base'; 4 | import { ReusablesProvider } from '../reusables-provider'; 5 | import { getLineDescSVGAttributes } from '../extensions/line-desc.extensions'; 6 | 7 | export class LineRenderer extends RendererBase { 8 | constructor() { 9 | super('line'); 10 | } 11 | 12 | protected renderPart( 13 | part: Line, 14 | partElement: SVGElement, 15 | reusablesProvider: ReusablesProvider 16 | ): void { 17 | partElement.setAttribute('x1', part.startX.toString()); 18 | partElement.setAttribute('x2', part.endX.toString()); 19 | partElement.setAttribute('y1', part.startY.toString()); 20 | partElement.setAttribute('y2', part.endY.toString()); 21 | 22 | if (part.lineDesc) { 23 | const lineDesc = 24 | part.lineDesc instanceof LineDescRef 25 | ? reusablesProvider.getLineDescById(part.lineDesc.id) 26 | : part.lineDesc; 27 | const lineDescAttributes = getLineDescSVGAttributes(lineDesc); 28 | for (const lineDescAttribute of lineDescAttributes) { 29 | partElement.setAttribute(...lineDescAttribute); 30 | } 31 | } else { 32 | partElement.setAttribute('stroke-width', '0'); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/contour-renderer.ts: -------------------------------------------------------------------------------- 1 | import { Contour, FillDescRef } from 'boardui-parser'; 2 | import '../extensions/polygon.extensions'; 3 | import { RendererBase } from './renderer-base'; 4 | import { ReusablesProvider } from '../reusables-provider'; 5 | import { getFillDescSVGAttributes } from '../extensions/fill-desc.extensions'; 6 | import { getPolygonPath } from '../extensions/polygon.extensions'; 7 | 8 | export class ContourRenderer extends RendererBase { 9 | constructor() { 10 | super('path'); 11 | } 12 | 13 | protected renderPart( 14 | part: Contour, 15 | partElement: SVGElement, 16 | reusablesProvider: ReusablesProvider 17 | ): void { 18 | const path = getPolygonPath(part.polygon, part.cutouts); 19 | partElement.setAttribute('d', path); 20 | partElement.setAttribute('stroke-width', '0'); 21 | 22 | if (part.polygon.fillDesc) { 23 | const fillDesc = 24 | part.polygon.fillDesc instanceof FillDescRef 25 | ? reusablesProvider.getFillDescById(part.polygon.fillDesc.id) 26 | : part.polygon.fillDesc; 27 | const fillDescAttributes = getFillDescSVGAttributes( 28 | fillDesc, 29 | reusablesProvider 30 | ); 31 | for (const fillDescAttribute of fillDescAttributes) { 32 | partElement.setAttribute(...fillDescAttribute); 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/styles.scss: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | 3 | @use '@angular/material' as mat; 4 | 5 | @include mat.core(); 6 | 7 | @import '~@angular/material/prebuilt-themes/indigo-pink.css'; 8 | 9 | @font-face { 10 | font-family: Normschrift; 11 | src: url('assets/normschrift.woff2') format('woff2'); 12 | font-display: swap; 13 | } 14 | 15 | html { 16 | overflow: hidden !important; 17 | } 18 | 19 | html, 20 | body { 21 | height: 100%; 22 | font-size: 13pt; 23 | } 24 | 25 | body { 26 | overflow: overlay; 27 | margin: 0; 28 | font-family: Normschrift, 'Helvetica Neue', sans-serif; 29 | 30 | button { 31 | font-family: Normschrift, 'Helvetica Neue', sans-serif; 32 | } 33 | } 34 | 35 | ::-webkit-scrollbar { 36 | width: 0.5rem; 37 | height: 0.5rem; 38 | } 39 | ::-webkit-scrollbar-button { 40 | width: 0px; 41 | height: 0px; 42 | } 43 | ::-webkit-scrollbar-thumb { 44 | background: #e1e1e1; 45 | border: 0px none #ffffff; 46 | border-radius: 0.25rem; 47 | } 48 | ::-webkit-scrollbar-thumb:hover { 49 | background: #c6c6c6; 50 | } 51 | ::-webkit-scrollbar-thumb:active { 52 | background: #1e3d59; 53 | } 54 | ::-webkit-scrollbar-track { 55 | background: transparent; 56 | } 57 | ::-webkit-scrollbar-corner { 58 | background: transparent; 59 | } 60 | 61 | mat-dialog-container { 62 | max-width: 600px !important; 63 | } -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/app/element-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { MatButtonModule } from '@angular/material/button'; 4 | import { 5 | MatDialogModule, 6 | MatDialogRef, 7 | MAT_DIALOG_DATA, 8 | } from '@angular/material/dialog'; 9 | import { MatFormFieldModule } from '@angular/material/form-field'; 10 | import { MatInputModule } from '@angular/material/input'; 11 | 12 | export interface ElementDialogData { 13 | type: string; 14 | json: string; 15 | } 16 | 17 | @Component({ 18 | selector: 'app-element-dialog', 19 | template: ` 20 |

PCB element data preview

21 |
22 | Element type: {{data.type}}
23 | Data: 24 |
{{data.json}}
25 |
26 |
27 | 28 |
29 | `, 30 | standalone: true, 31 | imports: [ 32 | MatDialogModule, 33 | MatFormFieldModule, 34 | MatInputModule, 35 | FormsModule, 36 | MatButtonModule, 37 | ], 38 | }) 39 | export class ElementDialogComponent { 40 | constructor( 41 | public dialogRef: MatDialogRef, 42 | @Inject(MAT_DIALOG_DATA) public data: ElementDialogData 43 | ) {} 44 | } 45 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'; 2 | import { BoardUIModule } from 'boardui-angular'; 3 | import { AppComponent } from './app.component'; 4 | import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; 5 | import { MatButtonModule } from '@angular/material/button'; 6 | import { MatIconModule } from '@angular/material/icon'; 7 | import { MatFormFieldModule } from '@angular/material/form-field'; 8 | import { MatTableModule } from '@angular/material/table'; 9 | import { MatTabsModule } from '@angular/material/tabs'; 10 | import { MatExpansionModule } from '@angular/material/expansion'; 11 | import { HttpClientModule } from '@angular/common/http'; 12 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 13 | import { MatDialogModule } from '@angular/material/dialog'; 14 | import { CommonModule } from '@angular/common'; 15 | 16 | @NgModule({ 17 | imports: [ 18 | AppComponent, 19 | CommonModule, 20 | HttpClientModule, 21 | BoardUIModule, 22 | BrowserAnimationsModule, 23 | MatButtonModule, 24 | MatIconModule, 25 | MatFormFieldModule, 26 | MatTableModule, 27 | MatTabsModule, 28 | MatExpansionModule, 29 | MatProgressSpinnerModule, 30 | MatDialogModule, 31 | ], 32 | providers: [], 33 | schemas: [CUSTOM_ELEMENTS_SCHEMA], 34 | }) 35 | export class AppModule {} 36 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boardui-parser", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/boardui-parser/src", 5 | "projectType": "library", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/js:tsc", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/packages/boardui-parser", 13 | "main": "packages/boardui-parser/src/index.ts", 14 | "tsConfig": "packages/boardui-parser/tsconfig.lib.json", 15 | "assets": ["packages/boardui-parser/*.md"] 16 | }, 17 | "dependsOn": [ 18 | { 19 | "dependencies": true, 20 | "target": "build" 21 | } 22 | ] 23 | }, 24 | "lint": { 25 | "executor": "@nx/eslint:lint", 26 | "outputs": ["{options.outputFile}"], 27 | "options": { 28 | "lintFilePatterns": ["packages/boardui-parser/**/*.ts"] 29 | } 30 | }, 31 | "test": { 32 | "executor": "@nx/jest:jest", 33 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 34 | "options": { 35 | "jestConfig": "packages/boardui-parser/jest.config.ts", 36 | "passWithNoTests": true 37 | }, 38 | "configurations": { 39 | "ci": { 40 | "ci": true, 41 | "codeCoverage": true 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boardui-renderer", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/boardui-renderer/src", 5 | "projectType": "library", 6 | "tags": [], 7 | "targets": { 8 | "build": { 9 | "executor": "@nx/js:tsc", 10 | "outputs": ["{options.outputPath}"], 11 | "options": { 12 | "outputPath": "dist/packages/boardui-renderer", 13 | "main": "packages/boardui-renderer/src/index.ts", 14 | "tsConfig": "packages/boardui-renderer/tsconfig.lib.json", 15 | "assets": ["packages/boardui-renderer/*.md"] 16 | }, 17 | "dependsOn": [ 18 | { 19 | "dependencies": true, 20 | "target": "build" 21 | } 22 | ] 23 | }, 24 | "lint": { 25 | "executor": "@nx/eslint:lint", 26 | "outputs": ["{options.outputFile}"], 27 | "options": { 28 | "lintFilePatterns": ["packages/boardui-renderer/**/*.ts"] 29 | } 30 | }, 31 | "test": { 32 | "executor": "@nx/jest:jest", 33 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 34 | "options": { 35 | "jestConfig": "packages/boardui-renderer/jest.config.ts", 36 | "passWithNoTests": true 37 | }, 38 | "configurations": { 39 | "ci": { 40 | "ci": true, 41 | "codeCoverage": true 42 | } 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/test/reusables-provider.mock.ts: -------------------------------------------------------------------------------- 1 | import { FillDesc, Package } from 'boardui-parser/src'; 2 | import { ReusablesProvider } from '../src/lib/reusables-provider'; 3 | import { getPolygon } from './polygonUtils'; 4 | 5 | export const ReusablesProviderMock: ReusablesProvider = { 6 | getFillDescById: (): FillDesc => ({ 7 | fillProperty: 'FILL', 8 | lineWidth: 1, 9 | pitch1: 2, 10 | pitch2: 3, 11 | angle1: 4, 12 | angle2: 5, 13 | color: { 14 | r: 6, 15 | g: 7, 16 | b: 8, 17 | }, 18 | }), 19 | getLayerByName: function (name: string) { 20 | throw new Error('Function not implemented.'); 21 | }, 22 | getColorById: function (colorId: string) { 23 | throw new Error('Function not implemented.'); 24 | }, 25 | getLineDescById: function (lineDescId: string) { 26 | throw new Error('Function not implemented.'); 27 | }, 28 | getPrimitiveById: function (primitiveId: string) { 29 | throw new Error('Function not implemented.'); 30 | }, 31 | getPackageByName: function (name: string) { 32 | const testPackage = new Package(); 33 | testPackage.outline = { 34 | polygon: getPolygon([ 35 | [0, 0], 36 | [1, 0], 37 | [1, 1], 38 | [0, 1], 39 | ]), 40 | lineDesc: { 41 | lineEnd: 'ROUND', 42 | lineWidth: '1', 43 | lineProperty: 'SOLID', 44 | }, 45 | }; 46 | 47 | return testPackage; 48 | }, 49 | }; 50 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | BoardUI | Demo and Examples 6 | 7 | 8 | 9 | 16 | 20 | 24 | 25 | 29 | 33 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/pin-renderer.ts: -------------------------------------------------------------------------------- 1 | import { RendererBase } from './renderer-base'; 2 | import { ElementType } from '../element-type'; 3 | import { ElementIdProvider } from '../element-id-provider'; 4 | import { RenderProperties } from 'boardui-core'; 5 | import { Pin } from 'boardui-parser'; 6 | import { ReusablesProvider } from '../reusables-provider'; 7 | 8 | export class PinRenderer extends RendererBase { 9 | private _cloneablePinAttribute = document.createAttribute(ElementType.PIN); 10 | constructor() { 11 | super('g'); 12 | } 13 | 14 | protected renderPart( 15 | part: Pin, 16 | partElement: SVGElement, 17 | reusablesProvider: ReusablesProvider, 18 | elementIdProvider: ElementIdProvider, 19 | renderProperties: RenderProperties, 20 | renderSubpart: (part: any, partElement: SVGElement) => void 21 | ): void { 22 | partElement.setAttributeNode( 23 | this._cloneablePinAttribute.cloneNode() as Attr 24 | ); 25 | partElement.setAttribute( 26 | 'id', 27 | elementIdProvider.getElementId(part).toString() 28 | ); 29 | 30 | const transformation = [`translate(${part.location.x},${part.location.y})`]; 31 | if (part.xform) transformation.push(part.xform.getSVGTransformation()); 32 | 33 | partElement.setAttribute('transform', transformation.join(' ')); 34 | 35 | const standardPrimitive = reusablesProvider.getPrimitiveById( 36 | part.standardPrimitiveRef.id 37 | ); 38 | renderSubpart(standardPrimitive, partElement); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/renderer-base.ts: -------------------------------------------------------------------------------- 1 | import { RenderProperties } from 'boardui-core'; 2 | import { ElementIdProvider } from '../element-id-provider'; 3 | import { RenderContext } from '../render-context'; 4 | import { Renderer } from '../renderer'; 5 | import { ReusablesProvider } from '../reusables-provider'; 6 | 7 | export abstract class RendererBase implements Renderer { 8 | private _cloneablePartElement = document.createElementNS( 9 | 'http://www.w3.org/2000/svg', 10 | this._tagName 11 | ); 12 | 13 | protected getPartElement(part: T): SVGElement { 14 | return this._cloneablePartElement.cloneNode() as SVGElement; 15 | } 16 | 17 | protected abstract renderPart( 18 | part: T, 19 | partElement: SVGElement, 20 | reusablesProvider: ReusablesProvider, 21 | elementIdProvider: ElementIdProvider, 22 | renderProperties: RenderProperties, 23 | renderSubpart: (part: any, partElement: SVGElement) => void 24 | ): void; 25 | 26 | constructor(private _tagName: string) {} 27 | 28 | public render(part: T, target: Node, context: RenderContext): void { 29 | const partElement = this.getPartElement(part); 30 | 31 | this.renderPart( 32 | part, 33 | partElement, 34 | context.reusablesProvider, 35 | context.elementIdProvider, 36 | context.renderProperties, 37 | (part: any, partElement: SVGElement) => 38 | context.getRenderer(part).render(part, partElement, context) 39 | ); 40 | 41 | target.appendChild(partElement); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { FlatCompat } from '@eslint/eslintrc'; 2 | import { dirname } from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | import js from '@eslint/js'; 5 | import baseConfig from '../../eslint.config.mjs'; 6 | 7 | const compat = new FlatCompat({ 8 | baseDirectory: dirname(fileURLToPath(import.meta.url)), 9 | recommendedConfig: js.configs.recommended, 10 | }); 11 | 12 | export default [ 13 | { 14 | ignores: ['**/dist'], 15 | }, 16 | ...baseConfig, 17 | ...compat 18 | .config({ 19 | extends: [ 20 | 'plugin:@nx/angular', 21 | 'plugin:@angular-eslint/template/process-inline-templates', 22 | ], 23 | }) 24 | .map((config) => ({ 25 | ...config, 26 | files: ['**/*.ts'], 27 | rules: { 28 | ...config.rules, 29 | '@angular-eslint/directive-selector': [ 30 | 'error', 31 | { 32 | type: 'attribute', 33 | prefix: 'app', 34 | style: 'camelCase', 35 | }, 36 | ], 37 | '@angular-eslint/component-selector': [ 38 | 'error', 39 | { 40 | type: 'element', 41 | prefix: 'app', 42 | style: 'kebab-case', 43 | }, 44 | ], 45 | }, 46 | })), 47 | ...compat 48 | .config({ 49 | extends: ['plugin:@nx/angular-template'], 50 | }) 51 | .map((config) => ({ 52 | ...config, 53 | files: ['**/*.html'], 54 | rules: { 55 | ...config.rules, 56 | }, 57 | })), 58 | ]; 59 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { FlatCompat } from '@eslint/eslintrc'; 2 | import { dirname } from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | import js from '@eslint/js'; 5 | import baseConfig from '../../eslint.config.mjs'; 6 | 7 | const compat = new FlatCompat({ 8 | baseDirectory: dirname(fileURLToPath(import.meta.url)), 9 | recommendedConfig: js.configs.recommended, 10 | }); 11 | 12 | export default [ 13 | { 14 | ignores: ['**/dist'], 15 | }, 16 | ...baseConfig, 17 | ...compat 18 | .config({ 19 | extends: [ 20 | 'plugin:@nx/angular', 21 | 'plugin:@angular-eslint/template/process-inline-templates', 22 | ], 23 | }) 24 | .map((config) => ({ 25 | ...config, 26 | files: ['**/*.ts'], 27 | rules: { 28 | ...config.rules, 29 | '@angular-eslint/directive-selector': [ 30 | 'error', 31 | { 32 | type: 'attribute', 33 | prefix: 'bui', 34 | style: 'camelCase', 35 | }, 36 | ], 37 | '@angular-eslint/component-selector': [ 38 | 'error', 39 | { 40 | type: 'element', 41 | prefix: 'bui', 42 | style: 'kebab-case', 43 | }, 44 | ], 45 | }, 46 | })), 47 | ...compat 48 | .config({ 49 | extends: ['plugin:@nx/angular-template'], 50 | }) 51 | .map((config) => ({ 52 | ...config, 53 | files: ['**/*.html'], 54 | rules: { 55 | ...config.rules, 56 | }, 57 | })), 58 | ]; 59 | -------------------------------------------------------------------------------- /logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/assets/logo-boardui-varianta2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boardui-angular", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "sourceRoot": "packages/boardui-angular/src", 5 | "prefix": "boardui", 6 | "projectType": "library", 7 | "tags": [], 8 | "targets": { 9 | "build": { 10 | "executor": "@nx/angular:package", 11 | "outputs": ["{workspaceRoot}/dist/{projectRoot}"], 12 | "options": { 13 | "project": "packages/boardui-angular/ng-package.json" 14 | }, 15 | "configurations": { 16 | "production": { 17 | "tsConfig": "packages/boardui-angular/tsconfig.lib.prod.json" 18 | }, 19 | "development": { 20 | "tsConfig": "packages/boardui-angular/tsconfig.lib.json" 21 | } 22 | }, 23 | "defaultConfiguration": "production", 24 | "dependsOn": [ 25 | { 26 | "dependencies": true, 27 | "target": "build" 28 | } 29 | ] 30 | }, 31 | "test": { 32 | "executor": "@nx/jest:jest", 33 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 34 | "options": { 35 | "jestConfig": "packages/boardui-angular/jest.config.ts", 36 | "passWithNoTests": true 37 | }, 38 | "configurations": { 39 | "ci": { 40 | "ci": true, 41 | "codeCoverage": true 42 | } 43 | } 44 | }, 45 | "lint": { 46 | "executor": "@nx/eslint:lint", 47 | "outputs": ["{options.outputFile}"], 48 | "options": { 49 | "lintFilePatterns": [ 50 | "packages/boardui-angular/**/*.ts", 51 | "packages/boardui-angular/**/*.html" 52 | ] 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/pad-renderer.ts: -------------------------------------------------------------------------------- 1 | import { RendererBase } from './renderer-base'; 2 | import { ElementType } from '../element-type'; 3 | import { Pad, StandardPrimitiveRef } from 'boardui-parser'; 4 | import { ElementIdProvider } from '../element-id-provider'; 5 | import { RenderProperties } from 'boardui-core'; 6 | import { ReusablesProvider } from '../reusables-provider'; 7 | 8 | export class PadRenderer extends RendererBase { 9 | private _cloneablePinAttribute = document.createAttribute(ElementType.PAD); 10 | constructor() { 11 | super('g'); 12 | } 13 | 14 | protected renderPart( 15 | part: Pad, 16 | partElement: SVGElement, 17 | reusablesProvider: ReusablesProvider, 18 | elementIdProvider: ElementIdProvider, 19 | renderProperties: RenderProperties, 20 | renderSubpart: (part: any, partElement: SVGElement) => void 21 | ): void { 22 | partElement.setAttributeNode( 23 | this._cloneablePinAttribute.cloneNode() as Attr 24 | ); 25 | partElement.setAttribute( 26 | 'id', 27 | elementIdProvider.getElementId(part).toString() 28 | ); 29 | 30 | const transformation = [`translate(${part.location.x},${part.location.y})`]; 31 | if (part.xform) { 32 | transformation.push(part.xform.getSVGTransformation()); 33 | } 34 | 35 | partElement.setAttribute('transform', transformation.join(' ')); 36 | 37 | const standardPrimitive = 38 | part.feature instanceof StandardPrimitiveRef 39 | ? reusablesProvider.getPrimitiveById(part.feature.id) 40 | : part.feature; 41 | if (!standardPrimitive) { 42 | throw new Error(`Pad ${part.padstackDefRef} has no graphic.`); 43 | } 44 | renderSubpart(standardPrimitive, partElement); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/layers/component-layer.ts: -------------------------------------------------------------------------------- 1 | import { RendererProvider } from '../renderer-provider'; 2 | import { Component, Layer, Step } from 'boardui-parser'; 3 | import { LayerBase } from './layer-base'; 4 | import { ElementType } from '../element-type'; 5 | import { RenderContext } from '../render-context'; 6 | 7 | export class ComponentLayer extends LayerBase { 8 | constructor(layer: Layer, step: Step, renderContext: RenderContext) { 9 | super(layer, step, renderContext, 'COMPONENT'); 10 | } 11 | 12 | private get components(): Component[] { 13 | return this._step.components.filter((x) => x.layerRef === this._layer.name); 14 | } 15 | 16 | render(): SVGElement { 17 | const layerElement: SVGElement = document.createElementNS( 18 | 'http://www.w3.org/2000/svg', 19 | 'g' 20 | ); 21 | //layerElement.setAttribute("id", this._renderContext.elementIdProvider.getElementId(this._layer).toString()); 22 | layerElement.setAttribute(ElementType.LAYER, this.name); 23 | layerElement.setAttribute('layerType', this._layerType); 24 | 25 | if (this._renderProperties?.invisible) { 26 | layerElement.setAttribute('visibility', 'hidden'); 27 | } 28 | 29 | if (this._renderProperties?.color) { 30 | const color = `rgb(${this._renderProperties.color.r},${this._renderProperties.color.g},${this._renderProperties.color.b})`; 31 | layerElement.setAttribute('fill', color); 32 | layerElement.setAttribute('stroke', color); 33 | } 34 | 35 | for (const component of this.components) { 36 | const renderer = RendererProvider.getRenderer(component); 37 | renderer.render(component, layerElement, this._renderContext); 38 | } 39 | 40 | return (this._layerElement = layerElement); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/circle-renderer.ts: -------------------------------------------------------------------------------- 1 | import { Circle, FillDescRef, LineDescRef } from 'boardui-parser'; 2 | import '../extensions/fill-desc.extensions'; 3 | import { RendererBase } from './renderer-base'; 4 | import { ReusablesProvider } from '../reusables-provider'; 5 | import { getFillDescSVGAttributes } from '../extensions/fill-desc.extensions'; 6 | import { getLineDescSVGAttributes } from '../extensions/line-desc.extensions'; 7 | 8 | export class CircleRenderer extends RendererBase { 9 | constructor() { 10 | super('circle'); 11 | } 12 | 13 | protected renderPart( 14 | part: Circle, 15 | partElement: SVGElement, 16 | reusablesProvider: ReusablesProvider 17 | ): void { 18 | partElement.setAttribute('r', (part.diameter / 2).toString()); 19 | 20 | if (part.fillDesc) { 21 | const fillDesc = 22 | part.fillDesc instanceof FillDescRef 23 | ? reusablesProvider.getFillDescById(part.fillDesc.id) 24 | : part.fillDesc; 25 | const fillDescAttributes = getFillDescSVGAttributes( 26 | fillDesc, 27 | reusablesProvider 28 | ); 29 | for (const fillDescAttribute of fillDescAttributes) { 30 | partElement.setAttribute(...fillDescAttribute); 31 | } 32 | } 33 | 34 | if (part.lineDesc) { 35 | const lineDesc = 36 | part.lineDesc instanceof LineDescRef 37 | ? reusablesProvider.getLineDescById(part.lineDesc.id) 38 | : part.lineDesc; 39 | const lineDescAttributes = getLineDescSVGAttributes(lineDesc); 40 | for (const lineDescAttribute of lineDescAttributes) { 41 | partElement.setAttribute(...lineDescAttribute); 42 | } 43 | } else { 44 | partElement.setAttribute('stroke-width', '0'); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/layers/profile-layer.ts: -------------------------------------------------------------------------------- 1 | import { Layer, Step } from 'boardui-parser'; 2 | import { ElementType } from '../element-type'; 3 | import { RenderContext } from '../render-context'; 4 | import { RendererProvider } from '../renderer-provider'; 5 | import { LayerBase } from './layer-base'; 6 | 7 | export class ProfileLayer extends LayerBase { 8 | constructor(layer: Layer, step: Step, renderContext: RenderContext) { 9 | super(layer, step, renderContext, 'PROFILE'); 10 | } 11 | 12 | render(): SVGElement { 13 | const profileLayerElement: SVGElement = document.createElementNS( 14 | 'http://www.w3.org/2000/svg', 15 | 'g' 16 | ); 17 | //profileLayerElement.setAttribute("id", this._renderContext.elementIdProvider.getElementId(this._layer).toString()); 18 | profileLayerElement.setAttribute(ElementType.LAYER, this._layerType); 19 | profileLayerElement.setAttribute('layerType', this._layerType); 20 | 21 | if (this._renderProperties?.invisible) { 22 | profileLayerElement.setAttribute('visibility', 'hidden'); 23 | } 24 | 25 | if (this._renderProperties?.color) { 26 | const color = `rgb(${this._renderProperties.color.r},${this._renderProperties.color.g},${this._renderProperties.color.b})`; 27 | profileLayerElement.setAttribute('fill', color); 28 | profileLayerElement.setAttribute('stroke', color); 29 | } else { 30 | profileLayerElement.setAttribute('fill', 'none'); 31 | profileLayerElement.setAttribute('stroke', 'none'); 32 | } 33 | 34 | const profileRenderer = RendererProvider.getProfileRenderer( 35 | this._step.profile! 36 | ); 37 | profileRenderer.render( 38 | this._step.profile!, 39 | profileLayerElement, 40 | this._renderContext 41 | ); 42 | 43 | return profileLayerElement; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /boardui/eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { FlatCompat } from '@eslint/eslintrc'; 2 | import { dirname } from 'path'; 3 | import { fileURLToPath } from 'url'; 4 | import js from '@eslint/js'; 5 | import nxEslintPlugin from '@nx/eslint-plugin'; 6 | 7 | const compat = new FlatCompat({ 8 | baseDirectory: dirname(fileURLToPath(import.meta.url)), 9 | recommendedConfig: js.configs.recommended, 10 | }); 11 | 12 | export default [ 13 | { 14 | ignores: ['**/dist'], 15 | }, 16 | { plugins: { '@nx': nxEslintPlugin } }, 17 | { 18 | files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'], 19 | rules: { 20 | '@nx/enforce-module-boundaries': [ 21 | 'error', 22 | { 23 | enforceBuildableLibDependency: true, 24 | allow: [], 25 | depConstraints: [ 26 | { 27 | sourceTag: '*', 28 | onlyDependOnLibsWithTags: ['*'], 29 | }, 30 | ], 31 | }, 32 | ], 33 | }, 34 | }, 35 | ...compat 36 | .config({ 37 | extends: ['plugin:@nx/typescript'], 38 | }) 39 | .map((config) => ({ 40 | ...config, 41 | files: ['**/*.ts', '**/*.tsx', '**/*.cts', '**/*.mts'], 42 | rules: { 43 | ...config.rules, 44 | }, 45 | })), 46 | ...compat 47 | .config({ 48 | extends: ['plugin:@nx/javascript'], 49 | }) 50 | .map((config) => ({ 51 | ...config, 52 | files: ['**/*.js', '**/*.jsx', '**/*.cjs', '**/*.mjs'], 53 | rules: { 54 | ...config.rules, 55 | }, 56 | })), 57 | ...compat 58 | .config({ 59 | env: { 60 | jest: true, 61 | }, 62 | }) 63 | .map((config) => ({ 64 | ...config, 65 | files: ['**/*.spec.ts', '**/*.spec.tsx', '**/*.spec.js', '**/*.spec.jsx'], 66 | rules: { 67 | ...config.rules, 68 | }, 69 | })), 70 | { 71 | ignores: ['node_modules\r'], 72 | }, 73 | ]; 74 | -------------------------------------------------------------------------------- /boardui/nx.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/nx/schemas/nx-schema.json", 3 | "affected": { 4 | "defaultBase": "master" 5 | }, 6 | "tasksRunnerOptions": { 7 | "default": { 8 | "runner": "nx/tasks-runners/default", 9 | "options": { 10 | "cacheableOperations": ["build", "lint", "test", "e2e"] 11 | } 12 | } 13 | }, 14 | "targetDefaults": { 15 | "build": { 16 | "dependsOn": ["^build"], 17 | "inputs": ["production", "^production"] 18 | }, 19 | "lint": { 20 | "inputs": [ 21 | "default", 22 | "{workspaceRoot}/.eslintrc.json", 23 | "{workspaceRoot}/.eslintignore", 24 | "{workspaceRoot}/eslint.config.mjs" 25 | ] 26 | }, 27 | "test": { 28 | "inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"] 29 | }, 30 | "e2e": { 31 | "inputs": ["default", "^production"] 32 | } 33 | }, 34 | "namedInputs": { 35 | "default": ["{projectRoot}/**/*", "sharedGlobals"], 36 | "production": [ 37 | "default", 38 | "!{projectRoot}/.eslintrc.json", 39 | "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)?(.snap)", 40 | "!{projectRoot}/tsconfig.spec.json", 41 | "!{projectRoot}/jest.config.[jt]s", 42 | "!{projectRoot}/eslint.config.mjs" 43 | ], 44 | "sharedGlobals": [] 45 | }, 46 | "workspaceLayout": { 47 | "appsDir": "packages", 48 | "libsDir": "packages" 49 | }, 50 | "generators": { 51 | "@nx/angular:application": { 52 | "style": "css", 53 | "linter": "eslint", 54 | "unitTestRunner": "jest", 55 | "e2eTestRunner": "cypress" 56 | }, 57 | "@nx/angular:library": { 58 | "linter": "eslint", 59 | "unitTestRunner": "jest" 60 | }, 61 | "@nx/angular:component": { 62 | "style": "css" 63 | } 64 | }, 65 | "pluginsConfig": { 66 | "@nx/js": { 67 | "analyzeSourceFiles": true 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/app/app.component.spec.ts: -------------------------------------------------------------------------------- 1 | import { HttpClientModule } from '@angular/common/http'; 2 | import { TestBed } from '@angular/core/testing'; 3 | import { AppComponent } from './app.component'; 4 | import { BoardUIModule } from 'boardui-angular'; 5 | import { MatButtonModule } from '@angular/material/button'; 6 | import { MatIconModule } from '@angular/material/icon'; 7 | import { MatFormFieldModule } from '@angular/material/form-field'; 8 | import { MatTableModule } from '@angular/material/table'; 9 | import { MatTabsModule } from '@angular/material/tabs'; 10 | import { MatExpansionModule } from '@angular/material/expansion'; 11 | import { NoopAnimationsModule } from '@angular/platform-browser/animations'; 12 | import { MatDialogModule } from '@angular/material/dialog'; 13 | import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; 14 | 15 | describe('AppComponent', () => { 16 | beforeEach(async () => { 17 | await TestBed.configureTestingModule({ 18 | imports: [ 19 | AppComponent, 20 | HttpClientModule, 21 | NoopAnimationsModule, 22 | BoardUIModule, 23 | MatButtonModule, 24 | MatIconModule, 25 | MatFormFieldModule, 26 | MatTableModule, 27 | MatTabsModule, 28 | MatExpansionModule, 29 | MatDialogModule, 30 | MatProgressSpinnerModule, 31 | ], 32 | }).compileComponents(); 33 | }); 34 | 35 | it('should create the app', () => { 36 | const fixture = TestBed.createComponent(AppComponent); 37 | const app = fixture.componentInstance; 38 | expect(app).toBeTruthy(); 39 | }); 40 | 41 | it('should render title', () => { 42 | const fixture = TestBed.createComponent(AppComponent); 43 | fixture.detectChanges(); 44 | const compiled = fixture.nativeElement as HTMLElement; 45 | expect(compiled.querySelector('h1')?.textContent).toContain('BoardUI demo'); 46 | }); 47 | }); 48 | -------------------------------------------------------------------------------- /boardui/packages/boardui-angular/src/lib/template/template.directive.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Directive, 3 | TemplateRef, 4 | ViewContainerRef, 5 | Input, 6 | Renderer2, 7 | NgZone, 8 | EmbeddedViewRef, 9 | OnDestroy, 10 | OnInit, 11 | } from '@angular/core'; 12 | import { Template } from './template'; 13 | import { TemplateCollection } from './template-collection'; 14 | 15 | @Directive({ 16 | selector: '[buiTemplate]', 17 | standalone: true, 18 | }) 19 | export class TemplateDirective implements Template, OnInit, OnDestroy { 20 | @Input() 21 | set buiTemplateOf(value: any) { 22 | this.name = value; 23 | } 24 | name!: string; 25 | 26 | constructor( 27 | private _templateRef: TemplateRef, 28 | private _viewContainerRef: ViewContainerRef, 29 | private _renderer: Renderer2, 30 | private _zone: NgZone, 31 | private _templateCollection: TemplateCollection 32 | ) {} 33 | 34 | ngOnInit(): void { 35 | this._templateCollection.add(this); 36 | } 37 | 38 | ngOnDestroy(): void { 39 | this._templateCollection.remove(this.name); 40 | } 41 | 42 | private renderTemplate(target: any, data: any): EmbeddedViewRef { 43 | const childView = this._viewContainerRef.createEmbeddedView( 44 | this._templateRef, 45 | { 46 | $implicit: data, 47 | } 48 | ); 49 | 50 | childView.rootNodes.forEach((element) => { 51 | this._renderer.appendChild(target, element); 52 | }); 53 | 54 | return childView; 55 | } 56 | 57 | /** Renders template 58 | * @param target Target element to render template into 59 | * @param data Data passed to template as $implicit 60 | */ 61 | render(target: any, data: any): any[] { 62 | const childView = this._zone.isStable 63 | ? this._zone.run(() => this.renderTemplate(target, data)) 64 | : this.renderTemplate(target, data); 65 | childView.detectChanges(); 66 | return childView.rootNodes; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/rect-center-renderer.ts: -------------------------------------------------------------------------------- 1 | import { RectCenter, FillDescRef, LineDescRef } from 'boardui-parser'; 2 | import { RendererBase } from './renderer-base'; 3 | import { ReusablesProvider } from '../reusables-provider'; 4 | import { getFillDescSVGAttributes } from '../extensions/fill-desc.extensions'; 5 | import { getLineDescSVGAttributes } from '../extensions/line-desc.extensions'; 6 | 7 | export class RectCenterRenderer extends RendererBase { 8 | constructor() { 9 | super('rect'); 10 | } 11 | 12 | protected renderPart( 13 | part: RectCenter, 14 | partElement: SVGElement, 15 | reusablesProvider: ReusablesProvider 16 | ): void { 17 | partElement.setAttribute('width', part.width.toString()); 18 | partElement.setAttribute('height', part.height.toString()); 19 | partElement.setAttribute('x', (-part.width / 2).toString()); 20 | partElement.setAttribute('y', (-part.height / 2).toString()); 21 | 22 | if (part.fillDesc) { 23 | const fillDesc = 24 | part.fillDesc instanceof FillDescRef 25 | ? reusablesProvider.getFillDescById(part.fillDesc.id) 26 | : part.fillDesc; 27 | const fillDescAttributes = getFillDescSVGAttributes( 28 | fillDesc, 29 | reusablesProvider 30 | ); 31 | for (const fillDescAttribute of fillDescAttributes) { 32 | partElement.setAttribute(...fillDescAttribute); 33 | } 34 | } 35 | 36 | if (part.lineDesc) { 37 | const lineDesc = 38 | part.lineDesc instanceof LineDescRef 39 | ? reusablesProvider.getLineDescById(part.lineDesc.id) 40 | : part.lineDesc; 41 | const lineDescAttributes = getLineDescSVGAttributes(lineDesc); 42 | for (const lineDescAttribute of lineDescAttributes) { 43 | partElement.setAttribute(...lineDescAttribute); 44 | } 45 | } else { 46 | partElement.setAttribute('stroke-width', '0'); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/test/xml-to-js-parser.test.ts: -------------------------------------------------------------------------------- 1 | import { XMLtoJSMapping } from '../src/lib/xml-to-js-mapping'; 2 | import { XmlToJsParser } from '../src/lib/xml-to-js-parser'; 3 | import { Attribute, SaxEventType, Tag } from 'sax-wasm'; 4 | 5 | describe('XMLToJSParser processAttribute method test', () => { 6 | it('should assign attribute', async () => { 7 | const stack = [ 8 | { 9 | child: null, 10 | }, 11 | ]; 12 | 13 | XmlToJsParser.processOpenTag( 14 | { 15 | name: 'child', 16 | } as Tag, 17 | [], 18 | new Map(), 19 | ); 20 | XmlToJsParser.processAttribute( 21 | { name: { value: 'child' }, value: { value: 'test' } } as Attribute, 22 | stack, 23 | ); 24 | 25 | expect(stack.at(0)).toEqual({ 26 | child: 'test', 27 | }); 28 | }); 29 | }); 30 | 31 | const processOpenTagFuncHashSet = new Map( 32 | [['child', Object.prototype, ['child']] as XMLtoJSMapping].map((x) => 33 | XmlToJsParser.getMappingFunc(...x), 34 | ), 35 | ); 36 | 37 | describe('XMLToJSParser processOpenTag method test', () => { 38 | it('should assign child', async () => { 39 | const stack = [ 40 | { 41 | child: null, 42 | }, 43 | ]; 44 | 45 | XmlToJsParser.processOpenTag( 46 | { 47 | name: 'child', 48 | } as Tag, 49 | stack, 50 | processOpenTagFuncHashSet, 51 | ); 52 | 53 | expect(stack.at(0)).toEqual({ 54 | child: {}, 55 | }); 56 | }); 57 | }); 58 | 59 | describe('XMLToJSParser processCloseTag method test', () => { 60 | it('should pop item from stack', async () => { 61 | const stack = [ 62 | { 63 | child: null, 64 | }, 65 | ]; 66 | 67 | XmlToJsParser.processEvent( 68 | SaxEventType.CloseTag, 69 | { 70 | name: 'child', 71 | }, 72 | stack, 73 | processOpenTagFuncHashSet, 74 | ); 75 | 76 | expect(stack.length).toEqual(0); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/assets/logo-boardui.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /boardui/.nx/cache/run.json: -------------------------------------------------------------------------------- 1 | { 2 | "run": { 3 | "command": "nx run-many --target=test", 4 | "startTime": "2025-03-05T19:25:05.708Z", 5 | "endTime": "2025-03-05T19:25:12.433Z", 6 | "inner": false 7 | }, 8 | "tasks": [ 9 | { 10 | "taskId": "boardui-core:test", 11 | "target": "test", 12 | "projectName": "boardui-core", 13 | "hash": "12647603704475390413", 14 | "startTime": "2025-03-05T19:25:05.717Z", 15 | "endTime": "2025-03-05T19:25:05.730Z", 16 | "params": "", 17 | "cacheStatus": "local-cache-hit", 18 | "status": 0 19 | }, 20 | { 21 | "taskId": "boardui-parser:test", 22 | "target": "test", 23 | "projectName": "boardui-parser", 24 | "hash": "17238835703048271077", 25 | "startTime": "2025-03-05T19:25:05.717Z", 26 | "endTime": "2025-03-05T19:25:08.843Z", 27 | "params": "", 28 | "cacheStatus": "cache-miss", 29 | "status": 0 30 | }, 31 | { 32 | "taskId": "boardui-angular:test", 33 | "target": "test", 34 | "projectName": "boardui-angular", 35 | "hash": "6409231760456266494", 36 | "startTime": "2025-03-05T19:25:05.733Z", 37 | "endTime": "2025-03-05T19:25:09.508Z", 38 | "params": "", 39 | "cacheStatus": "cache-miss", 40 | "status": 0 41 | }, 42 | { 43 | "taskId": "boardui-renderer:test", 44 | "target": "test", 45 | "projectName": "boardui-renderer", 46 | "hash": "17429145055230759622", 47 | "startTime": "2025-03-05T19:25:05.717Z", 48 | "endTime": "2025-03-05T19:25:10.290Z", 49 | "params": "", 50 | "cacheStatus": "cache-miss", 51 | "status": 0 52 | }, 53 | { 54 | "taskId": "boardui-demo:test", 55 | "target": "test", 56 | "projectName": "boardui-demo", 57 | "hash": "14281180506031957548", 58 | "startTime": "2025-03-05T19:25:08.929Z", 59 | "endTime": "2025-03-05T19:25:12.429Z", 60 | "params": "", 61 | "cacheStatus": "cache-miss", 62 | "status": 0 63 | } 64 | ] 65 | } -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/oval-renderer.ts: -------------------------------------------------------------------------------- 1 | import { Oval, FillDescRef, LineDescRef } from 'boardui-parser'; 2 | import '../extensions/fill-desc.extensions'; 3 | import { RendererBase } from './renderer-base'; 4 | import { ReusablesProvider } from '../reusables-provider'; 5 | import { getFillDescSVGAttributes } from '../extensions/fill-desc.extensions'; 6 | import { getLineDescSVGAttributes } from '../extensions/line-desc.extensions'; 7 | 8 | export class OvalRenderer extends RendererBase { 9 | constructor() { 10 | super('rect'); 11 | } 12 | 13 | protected renderPart( 14 | part: Oval, 15 | partElement: SVGElement, 16 | reusablesProvider: ReusablesProvider 17 | ): void { 18 | partElement.setAttribute('width', part.width.toString()); 19 | partElement.setAttribute('height', part.height.toString()); 20 | partElement.setAttribute('x', (-part.width / 2).toString()); 21 | partElement.setAttribute('y', (-part.height / 2).toString()); 22 | partElement.setAttribute('ry', (part.height / 2).toString()); 23 | 24 | if (part.fillDesc) { 25 | const fillDesc = 26 | part.fillDesc instanceof FillDescRef 27 | ? reusablesProvider.getFillDescById(part.fillDesc.id) 28 | : part.fillDesc; 29 | const fillDescAttributes = getFillDescSVGAttributes( 30 | fillDesc, 31 | reusablesProvider 32 | ); 33 | for (const fillDescAttribute of fillDescAttributes) { 34 | partElement.setAttribute(...fillDescAttribute); 35 | } 36 | } 37 | 38 | if (part.lineDesc) { 39 | const lineDesc = 40 | part.lineDesc instanceof LineDescRef 41 | ? reusablesProvider.getLineDescById(part.lineDesc.id) 42 | : part.lineDesc; 43 | const lineDescAttributes = getLineDescSVGAttributes(lineDesc); 44 | for (const lineDescAttribute of lineDescAttributes) { 45 | partElement.setAttribute(...lineDescAttribute); 46 | } 47 | } else { 48 | partElement.setAttribute('stroke-width', '0'); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderer-provider.ts: -------------------------------------------------------------------------------- 1 | import { Renderer } from './renderer'; 2 | import { 3 | Circle, 4 | Component, 5 | Contour, 6 | Hole, 7 | Line, 8 | Oval, 9 | Pad, 10 | Pin, 11 | Polyline, 12 | RectCenter, 13 | StandardPrimitive, 14 | } from 'boardui-parser'; 15 | import { 16 | ContourRenderer, 17 | PolylineRenderer, 18 | CircleRenderer, 19 | RectCenterRenderer, 20 | PinRenderer, 21 | ComponentRenderer, 22 | HoleRenderer, 23 | ProfileContourRenderer, 24 | OvalRenderer, 25 | LineRenderer, 26 | } from './renderers'; 27 | import { PadRenderer } from './renderers/pad-renderer'; 28 | 29 | export class RendererProvider { 30 | // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type 31 | private static _renderers = new Map>([ 32 | [Contour.prototype.constructor, new ContourRenderer()], 33 | [Polyline.prototype.constructor, new PolylineRenderer()], 34 | [Circle.prototype.constructor, new CircleRenderer()], 35 | [RectCenter.prototype.constructor, new RectCenterRenderer()], 36 | [Pin.prototype.constructor, new PinRenderer()], 37 | [Component.prototype.constructor, new ComponentRenderer()], 38 | [Hole.prototype.constructor, new HoleRenderer()], 39 | [Pad.prototype.constructor, new PadRenderer()], 40 | [Oval.prototype.constructor, new OvalRenderer()], 41 | [Line.prototype.constructor, new LineRenderer()], 42 | ]); 43 | 44 | private static _profileContourRenderer = new ProfileContourRenderer(); 45 | 46 | static getRenderer(primitive: any): Renderer { 47 | const renderer = RendererProvider._renderers.get(primitive.constructor); 48 | if (!renderer) { 49 | throw new Error(`Renderer for ${primitive} not found.`); 50 | } 51 | return renderer; 52 | } 53 | 54 | static getProfileRenderer(primitive: StandardPrimitive): Renderer { 55 | if (primitive instanceof Contour) { 56 | return RendererProvider._profileContourRenderer; 57 | } 58 | 59 | throw new Error('Not implemented'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/test/line-renderer.test.ts: -------------------------------------------------------------------------------- 1 | import { FillDesc, Line, LineDesc } from 'boardui-parser/src'; 2 | import { ReusablesProviderMock } from './reusables-provider.mock'; 3 | import { ElementIdProviderMock } from './element-id-provider.mock'; 4 | import { RendererProvider } from '../src/lib/renderer-provider'; 5 | import { RenderPropertiesMock } from './render-properties.mock'; 6 | import '@testing-library/jest-dom'; 7 | import { LineRenderer } from '../src/lib/renderers'; 8 | 9 | let lineRenderer: LineRenderer; 10 | let targetElement: SVGElement; 11 | const testFillDesc: FillDesc = new FillDesc(); 12 | const testLineDesc: LineDesc = { 13 | lineEnd: 'ROUND', 14 | lineWidth: '1', 15 | lineProperty: 'SOLID', 16 | }; 17 | 18 | function render(line: Line): SVGElement { 19 | lineRenderer.render(line, targetElement, { 20 | reusablesProvider: ReusablesProviderMock, 21 | elementIdProvider: ElementIdProviderMock, 22 | renderProperties: RenderPropertiesMock, 23 | getRenderer: RendererProvider.getRenderer, 24 | }); 25 | 26 | return targetElement.childNodes[0] as SVGElement; 27 | } 28 | 29 | beforeEach(() => { 30 | lineRenderer = new LineRenderer(); 31 | targetElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 32 | testFillDesc.color = { 33 | r: 1, 34 | g: 2, 35 | b: 3, 36 | }; 37 | }); 38 | 39 | describe('LineRenderer render test', () => { 40 | it('should render basic line', async () => { 41 | const line: Line = { 42 | startX: -1, 43 | startY: -2, 44 | endX: 1, 45 | endY: 2, 46 | lineDesc: testLineDesc, 47 | }; 48 | 49 | const result = render(line); 50 | 51 | expect(result.tagName).toBe('line'); 52 | expect(result).toHaveAttribute('x1', '-1'); 53 | expect(result).toHaveAttribute('x2', '1'); 54 | expect(result).toHaveAttribute('y1', '-2'); 55 | expect(result).toHaveAttribute('y2', '2'); 56 | expect(result).toHaveAttribute('stroke-width', '1'); 57 | expect(result).toHaveAttribute('stroke-linecap', 'round'); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /boardui/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boardui", 3 | "version": "1.0.0", 4 | "license": "MIT", 5 | "scripts": {}, 6 | "private": true, 7 | "devDependencies": { 8 | "@angular-devkit/build-angular": "^19.0.0", 9 | "@angular-devkit/core": "^19.0.0", 10 | "@angular-devkit/schematics": "^19.0.0", 11 | "@angular-eslint/eslint-plugin": "^19.0.0", 12 | "@angular-eslint/eslint-plugin-template": "^19.0.0", 13 | "@angular-eslint/template-parser": "^19.0.0", 14 | "@angular/cli": "^19.0.0", 15 | "@angular/compiler-cli": "^19.0.0", 16 | "@angular/language-service": "^19.0.0", 17 | "@eslint/eslintrc": "^2.1.1", 18 | "@eslint/js": "~8.57.0", 19 | "@nx/angular": "20.4.6", 20 | "@nx/cypress": "20.4.6", 21 | "@nx/eslint": "20.4.6", 22 | "@nx/eslint-plugin": "20.4.6", 23 | "@nx/jest": "20.4.6", 24 | "@nx/js": "20.4.6", 25 | "@nx/web": "20.4.6", 26 | "@nx/workspace": "20.4.6", 27 | "@schematics/angular": "^19.0.0", 28 | "@testing-library/jest-dom": "^6.0.0", 29 | "@types/jest": "^29.4.0", 30 | "@types/node": "^22.0.0", 31 | "cypress": "^13.0.0", 32 | "eslint": "^9.8.0", 33 | "eslint-config-prettier": "^9.0.0", 34 | "eslint-plugin-cypress": "^4.0.0", 35 | "jest": "^29.4.1", 36 | "jest-environment-jsdom": "^29.4.1", 37 | "jest-fixed-jsdom": "^0.0.9", 38 | "jest-preset-angular": "^14.5.0", 39 | "ng-packagr": "^19.0.0", 40 | "nx": "20.4.6", 41 | "postcss": "^8.0.0", 42 | "postcss-import": "^16.0.0", 43 | "postcss-preset-env": "^10.0.0", 44 | "postcss-url": "^10.0.0", 45 | "prettier": "^3.0.0", 46 | "ts-jest": "^29.1.0", 47 | "ts-node": "10.9.1", 48 | "typescript": "~5.5.0", 49 | "typescript-eslint": "^8.19.0" 50 | }, 51 | "workspaces": [ 52 | "packages/*" 53 | ], 54 | "dependencies": { 55 | "@angular/animations": "^19.0.0", 56 | "@angular/cdk": "^19.0.0", 57 | "@angular/common": "^19.0.0", 58 | "@angular/compiler": "^19.0.0", 59 | "@angular/core": "^19.0.0", 60 | "@angular/forms": "^19.0.0", 61 | "@angular/material": "^19.0.0", 62 | "@angular/platform-browser": "^19.0.0", 63 | "@angular/platform-browser-dynamic": "^19.0.0", 64 | "@angular/router": "^19.0.0", 65 | "rxjs": "~7.8.0", 66 | "sax-wasm": "^2.0.0", 67 | "tslib": "^2.3.0", 68 | "zone.js": "~0.15.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/ipc/index.ts: -------------------------------------------------------------------------------- 1 | export * from './assembly-drawing'; 2 | export * from './cad-data'; 3 | export * from './circle'; 4 | export * from './color'; 5 | export * from './color-group'; 6 | export * from './color-ref'; 7 | export * from './component'; 8 | export * from './content'; 9 | export * from './contour'; 10 | export * from './dictionary'; 11 | export * from './dictionary-entry'; 12 | export * from './ecad'; 13 | export * from './features'; 14 | export * from './fill-desc'; 15 | export * from './fill-desc-group'; 16 | export * from './fill-desc-ref'; 17 | export * from './fill-property'; 18 | export * from './hole'; 19 | export * from './hole-type'; 20 | export * from './ipc2581'; 21 | export * from './layer'; 22 | export * from './layer-feature'; 23 | export * from './layer-function'; 24 | export * from './layer-ref'; 25 | export * from './line-desc'; 26 | export * from './line-desc-group'; 27 | export * from './line-desc-ref'; 28 | export * from './line-end'; 29 | export * from './line-property'; 30 | export * from './location'; 31 | export * from './marking'; 32 | export * from './mount'; 33 | export * from './nonstandard-attribute'; 34 | export * from './outline'; 35 | export * from './package'; 36 | export * from './pad'; 37 | export * from './pin'; 38 | export * from './pin-one-orientation'; 39 | export * from './pin-ref'; 40 | export * from './plating-status'; 41 | export * from './polarity'; 42 | export * from './poly-begin'; 43 | export * from './poly-step-curve'; 44 | export * from './poly-step-segment'; 45 | export * from './polygon'; 46 | export * from './polyline'; 47 | export * from './rect-center'; 48 | export * from './set'; 49 | export * from './side'; 50 | export * from './span'; 51 | export * from './standard-primitive'; 52 | export * from './standard-primitive-group'; 53 | export * from './standard-primitive-ref'; 54 | export * from './step'; 55 | export * from './step-ref'; 56 | export * from './x-form'; 57 | export * from './function-mode'; 58 | export * from './bom'; 59 | export * from './bom-header'; 60 | export * from './bom-item'; 61 | export * from './bom-ref'; 62 | export * from './characteristics'; 63 | export * from './enumerated'; 64 | export * from './measured'; 65 | export * from './ranged'; 66 | export * from './ref-des'; 67 | export * from './silk-screen'; 68 | export * from './textual'; 69 | export * from './oval'; 70 | export * from './line'; 71 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/test/component-renderer.test.ts: -------------------------------------------------------------------------------- 1 | import { Component, FillDesc, PolyStepSegment } from 'boardui-parser/src'; 2 | import { ReusablesProviderMock } from './reusables-provider.mock'; 3 | import { ElementIdProviderMock } from './element-id-provider.mock'; 4 | import { RendererProvider } from '../src/lib/renderer-provider'; 5 | import { RenderPropertiesMock } from './render-properties.mock'; 6 | import '@testing-library/jest-dom'; 7 | import { ComponentRenderer } from '../src/lib/renderers'; 8 | 9 | let componentRenderer: ComponentRenderer; 10 | let targetElement: SVGElement; 11 | const testFillDesc: FillDesc = new FillDesc(); 12 | 13 | function render(component: Component): SVGElement { 14 | componentRenderer.render(component, targetElement, { 15 | reusablesProvider: ReusablesProviderMock, 16 | elementIdProvider: ElementIdProviderMock, 17 | renderProperties: RenderPropertiesMock, 18 | getRenderer: RendererProvider.getRenderer, 19 | }); 20 | 21 | return targetElement.childNodes[0] as SVGElement; 22 | } 23 | 24 | function pathSegment(x: number, y: number): PolyStepSegment { 25 | const segment = new PolyStepSegment(); 26 | segment.x = x; 27 | segment.y = y; 28 | 29 | return segment; 30 | } 31 | 32 | beforeEach(() => { 33 | componentRenderer = new ComponentRenderer(); 34 | targetElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 35 | testFillDesc.color = { 36 | r: 1, 37 | g: 2, 38 | b: 3, 39 | }; 40 | }); 41 | 42 | describe('ComponentRenderer render test', () => { 43 | it('should render component', async () => { 44 | const component: Component = { 45 | refDes: null, 46 | matDes: null, 47 | packageRef: 'testPackage', 48 | part: 'testPart', 49 | layerRef: 'TOP', 50 | layerRefTopside: null, 51 | mountType: 'SMT', 52 | modelRef: null, 53 | weight: null, 54 | height: null, 55 | standoff: null, 56 | location: { 57 | x: 10, 58 | y: 15, 59 | }, 60 | xform: null, 61 | nonstandardAttributes: [], 62 | }; 63 | 64 | const result = render(component); 65 | 66 | expect(result.tagName).toBe('g'); 67 | expect(result).toHaveAttribute('component'); 68 | 69 | const outline = Array.from(result.children).find( 70 | (x) => x.tagName === 'path', 71 | ) as SVGElement; 72 | expect(outline).toBeTruthy(); 73 | expect(outline.tagName).toBe('path'); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/test/circle-renderer.test.ts: -------------------------------------------------------------------------------- 1 | import { CircleRenderer } from '../src/lib/renderers/circle-renderer'; 2 | import { Circle, FillDesc, LineDesc } from 'boardui-parser/src'; 3 | import { ReusablesProviderMock } from './reusables-provider.mock'; 4 | import { ElementIdProviderMock } from './element-id-provider.mock'; 5 | import { RendererProvider } from '../src/lib/renderer-provider'; 6 | import { RenderPropertiesMock } from './render-properties.mock'; 7 | import '@testing-library/jest-dom'; 8 | 9 | let circleRenderer: CircleRenderer; 10 | let targetElement: SVGElement; 11 | const testFillDesc: FillDesc = new FillDesc(); 12 | const testLineDesc: LineDesc = { 13 | lineEnd: 'ROUND', 14 | lineWidth: '1', 15 | lineProperty: 'SOLID', 16 | }; 17 | 18 | function render(circle: Circle): SVGElement { 19 | circleRenderer.render(circle, targetElement, { 20 | reusablesProvider: ReusablesProviderMock, 21 | elementIdProvider: ElementIdProviderMock, 22 | renderProperties: RenderPropertiesMock, 23 | getRenderer: RendererProvider.getRenderer, 24 | }); 25 | 26 | return targetElement.childNodes[0] as SVGElement; 27 | } 28 | 29 | beforeEach(() => { 30 | circleRenderer = new CircleRenderer(); 31 | targetElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 32 | testFillDesc.color = { 33 | r: 1, 34 | g: 2, 35 | b: 3, 36 | }; 37 | }); 38 | 39 | describe('CircleRenderer render test', () => { 40 | it('should render basic circle', async () => { 41 | const circle: Circle = { 42 | diameter: 2, 43 | lineDesc: null, 44 | fillDesc: null, 45 | xform: null, 46 | }; 47 | 48 | const result = render(circle); 49 | 50 | expect(result.tagName).toBe('circle'); 51 | expect(result).toHaveAttribute('r', '1'); 52 | }); 53 | 54 | it('should render circle with fill', async () => { 55 | const circle: Circle = { 56 | diameter: 2, 57 | lineDesc: null, 58 | fillDesc: testFillDesc, 59 | xform: null, 60 | }; 61 | 62 | const result = render(circle); 63 | 64 | expect(result).toHaveAttribute('fill', 'rgb(1,2,3)'); 65 | }); 66 | 67 | it('should render circle with stroke', async () => { 68 | const circle: Circle = { 69 | diameter: 2, 70 | lineDesc: testLineDesc, 71 | fillDesc: null, 72 | xform: null, 73 | }; 74 | 75 | const result = render(circle); 76 | 77 | expect(result).toHaveAttribute('stroke-width', '1'); 78 | expect(result).toHaveAttribute('stroke-linecap', 'round'); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/test/contour-renderer.test.ts: -------------------------------------------------------------------------------- 1 | import { Contour, FillDesc, PolyStepSegment } from 'boardui-parser/src'; 2 | import { ReusablesProviderMock } from './reusables-provider.mock'; 3 | import { ElementIdProviderMock } from './element-id-provider.mock'; 4 | import { RendererProvider } from '../src/lib/renderer-provider'; 5 | import { RenderPropertiesMock } from './render-properties.mock'; 6 | import '@testing-library/jest-dom'; 7 | import { ContourRenderer } from '../src/lib/renderers'; 8 | 9 | let contourRenderer: ContourRenderer; 10 | let targetElement: SVGElement; 11 | const testFillDesc: FillDesc = new FillDesc(); 12 | 13 | function render(contour: Contour): SVGElement { 14 | contourRenderer.render(contour, targetElement, { 15 | reusablesProvider: ReusablesProviderMock, 16 | elementIdProvider: ElementIdProviderMock, 17 | renderProperties: RenderPropertiesMock, 18 | getRenderer: RendererProvider.getRenderer, 19 | }); 20 | 21 | return targetElement.childNodes[0] as SVGElement; 22 | } 23 | 24 | function pathSegment(x: number, y: number): PolyStepSegment { 25 | const segment = new PolyStepSegment(); 26 | segment.x = x; 27 | segment.y = y; 28 | 29 | return segment; 30 | } 31 | 32 | beforeEach(() => { 33 | contourRenderer = new ContourRenderer(); 34 | targetElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 35 | testFillDesc.color = { 36 | r: 1, 37 | g: 2, 38 | b: 3, 39 | }; 40 | }); 41 | 42 | describe('ContourRenderer render test', () => { 43 | it('should render basic contour', async () => { 44 | const contour: Contour = { 45 | polygon: { 46 | polyBegin: { 47 | x: 10, 48 | y: 15, 49 | }, 50 | polySteps: [pathSegment(20, 25), pathSegment(15, 20)], 51 | fillDesc: null, 52 | xform: null, 53 | }, 54 | cutouts: [], 55 | }; 56 | 57 | const result = render(contour); 58 | 59 | expect(result.tagName).toBe('path'); 60 | expect(result).toHaveAttribute('d', 'M 10 15 L 20 25 L 15 20'); 61 | }); 62 | 63 | it('should render contour with fill', async () => { 64 | const contour: Contour = { 65 | polygon: { 66 | polyBegin: { 67 | x: 10, 68 | y: 15, 69 | }, 70 | polySteps: [pathSegment(20, 25), pathSegment(15, 20)], 71 | fillDesc: testFillDesc, 72 | xform: null, 73 | }, 74 | cutouts: [], 75 | }; 76 | 77 | const result = render(contour); 78 | 79 | expect(result).toHaveAttribute('fill', 'rgb(1,2,3)'); 80 | }); 81 | }); 82 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/app/layers-dialog.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Inject } from '@angular/core'; 2 | import { FormsModule } from '@angular/forms'; 3 | import { MatButtonModule } from '@angular/material/button'; 4 | import { 5 | MatDialogModule, 6 | MatDialogRef, 7 | MAT_DIALOG_DATA, 8 | } from '@angular/material/dialog'; 9 | import { MatFormFieldModule } from '@angular/material/form-field'; 10 | import { MatInputModule } from '@angular/material/input'; 11 | import { MatCheckboxModule } from '@angular/material/checkbox'; 12 | import { LAYER_TYPES, LayerType, RenderProperties } from 'boardui-core'; 13 | import { IPC2581 } from 'boardui-parser'; 14 | import { CommonModule } from '@angular/common'; 15 | 16 | export interface LayersDialogData { 17 | renderProperties: RenderProperties; 18 | pcb: IPC2581; 19 | renderPropertiesChange: (renderProperties: RenderProperties) => void; 20 | } 21 | 22 | @Component({ 23 | selector: 'app-layers-dialog', 24 | template: ` 25 |

Layers

26 |
27 | 28 | 29 | {{ layerType }} 30 | 31 | 32 |
33 |
34 | 35 |
36 | `, 37 | standalone: true, 38 | imports: [ 39 | CommonModule, 40 | MatDialogModule, 41 | MatFormFieldModule, 42 | MatInputModule, 43 | FormsModule, 44 | MatButtonModule, 45 | MatCheckboxModule 46 | ], 47 | }) 48 | export class LayersDialogComponent { 49 | layerTypes = LAYER_TYPES; 50 | 51 | constructor( 52 | public dialogRef: MatDialogRef, 53 | @Inject(MAT_DIALOG_DATA) public data: LayersDialogData 54 | ) { } 55 | 56 | isChecked = (layerType: LayerType): boolean => !this.data.renderProperties.layerTypesRenderProperties.find(x => x.layerType === layerType)?.invisible; 57 | 58 | toggleLayerType = (layerType: LayerType) => { 59 | const layerTypeRenderProperties = this.data.renderProperties.layerTypesRenderProperties.find(x => x.layerType === layerType); 60 | if (layerTypeRenderProperties) { 61 | layerTypeRenderProperties.invisible = !layerTypeRenderProperties.invisible; 62 | } else { 63 | this.data.renderProperties.layerTypesRenderProperties.push({ 64 | layerType, 65 | invisible: false 66 | }); 67 | } 68 | this.data.renderPropertiesChange(this.data.renderProperties); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/test/oval-renderer.test.ts: -------------------------------------------------------------------------------- 1 | import { OvalRenderer } from '../src/lib/renderers/oval-renderer'; 2 | import { FillDesc, LineDesc, Oval } from 'boardui-parser/src'; 3 | import { ReusablesProviderMock } from './reusables-provider.mock'; 4 | import { ElementIdProviderMock } from './element-id-provider.mock'; 5 | import { RendererProvider } from '../src/lib/renderer-provider'; 6 | import { RenderPropertiesMock } from './render-properties.mock'; 7 | import '@testing-library/jest-dom'; 8 | 9 | let ovalRenderer: OvalRenderer; 10 | let targetElement: SVGElement; 11 | const testFillDesc: FillDesc = new FillDesc(); 12 | const testLineDesc: LineDesc = { 13 | lineEnd: 'ROUND', 14 | lineWidth: '1', 15 | lineProperty: 'SOLID', 16 | }; 17 | 18 | function render(oval: Oval): SVGElement { 19 | ovalRenderer.render(oval, targetElement, { 20 | reusablesProvider: ReusablesProviderMock, 21 | elementIdProvider: ElementIdProviderMock, 22 | renderProperties: RenderPropertiesMock, 23 | getRenderer: RendererProvider.getRenderer, 24 | }); 25 | 26 | return targetElement.childNodes[0] as SVGElement; 27 | } 28 | 29 | beforeEach(() => { 30 | ovalRenderer = new OvalRenderer(); 31 | targetElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); 32 | testFillDesc.color = { 33 | r: 1, 34 | g: 2, 35 | b: 3, 36 | }; 37 | }); 38 | 39 | describe('OvalRenderer render test', () => { 40 | it('should render basic oval', async () => { 41 | const oval: Oval = { 42 | width: 2, 43 | height: 4, 44 | lineDesc: null, 45 | fillDesc: null, 46 | xform: null, 47 | }; 48 | 49 | const result = render(oval); 50 | 51 | expect(result.tagName).toBe('rect'); 52 | expect(result).toHaveAttribute('x', '-1'); 53 | expect(result).toHaveAttribute('width', '2'); 54 | expect(result).toHaveAttribute('y', '-2'); 55 | expect(result).toHaveAttribute('height', '4'); 56 | expect(result).toHaveAttribute('ry', '2'); 57 | }); 58 | 59 | it('should render oval with fill', async () => { 60 | const oval: Oval = { 61 | width: 2, 62 | height: 4, 63 | lineDesc: null, 64 | fillDesc: testFillDesc, 65 | xform: null, 66 | }; 67 | 68 | const result = render(oval); 69 | 70 | expect(result).toHaveAttribute('fill', 'rgb(1,2,3)'); 71 | }); 72 | 73 | it('should render oval with stroke', async () => { 74 | const oval: Oval = { 75 | width: 2, 76 | height: 4, 77 | lineDesc: testLineDesc, 78 | fillDesc: null, 79 | xform: null, 80 | }; 81 | 82 | const result = render(oval); 83 | 84 | expect(result).toHaveAttribute('stroke-width', '1'); 85 | expect(result).toHaveAttribute('stroke-linecap', 'round'); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/layers/conductor-layer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Color, 3 | ColorRef, 4 | Layer, 5 | LayerFeature, 6 | Pad, 7 | Step, 8 | } from 'boardui-parser'; 9 | import { ElementType } from '../element-type'; 10 | import '../extensions/color.extensions'; 11 | import { RenderContext } from '../render-context'; 12 | import { RendererProvider } from '../renderer-provider'; 13 | import { LayerBase } from './layer-base'; 14 | import { getSvgColor } from '../extensions/color.extensions'; 15 | 16 | export class ConductorLayer extends LayerBase { 17 | get features(): any[] { 18 | return this._layerFeature.sets.flatMap((x) => 19 | x.features.flatMap((xx) => xx.features) 20 | ); 21 | } 22 | 23 | get pads(): Pad[] { 24 | return this._layerFeature.sets.flatMap((x) => x.pads); 25 | } 26 | 27 | constructor( 28 | layer: Layer, 29 | step: Step, 30 | renderContext: RenderContext, 31 | private _layerFeature: LayerFeature 32 | ) { 33 | super(layer, step, renderContext, 'SIGNAL'); 34 | } 35 | 36 | render(): SVGElement { 37 | const layerElement: SVGElement = document.createElementNS( 38 | 'http://www.w3.org/2000/svg', 39 | 'g' 40 | ); 41 | //layerElement.setAttribute("id", this._renderContext.elementIdProvider.getElementId(this._layer).toString()); 42 | layerElement.setAttribute(ElementType.LAYER, this.name); 43 | layerElement.setAttribute('layerType', this._layerType); 44 | 45 | if (this._renderProperties?.invisible) { 46 | layerElement.setAttribute('visibility', 'hidden'); 47 | } 48 | 49 | if (this._renderProperties?.color) { 50 | const color = `rgb(${this._renderProperties.color.r},${this._renderProperties.color.g},${this._renderProperties.color.b})`; 51 | layerElement.setAttribute('fill', color); 52 | layerElement.setAttribute('stroke', color); 53 | } else { 54 | const color = this.getColor(); 55 | if (color) { 56 | layerElement.setAttribute('fill', color); 57 | layerElement.setAttribute('stroke', color); 58 | } 59 | } 60 | 61 | for (const feature of this.features) { 62 | const renderer = RendererProvider.getRenderer(feature); 63 | renderer.render(feature, layerElement, this._renderContext); 64 | } 65 | 66 | const padRenderer = RendererProvider.getRenderer(Pad.prototype); 67 | for (const pad of this.pads) { 68 | padRenderer.render(pad, layerElement, this._renderContext); 69 | } 70 | 71 | return (this._layerElement = layerElement); 72 | } 73 | 74 | private getColor(): string | null { 75 | const colorGroup = this._layerFeature.sets.find( 76 | (set) => set.colorGroups.length > 0 77 | )?.colorGroups[0]; 78 | if (!colorGroup) { 79 | return null; 80 | } 81 | 82 | const color = 83 | colorGroup instanceof ColorRef 84 | ? this._renderContext.reusablesProvider.getColorById(colorGroup.id) 85 | : (colorGroup as Color); 86 | 87 | return getSvgColor(color); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/extensions/polygon.extensions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | PolyBegin, 3 | Polygon, 4 | Polyline, 5 | PolyStepCurve, 6 | PolyStepSegment, 7 | } from 'boardui-parser'; 8 | import { Bounds } from '../bounds'; 9 | 10 | export function getPolygonPath(polygon: Polygon | Polyline, cutouts: Polygon[]): string { 11 | const strArr = new Array(1 + cutouts.length); 12 | strArr[0] = getPolygonPath(polygon); 13 | let i = 1; 14 | for (const cutout of cutouts) { 15 | strArr[i++] = getPolygonPath(cutout); 16 | } 17 | return strArr.join(' '); 18 | 19 | function getPolygonPath(polygon: Polygon | Polyline) { 20 | const strArr = new Array(polygon.polySteps.length + 1); 21 | strArr[0] = `M ${polygon.polyBegin.x} ${polygon.polyBegin.y}`; 22 | let lastStep: PolyBegin = polygon.polyBegin; 23 | for (let i = 0; i < polygon.polySteps.length; i++) { 24 | const step = polygon.polySteps[i]; 25 | if (step instanceof PolyStepSegment) { 26 | strArr[i + 1] = `L ${step.x} ${step.y}`; 27 | } else { 28 | strArr[i + 1] = getSVGArc(step, lastStep); 29 | } 30 | lastStep = step; 31 | } 32 | 33 | return strArr.join(' '); 34 | } 35 | } 36 | 37 | export function getPolygonBounds(polygon: Polygon): Bounds { 38 | const sections = [polygon.polyBegin, ...polygon.polySteps]; 39 | const offsetX = Math.min(...sections.map((x) => x.x!)); 40 | const offsetY = Math.min(...sections.map((x) => x.y!)); 41 | const width = Math.max(...sections.map((x) => x.x!)) - offsetX; 42 | const height = Math.max(...sections.map((x) => x.y!)) - offsetY; 43 | return { 44 | offsetX, 45 | offsetY, 46 | width, 47 | height, 48 | }; 49 | } 50 | 51 | export function getSVGArc(curve: PolyStepCurve, lastStep: PolyBegin): string { 52 | const start: Point = { x: lastStep.x, y: lastStep.y }; 53 | const center: Point = { x: curve.centerX, y: curve.centerY }; 54 | const end: Point = { x: curve.x, y: curve.y }; 55 | const clockwise = curve.clockwise === 'true'; 56 | const distance = getDistance(end, center); 57 | 58 | const [startAngle, endAngle] = getAngles(start, center, end); 59 | 60 | let angleDiff = clockwise ? startAngle - endAngle : endAngle - startAngle; 61 | if (angleDiff < 0) { 62 | angleDiff += 2 * Math.PI; 63 | } 64 | const largeArcFlag = angleDiff > Math.PI ? "1" : "0"; 65 | const sweepFlag = clockwise ? "0" : "1"; 66 | const rotationAngle = endAngle - startAngle; 67 | 68 | return `A ${distance} ${distance} ${rotationAngle} ${largeArcFlag} ${sweepFlag} ${curve.x} ${curve.y}`; 69 | 70 | function getAngles(start: Point, center: Point, end: Point): [number, number] { 71 | if (Math.abs(start.x - end.x) < 1E-09 && Math.abs(start.y - end.y) < 1E-09) { 72 | return [0, 0]; 73 | } 74 | else { 75 | return [ 76 | Math.atan2(start.y - center.y, start.x - center.x), 77 | Math.atan2(end.y - center.y, end.x - center.x) 78 | ]; 79 | } 80 | } 81 | 82 | function getDistance(start: Point, end: Point): number { 83 | const y = end.x - start.x; 84 | const x = end.y - start.y; 85 | 86 | return Math.sqrt(x * x + y * y); 87 | } 88 | 89 | interface Point { x: number, y: number } 90 | } -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/layers/drill-layer.ts: -------------------------------------------------------------------------------- 1 | import { Hole, Layer, LayerFeature, Set, Step } from 'boardui-parser'; 2 | import { Bounds } from '../bounds'; 3 | import { RenderContext } from '../render-context'; 4 | import { RendererProvider } from '../renderer-provider'; 5 | import { LayerBase } from './layer-base'; 6 | 7 | export class DrillLayer extends LayerBase { 8 | constructor( 9 | layer: Layer, 10 | step: Step, 11 | renderContext: RenderContext, 12 | private _layerFeature: LayerFeature, 13 | private _bounds: Bounds 14 | ) { 15 | super(layer, step, renderContext, 'DRILL'); 16 | } 17 | 18 | get drills(): Hole[] { 19 | return this._layerFeature.sets.flatMap((x: Set) => x.holes); 20 | } 21 | 22 | render(): SVGElement { 23 | const maskElement: SVGElement = document.createElementNS( 24 | 'http://www.w3.org/2000/svg', 25 | 'mask' 26 | ); 27 | maskElement.setAttribute( 28 | 'id', 29 | this._renderContext.elementIdProvider.getElementId(this._layer).toString() 30 | ); 31 | maskElement.setAttribute('layer', this.name); 32 | maskElement.setAttribute('layerType', this._layerType); 33 | 34 | if (this._renderProperties?.invisible) { 35 | maskElement.setAttribute('visibility', 'hidden'); 36 | } 37 | 38 | const rectElement: SVGElement = document.createElementNS( 39 | 'http://www.w3.org/2000/svg', 40 | 'rect' 41 | ); 42 | rectElement.setAttribute('width', this._bounds.width.toString()); 43 | rectElement.setAttribute('height', this._bounds.height.toString()); 44 | rectElement.setAttribute('fill', 'white'); 45 | rectElement.setAttribute('x', this._bounds.offsetX.toString()); 46 | rectElement.setAttribute('y', this._bounds.offsetY.toString()); 47 | 48 | maskElement.appendChild(rectElement); 49 | 50 | for (const drill of this.drills) { 51 | const renderer = RendererProvider.getRenderer(drill); 52 | renderer.render(drill, maskElement, this._renderContext); 53 | } 54 | 55 | return (this._layerElement = maskElement); 56 | } 57 | 58 | apply(layers: [Layer, SVGElement][]): void { 59 | if (this._renderProperties?.invisible) { 60 | return; 61 | } 62 | 63 | //const span = this._layer.span 64 | const selectedLayers: [Layer, SVGElement][] = layers.filter( 65 | (x) => x[0].layerFunction !== 'COMPONENT' 66 | ); 67 | // TODO: Remove as assembly/test files will not contain more that TOP or BOTTOM layers. 68 | /*if (span){ 69 | const fromIndex = layers.findIndex(x => x[0].name == span.fromLayer); 70 | const toIndex = layers.findIndex(x => x[0].name == span.toLayer); 71 | selectedLayers = layers.slice(fromIndex, toIndex + 1); 72 | }*/ 73 | 74 | for (const selectedLayer of selectedLayers) { 75 | const drilledLayerElement: SVGElement = document.createElementNS( 76 | 'http://www.w3.org/2000/svg', 77 | 'g' 78 | ); 79 | drilledLayerElement.innerHTML = selectedLayer[1].innerHTML; 80 | selectedLayer[1].replaceChildren(drilledLayerElement); 81 | 82 | drilledLayerElement.setAttribute( 83 | 'mask', 84 | `url(#${this._layerElement.getAttribute('id')})` 85 | ); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/layers/silkscreen-layer.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Color, 3 | ColorRef, 4 | Features, 5 | Layer, 6 | LayerFeature, 7 | Location, 8 | Step, 9 | Set, 10 | } from 'boardui-parser'; 11 | import { ElementType } from '../element-type'; 12 | import '../extensions/color.extensions'; 13 | import { RenderContext } from '../render-context'; 14 | import { RendererProvider } from '../renderer-provider'; 15 | import { LayerBase } from './layer-base'; 16 | import { getSvgColor } from '../extensions/color.extensions'; 17 | 18 | export class SilkscreenLayer extends LayerBase { 19 | get features(): any[] { 20 | return this._layerFeature.sets.flatMap((x: Set) => 21 | x.features.flatMap((xx: Features) => xx.features) 22 | ); 23 | } 24 | 25 | /* 26 | get packageSilkscreens(): [Location, SilkScreen | null][] { 27 | return this._step.components 28 | .filter(x => x.packageRef !== null) 29 | .map(x => [x.location, this._renderContext.reusablesProvider.getPackageByName(x.packageRef!).silkScreen?.features ?? []]); 30 | }*/ 31 | 32 | constructor( 33 | layer: Layer, 34 | step: Step, 35 | renderContext: RenderContext, 36 | private _layerFeature: LayerFeature 37 | ) { 38 | super(layer, step, renderContext, 'SILKSCREEN'); 39 | } 40 | 41 | render(): SVGElement { 42 | const layerElement: SVGElement = document.createElementNS( 43 | 'http://www.w3.org/2000/svg', 44 | 'g' 45 | ); 46 | //layerElement.setAttribute("id", this._renderContext.elementIdProvider.getElementId(this._layer).toString()); 47 | layerElement.setAttribute(ElementType.LAYER, this.name); 48 | layerElement.setAttribute('layerType', this._layerType); 49 | 50 | if (this._renderProperties?.invisible) { 51 | layerElement.setAttribute('visibility', 'hidden'); 52 | } 53 | 54 | if (this._renderProperties?.color) { 55 | const color = `rgb(${this._renderProperties.color.r},${this._renderProperties.color.g},${this._renderProperties.color.b})`; 56 | layerElement.setAttribute('fill', color); 57 | layerElement.setAttribute('stroke', color); 58 | } else { 59 | const color = this.getColor(); 60 | if (color) { 61 | layerElement.setAttribute('fill', color); 62 | layerElement.setAttribute('stroke', color); 63 | } 64 | } 65 | 66 | for (const feature of this.features) { 67 | const renderer = RendererProvider.getRenderer(feature); 68 | renderer.render(feature, layerElement, this._renderContext); 69 | } 70 | 71 | /* 72 | for (const packageSilkscreen of this.packageSilkscreens){ 73 | const renderer = RendererProvider.getRenderer(SilkScreen.prototype); 74 | renderer.render(packageSilkscreen, layerElement, this._renderContext); 75 | } 76 | */ 77 | 78 | return (this._layerElement = layerElement); 79 | } 80 | 81 | private getColor(): string | null { 82 | const colorGroup = this._layerFeature.sets.find( 83 | (set) => set.colorGroups.length > 0 84 | )?.colorGroups[0]; 85 | if (!colorGroup) { 86 | return null; 87 | } 88 | 89 | const color = 90 | colorGroup instanceof ColorRef 91 | ? this._renderContext.reusablesProvider.getColorById(colorGroup.id) 92 | : (colorGroup as Color); 93 | 94 | return getSvgColor(color); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/project.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boardui-demo", 3 | "$schema": "../../node_modules/nx/schemas/project-schema.json", 4 | "projectType": "application", 5 | "prefix": "boardui", 6 | "sourceRoot": "packages/boardui-demo/src", 7 | "tags": [], 8 | "targets": { 9 | "build": { 10 | "executor": "@angular-devkit/build-angular:browser", 11 | "outputs": ["{options.outputPath}"], 12 | "options": { 13 | "outputPath": "dist/packages/boardui-demo", 14 | "index": "packages/boardui-demo/src/index.html", 15 | "main": "packages/boardui-demo/src/main.ts", 16 | "polyfills": ["zone.js"], 17 | "tsConfig": "packages/boardui-demo/tsconfig.app.json", 18 | "assets": [ 19 | "packages/boardui-demo/src/favicon.ico", 20 | "packages/boardui-demo/src/assets" 21 | ], 22 | "styles": ["packages/boardui-demo/src/styles.scss"], 23 | "scripts": [] 24 | }, 25 | "configurations": { 26 | "production": { 27 | "budgets": [ 28 | { 29 | "type": "initial", 30 | "maximumWarning": "500kb", 31 | "maximumError": "1mb" 32 | }, 33 | { 34 | "type": "anyComponentStyle", 35 | "maximumWarning": "2kb", 36 | "maximumError": "4kb" 37 | } 38 | ], 39 | "outputHashing": "all" 40 | }, 41 | "development": { 42 | "buildOptimizer": false, 43 | "optimization": false, 44 | "vendorChunk": true, 45 | "extractLicenses": false, 46 | "sourceMap": true, 47 | "namedChunks": true 48 | } 49 | }, 50 | "defaultConfiguration": "production", 51 | "dependsOn": [ 52 | { 53 | "dependencies": true, 54 | "target": "build" 55 | } 56 | ] 57 | }, 58 | "serve": { 59 | "executor": "@angular-devkit/build-angular:dev-server", 60 | "configurations": { 61 | "production": { 62 | "buildTarget": "boardui-demo:build:production" 63 | }, 64 | "development": { 65 | "buildTarget": "boardui-demo:build:development" 66 | } 67 | }, 68 | "defaultConfiguration": "development" 69 | }, 70 | "extract-i18n": { 71 | "executor": "@angular-devkit/build-angular:extract-i18n", 72 | "options": { 73 | "buildTarget": "boardui-demo:build" 74 | } 75 | }, 76 | "lint": { 77 | "executor": "@nx/eslint:lint", 78 | "outputs": ["{options.outputFile}"], 79 | "options": { 80 | "lintFilePatterns": [ 81 | "packages/boardui-demo/**/*.ts", 82 | "packages/boardui-demo/**/*.html" 83 | ] 84 | } 85 | }, 86 | "test": { 87 | "executor": "@nx/jest:jest", 88 | "outputs": ["{workspaceRoot}/coverage/{projectRoot}"], 89 | "options": { 90 | "jestConfig": "packages/boardui-demo/jest.config.ts", 91 | "passWithNoTests": true 92 | }, 93 | "configurations": { 94 | "ci": { 95 | "ci": true, 96 | "codeCoverage": true 97 | } 98 | } 99 | }, 100 | "serve-static": { 101 | "executor": "@nx/web:file-server", 102 | "options": { 103 | "buildTarget": "boardui-demo:build" 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /boardui/packages/boardui-demo/src/app/app.component.scss: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | background: white; 3 | padding: 12px; 4 | border-radius: 16px; 5 | box-shadow: 0 0 8px 0 rgb(0 0 0 / 20%); 6 | border: 1px solid rgb(0 0 0 / 10%); 7 | } 8 | 9 | bui-viewer { 10 | border-radius: 3px; 11 | border: 1px solid rgb(0 0 0 / 10%); 12 | box-sizing: border-box; 13 | box-shadow: inset 0 0 8px 0 rgb(0 0 0 / 10%); 14 | width: 100%; 15 | height: clamp(400px, 50vh, 600px); 16 | overflow: hidden; 17 | background-color: white; 18 | } 19 | 20 | ::ng-deep bui-viewer .toolbar { 21 | .mat-mdc-fab { 22 | margin: 0 0.25rem; 23 | background: #1e3d59; 24 | color: white; 25 | } 26 | } 27 | 28 | h1 { 29 | background-color: #1e3d59; 30 | color: white; 31 | margin: 0; 32 | padding: 0.5rem 1rem; 33 | font-size: 20pt; 34 | font-weight: 500; 35 | } 36 | 37 | :host { 38 | margin: 0 auto; 39 | display: block; 40 | max-width: 1440px; 41 | background: white; 42 | } 43 | 44 | .navigation { 45 | width: 100%; 46 | height: 60px; 47 | display: flex; 48 | 49 | .logo { 50 | height: 60px; 51 | margin-left: 1rem; 52 | display: flex; 53 | text-decoration: none; 54 | 55 | object { 56 | display: block; 57 | padding: 0.75rem 0; 58 | height: calc(100% - 1.5rem); 59 | } 60 | 61 | span { 62 | margin-left: 0.75rem; 63 | display: block; 64 | font-size: 24pt; 65 | font-weight: 500; 66 | font-family: 'Normschrift'; 67 | color: #1e3d59; 68 | align-self: center; 69 | } 70 | } 71 | 72 | .menu { 73 | flex-grow: 1; 74 | } 75 | } 76 | 77 | .mat-mdc-tab-group { 78 | max-height: 70vh; 79 | } 80 | 81 | .section { 82 | padding: 1rem 1rem 2rem 1rem; 83 | 84 | h2 { 85 | margin: 0 0 1rem 0; 86 | } 87 | 88 | .content { 89 | display: grid; 90 | grid-template-columns: 100%; 91 | } 92 | } 93 | 94 | .section:nth-child(even) { 95 | background-color: #f5f5f5; 96 | } 97 | 98 | @media screen and (min-width: 800px) { 99 | bui-viewer { 100 | height: clamp(450px, 60vh, 700px); 101 | } 102 | 103 | .mat-mdc-tab-group { 104 | max-height: 80vh; 105 | } 106 | 107 | .section .content { 108 | grid-template-columns: 1fr 2fr; 109 | gap: 1rem; 110 | } 111 | 112 | .section:nth-child(odd) .content { 113 | .description { 114 | order: 2; 115 | } 116 | 117 | .demo { 118 | order: 1; 119 | } 120 | } 121 | } 122 | 123 | @media screen and (min-width: 1000px) { 124 | bui-viewer { 125 | height: clamp(600px, 70vh, 800px); 126 | } 127 | 128 | .mat-mdc-tab-group { 129 | max-height: 90vh; 130 | } 131 | 132 | .section .content { 133 | grid-template-columns: 2fr 5fr; 134 | } 135 | } 136 | 137 | .loading-indicator { 138 | position: fixed; 139 | z-index: 999; 140 | top: 0; 141 | left: 0; 142 | right: 0; 143 | bottom: 0; 144 | background-color: rgba(0.1, 0.1, 0.1, 0.3); 145 | backdrop-filter: blur(5px); 146 | 147 | mat-spinner { 148 | position: absolute; 149 | top: 50%; 150 | left: 50%; 151 | transform: translate(-50%, -50%); 152 | } 153 | 154 | span { 155 | position: absolute; 156 | top: calc(50% + 85px); 157 | left: 50%; 158 | transform: translate(-50%, -50%); 159 | font-weight: 500; 160 | font-size: 16pt; 161 | } 162 | } 163 | 164 | .footer { 165 | text-align: center; 166 | font-size: 11pt; 167 | padding: .5rem; 168 | } -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/reusables-repository.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Layer, 3 | Color, 4 | LineDesc, 5 | FillDesc, 6 | StandardPrimitive, 7 | Package, 8 | IPC2581, 9 | Step, 10 | DictionaryEntry, 11 | } from 'boardui-parser'; 12 | import './extensions/line-desc.extensions'; 13 | import { ReusablesProvider } from './reusables-provider'; 14 | 15 | export class ReusablesRepository implements ReusablesProvider { 16 | private readonly _layers: Map; 17 | private readonly _colors: Map; 18 | private readonly _lineDescs: Map; 19 | private readonly _fillDescs: Map; 20 | private readonly _primitives: Map; 21 | private readonly _packages: Map; 22 | 23 | constructor(_ipc: IPC2581) { 24 | this._layers = new Map( 25 | _ipc.ecad.cadData.layers.map((layer) => [layer.name, layer]) 26 | ); 27 | this._colors = new Map( 28 | _ipc.content.dictionaryColor.entries.map( 29 | (colorEntry: DictionaryEntry) => [ 30 | colorEntry.id, 31 | colorEntry.content, 32 | ] 33 | ) 34 | ); 35 | this._lineDescs = new Map( 36 | _ipc.content.dictionaryLineDesc.entries.map( 37 | (lineDescEntry: DictionaryEntry) => [ 38 | lineDescEntry.id, 39 | lineDescEntry.content, 40 | ] 41 | ) 42 | ); 43 | this._fillDescs = new Map( 44 | _ipc.content.dictionaryFillDesc.entries.map( 45 | (fillDescEntry: DictionaryEntry) => [ 46 | fillDescEntry.id, 47 | fillDescEntry.content, 48 | ] 49 | ) 50 | ); 51 | this._primitives = new Map( 52 | _ipc.content.dictionaryStandard.entries.map( 53 | (primitiveEntry: DictionaryEntry) => [ 54 | primitiveEntry.id, 55 | primitiveEntry.content, 56 | ] 57 | ) 58 | ); 59 | this._packages = new Map( 60 | _ipc.ecad.cadData.steps.flatMap((step: Step) => 61 | step.packages.map((p: Package) => [p.name, p]) 62 | ) 63 | ); 64 | } 65 | 66 | public getLayerByName(name: string): Layer { 67 | const layer = this._layers.get(name); 68 | if (!layer) { 69 | throw new Error(`Layer '${name}' not found.`); 70 | } 71 | return layer; 72 | } 73 | 74 | public getColorById(colorId: string): Color { 75 | const color = this._colors.get(colorId); 76 | if (!color) { 77 | throw new Error(`Color '${colorId}' not found.`); 78 | } 79 | return color; 80 | } 81 | 82 | public getLineDescById(lineDescId: string): LineDesc { 83 | const lineDesc = this._lineDescs.get(lineDescId); 84 | if (!lineDesc) { 85 | throw new Error(`Line descriptor '${lineDescId}' not found.`); 86 | } 87 | return lineDesc; 88 | } 89 | 90 | public getFillDescById(fillDescId: string): FillDesc { 91 | const fillDesc = this._fillDescs.get(fillDescId); 92 | if (!fillDesc) { 93 | throw new Error(`Fill descriptor '${fillDescId}' not found.`); 94 | } 95 | return fillDesc; 96 | } 97 | 98 | public getPrimitiveById(primitiveId: string): StandardPrimitive { 99 | const primitive = this._primitives.get(primitiveId); 100 | if (!primitive) { 101 | throw new Error(`Primitive '${primitiveId}' not found.`); 102 | } 103 | return primitive; 104 | } 105 | 106 | public getPackageByName(name: string): Package { 107 | const pckg = this._packages.get(name); 108 | if (!pckg) { 109 | throw new Error(`Package '${name}' not found.`); 110 | } 111 | return pckg; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/renderers/component-renderer.ts: -------------------------------------------------------------------------------- 1 | import '../extensions/xform.extensions'; 2 | import { RendererBase } from './renderer-base'; 3 | import { ElementType } from '../element-type'; 4 | import { ColorHelper } from '../color.helper'; 5 | import { Component, LineDescRef } from 'boardui-parser'; 6 | import { ElementIdProvider } from '../element-id-provider'; 7 | import { ComponentRenderProperties, RenderProperties } from 'boardui-core'; 8 | import { ReusablesProvider } from '../reusables-provider'; 9 | import { getLineDescSVGAttributes } from '../extensions/line-desc.extensions'; 10 | import { getPolygonPath } from '../extensions/polygon.extensions'; 11 | 12 | export class ComponentRenderer extends RendererBase { 13 | private _cloneableComponentAttribute = document.createAttribute( 14 | ElementType.COMPONENT 15 | ); 16 | constructor() { 17 | super('g'); 18 | } 19 | 20 | protected renderPart( 21 | part: Component, 22 | partElement: SVGElement, 23 | reusablesProvider: ReusablesProvider, 24 | elementIdProvider: ElementIdProvider, 25 | renderProperties: RenderProperties, 26 | renderSubpart: (part: any, partElement: SVGElement) => void 27 | ): void { 28 | partElement.setAttributeNode( 29 | this._cloneableComponentAttribute.cloneNode() as Attr 30 | ); 31 | partElement.setAttribute( 32 | 'id', 33 | elementIdProvider.getElementId(part).toString() 34 | ); 35 | 36 | if (part.refDes) partElement.setAttribute('refDes', part.refDes); 37 | const transformation = [`translate(${part.location.x},${part.location.y})`]; 38 | if (part.xform) transformation.push(part.xform.getSVGTransformation()); 39 | 40 | partElement.setAttribute('transform', transformation.join(' ')); 41 | 42 | const componentRenderProperties = this.getComponentRenderProperties( 43 | renderProperties, 44 | part 45 | ); 46 | 47 | if (part.packageRef) { 48 | const pckg = reusablesProvider.getPackageByName(part.packageRef); 49 | 50 | const outlineEle = document.createElementNS( 51 | 'http://www.w3.org/2000/svg', 52 | 'path' 53 | ); 54 | outlineEle.toggleAttribute('outline'); 55 | const lineDesc = 56 | pckg.outline.lineDesc instanceof LineDescRef 57 | ? reusablesProvider.getLineDescById(pckg.outline.lineDesc.id) 58 | : pckg.outline.lineDesc; 59 | const lineDescAttributes = getLineDescSVGAttributes(lineDesc); 60 | for (const lineDescAttribute of lineDescAttributes) { 61 | outlineEle.setAttribute(...lineDescAttribute); 62 | } 63 | const path = getPolygonPath(pckg.outline.polygon, []); 64 | outlineEle.setAttribute('d', path); 65 | outlineEle.setAttribute('fill', 'rgba(0,0,0,0)'); 66 | 67 | if (componentRenderProperties.fillColor) { 68 | outlineEle.setAttribute( 69 | 'fill', 70 | ColorHelper.getSVGColor(componentRenderProperties.fillColor) 71 | ); 72 | } 73 | 74 | if (componentRenderProperties.outlineColor) { 75 | outlineEle.setAttribute( 76 | 'stroke', 77 | ColorHelper.getSVGColor(componentRenderProperties.outlineColor) 78 | ); 79 | } 80 | 81 | for (const pin of pckg.pins) { 82 | renderSubpart(pin, partElement); 83 | } 84 | 85 | partElement.appendChild(outlineEle); 86 | } 87 | } 88 | 89 | private getComponentRenderProperties( 90 | renderProperties: RenderProperties, 91 | component: Component 92 | ): ComponentRenderProperties { 93 | let fillColor; 94 | let outlineColor; 95 | const renderPropertiesToApply = renderProperties.componentRenderProperties 96 | .filter((x) => 97 | x.selectors.every((selector) => component[selector[0]] === selector[1]) 98 | ) 99 | .sort((a, b) => a.selectors.length - b.selectors.length); 100 | for (const renderProp of renderPropertiesToApply) { 101 | if (renderProp.fillColor) { 102 | fillColor = renderProp.fillColor; 103 | } 104 | if (renderProp.outlineColor) { 105 | outlineColor = renderProp.outlineColor; 106 | } 107 | } 108 | return { 109 | selectors: [], 110 | fillColor: fillColor, 111 | outlineColor: outlineColor, 112 | }; 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/xml-to-js-parser.ts: -------------------------------------------------------------------------------- 1 | import { Attribute, SaxEventType, SAXParser, Tag } from 'sax-wasm'; 2 | import { XMLtoJSMapping as XMLToJSMapping } from './xml-to-js-mapping'; 3 | 4 | export class XmlToJsParser { 5 | private static _chunkSize = 64 * 1024; 6 | 7 | private _processOpenTagFuncHashSet: Map< 8 | string, 9 | (obj: any, stack: any[]) => any 10 | >; 11 | 12 | constructor( 13 | mappings: XMLToJSMapping[], 14 | private _rootNodePrototype: any, 15 | private _saxParser: SAXParser, 16 | ) { 17 | this._processOpenTagFuncHashSet = new Map( 18 | mappings 19 | .sort((a, b) => a[0].localeCompare(b[0])) 20 | .map((x) => XmlToJsParser.getMappingFunc(...x)), 21 | ); 22 | } 23 | 24 | static getMappingFunc( 25 | tagName: string, 26 | prototype: any, 27 | properties: string[], 28 | ): [string, (obj: any, stack: any[]) => void] { 29 | return [ 30 | tagName, 31 | (obj: any, stack: any[]) => { 32 | for (const property of properties) { 33 | if (Object.prototype.hasOwnProperty.call(obj, property)) { 34 | const emptyObj = new prototype.constructor(); 35 | if (Array.isArray(obj[property])) { 36 | Array.prototype.push.call(obj[property], emptyObj); 37 | } else if (obj[property]) { 38 | Array.prototype.push.call(stack, obj[property]); 39 | return; 40 | } else { 41 | obj[property] = emptyObj; 42 | } 43 | Array.prototype.push.call(stack, emptyObj); 44 | return; 45 | } 46 | } 47 | console.debug('Cannot find property for ' + tagName); 48 | }, 49 | ]; 50 | } 51 | 52 | static processOpenTag( 53 | tag: Tag, 54 | stack: any[], 55 | processOpenTagFuncHashSet: Map any>, 56 | ) { 57 | const obj = stack.at(-1); 58 | if (!obj) { 59 | Array.prototype.push.call(stack, null); 60 | console.debug('Cannot process ' + tag.name); 61 | } else { 62 | const mappingFunc = processOpenTagFuncHashSet.get(tag.name); 63 | if (mappingFunc) { 64 | try { 65 | mappingFunc(obj, stack); 66 | } catch (e) { 67 | console.log(e); 68 | } 69 | } else { 70 | Array.prototype.push.call(stack, null); 71 | console.debug('Cannot process ' + tag.name); 72 | } 73 | } 74 | } 75 | 76 | static processAttribute(attribute: Attribute, stack: any[]) { 77 | const obj = stack.at(-1); 78 | if (obj) { 79 | const attributeName = attribute.name.value; 80 | if (Object.prototype.hasOwnProperty.call(obj, attributeName)) { 81 | obj[attributeName] = attribute.value.value; 82 | } 83 | } 84 | } 85 | 86 | static processEvent( 87 | event: SaxEventType, 88 | data: any, 89 | stack: any[], 90 | processOpenTagFuncHashSet: Map any>, 91 | ) { 92 | switch (event) { 93 | case SaxEventType.OpenTagStart: 94 | XmlToJsParser.processOpenTag( 95 | data, 96 | stack, 97 | processOpenTagFuncHashSet, 98 | ); 99 | break; 100 | case SaxEventType.Attribute: 101 | XmlToJsParser.processAttribute(data, stack); 102 | break; 103 | case SaxEventType.CloseTag: 104 | stack.pop(); 105 | break; 106 | } 107 | } 108 | 109 | async parse(stream: ReadableStream): Promise { 110 | const rootNode = new this._rootNodePrototype.constructor(); 111 | const stack = [rootNode]; 112 | 113 | this._saxParser.eventHandler = (event, data) => 114 | XmlToJsParser.processEvent( 115 | event, 116 | data, 117 | stack, 118 | this._processOpenTagFuncHashSet, 119 | ); 120 | 121 | const streamReader = stream.getReader(); 122 | let readResult = null; 123 | while (!(readResult = await streamReader.read()).done) { 124 | const chunk = readResult.value; 125 | const chunkSliceCount = Math.floor( 126 | chunk.length / XmlToJsParser._chunkSize, 127 | ); 128 | let i = 0; 129 | while (i < chunkSliceCount) { 130 | this._saxParser.write( 131 | chunk.subarray( 132 | i * XmlToJsParser._chunkSize, 133 | ++i * XmlToJsParser._chunkSize, 134 | ), 135 | ); 136 | } 137 | this._saxParser.write( 138 | chunk.subarray(i * XmlToJsParser._chunkSize, chunk.length), 139 | ); 140 | } 141 | this._saxParser.end(); 142 | return rootNode; 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /boardui/packages/boardui-parser/src/lib/default-ipc-mappings.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Location, 3 | Set, 4 | AssemblyDrawing, 5 | CadData, 6 | Circle, 7 | Color, 8 | ColorRef, 9 | Component, 10 | Content, 11 | Contour, 12 | Dictionary, 13 | DictionaryEntry, 14 | Ecad, 15 | Features, 16 | FillDesc, 17 | FillDescRef, 18 | Hole, 19 | Layer, 20 | LayerFeature, 21 | LayerRef, 22 | LineDesc, 23 | LineDescRef, 24 | Marking, 25 | NonstandardAttribute, 26 | Outline, 27 | Package, 28 | Pad, 29 | Pin, 30 | PinRef, 31 | PolyBegin, 32 | Polygon, 33 | Polyline, 34 | PolyStepCurve, 35 | PolyStepSegment, 36 | RectCenter, 37 | Span, 38 | StandardPrimitiveRef, 39 | Step, 40 | StepRef, 41 | XForm, 42 | IPC2581, 43 | FunctionMode, 44 | SilkScreen, 45 | Bom, 46 | BomHeader, 47 | BomItem, 48 | BomRef, 49 | Characteristics, 50 | Enumerated, 51 | Measured, 52 | Ranged, 53 | RefDes, 54 | Textual, 55 | Oval, 56 | Line, 57 | } from './ipc'; 58 | import { XMLtoJSMapping } from './xml-to-js-mapping'; 59 | 60 | const defaultIPCMappings = new Array( 61 | ['Bom', Bom.prototype, ['bom']], 62 | ['BomItem', BomItem.prototype, ['bomItems']], 63 | ['BomHeader', BomHeader.prototype, ['bomHeader']], 64 | ['BomRef', BomRef.prototype, ['bomRefs']], 65 | ['CadData', CadData.prototype, ['cadData']], 66 | ['Content', Content.prototype, ['content']], 67 | ['Contour', Contour.prototype, ['content', 'features']], 68 | ['Cutout', Polygon.prototype, ['cutouts']], 69 | ['Ecad', Ecad.prototype, ['ecad']], 70 | ['EntryLineDesc', DictionaryEntry.prototype, ['entries']], 71 | ['Features', Features.prototype, ['features']], 72 | ['IPC-2581', IPC2581.prototype, []], 73 | ['Layer', Layer.prototype, ['layers']], 74 | ['LayerFeature', LayerFeature.prototype, ['layerFeatures']], 75 | ['LayerRef', LayerRef.prototype, ['layerRefs']], 76 | ['Step', Step.prototype, ['steps']], 77 | ['Profile', Contour.prototype, ['profile', 'profiles']], 78 | ['Polygon', Polygon.prototype, ['polygon']], 79 | ['PolyBegin', PolyBegin.prototype, ['polyBegin']], 80 | ['PolyStepSegment', PolyStepSegment.prototype, ['polySteps']], 81 | ['PolyStepCurve', PolyStepCurve.prototype, ['polySteps']], 82 | ['Set', Set.prototype, ['sets']], 83 | ['ColorRef', ColorRef.prototype, ['colorGroups']], 84 | ['DictionaryColor', Dictionary.prototype, ['dictionaryColor']], 85 | ['EntryColor', DictionaryEntry.prototype, ['entries']], 86 | ['Color', Color.prototype, ['content']], 87 | ['DictionaryLineDesc', Dictionary.prototype, ['dictionaryLineDesc']], 88 | ['LineDesc', LineDesc.prototype, ['content', 'lineDesc']], 89 | ['LineDescRef', LineDescRef.prototype, ['lineDesc']], 90 | ['Polyline', Polyline.prototype, ['features', 'polyline']], 91 | ['Package', Package.prototype, ['packages']], 92 | ['Outline', Outline.prototype, ['outline']], 93 | ['Pin', Pin.prototype, ['pins']], 94 | ['Location', Location.prototype, ['location']], 95 | [ 96 | 'StandardPrimitiveRef', 97 | StandardPrimitiveRef.prototype, 98 | ['standardPrimitiveRef', 'feature'], 99 | ], 100 | ['AssemblyDrawing', AssemblyDrawing.prototype, ['assemblyDrawing']], 101 | ['Marking', Marking.prototype, ['marking']], 102 | ['DictionaryUser', Dictionary.prototype, ['dictionaryStandard']], 103 | ['EntryUser', DictionaryEntry.prototype, ['entries']], 104 | ['DictionaryStandard', Dictionary.prototype, ['dictionaryStandard']], 105 | ['EntryStandard', DictionaryEntry.prototype, ['entries']], 106 | ['DictionaryFillDesc', Dictionary.prototype, ['dictionaryFillDesc']], 107 | ['EntryFillDesc', DictionaryEntry.prototype, ['entries']], 108 | ['FillDesc', FillDesc.prototype, ['content', 'fillDesc']], 109 | ['FillDescRef', FillDescRef.prototype, ['fillDesc']], 110 | ['Circle', Circle.prototype, ['content']], 111 | ['Oval', Oval.prototype, ['content']], 112 | ['RectCenter', RectCenter.prototype, ['content']], 113 | ['Component', Component.prototype, ['components']], 114 | ['Xform', XForm.prototype, ['xform']], 115 | ['Hole', Hole.prototype, ['holes']], 116 | ['StepRef', StepRef.prototype, ['stepRefs']], 117 | ['Span', Span.prototype, ['span']], 118 | ['Pad', Pad.prototype, ['pads']], 119 | [ 120 | 'NonstandardAttribute', 121 | NonstandardAttribute.prototype, 122 | ['nonstandardAttributes'], 123 | ], 124 | ['PinRef', PinRef.prototype, ['pinRefs']], 125 | ['SilkScreen', SilkScreen.prototype, ['silkScreen']], 126 | ['Measured', Measured.prototype, ['measured']], 127 | ['Ranged', Ranged.prototype, ['ranged']], 128 | ['Enumerated', Enumerated.prototype, ['enumerated']], 129 | ['Textual', Textual.prototype, ['textual']], 130 | ['RefDes', RefDes.prototype, ['refDes']], 131 | ['Characteristics', Characteristics.prototype, ['characteristics']], 132 | ['FunctionMode', FunctionMode.prototype, ['functionMode']], 133 | ['Line', Line.prototype, ['features']] 134 | ); 135 | 136 | export const DEFAULT_IPC_MAPPINGS = defaultIPCMappings; 137 | -------------------------------------------------------------------------------- /boardui/packages/boardui-renderer/src/lib/svg-pcb-renderer.ts: -------------------------------------------------------------------------------- 1 | import { DrillLayer } from './layers/drill-layer'; 2 | import { RendererProvider } from './renderer-provider'; 3 | import { ConductorLayer } from './layers/conductor-layer'; 4 | import { IPC2581, Layer, Side } from 'boardui-parser'; 5 | import { RenderContext } from './render-context'; 6 | import { SilkscreenLayer } from './layers/silkscreen-layer'; 7 | import { ComponentLayer } from './layers/component-layer'; 8 | import { ProfileLayer } from './layers/profile-layer'; 9 | import { ElementType } from './element-type'; 10 | import { RenderProperties } from 'boardui-core'; 11 | import { ElementIdProvider } from './element-id-provider'; 12 | import { getPolygonBounds } from './extensions/polygon.extensions'; 13 | import { ReusablesProvider } from './reusables-provider'; 14 | 15 | export class SVGPCBRenderer { 16 | private _cloneableSvgElement: SVGElement; 17 | 18 | constructor( 19 | private _reusablesProvider: ReusablesProvider, 20 | private _renderProperties: RenderProperties 21 | ) { 22 | this._cloneableSvgElement = document.createElementNS( 23 | 'http://www.w3.org/2000/svg', 24 | 'svg' 25 | ); 26 | this._cloneableSvgElement.setAttribute( 27 | 'xmlns', 28 | 'http://www.w3.org/2000/svg' 29 | ); 30 | } 31 | 32 | render( 33 | pcb: IPC2581, 34 | stepName: string, 35 | side: Side, 36 | elementIdProvider: ElementIdProvider 37 | ): SVGElement { 38 | const step = pcb.ecad.cadData.steps.find((x) => x.name === stepName); 39 | if (!step) { 40 | throw new Error('Step not found'); 41 | } 42 | 43 | const renderContext = new RenderContext( 44 | this._reusablesProvider, 45 | elementIdProvider, 46 | this._renderProperties, 47 | RendererProvider.getRenderer 48 | ); 49 | 50 | const svgElement: SVGElement = 51 | this._cloneableSvgElement.cloneNode() as SVGElement; 52 | svgElement.setAttribute(ElementType.STEP, step.name); 53 | //svgElement.setAttribute("id", renderContext.elementIdProvider.getElementId(step).toString()); 54 | const defsElement = document.createElementNS( 55 | 'http://www.w3.org/2000/svg', 56 | 'defs' 57 | ); 58 | 59 | if (side === 'TOP') { 60 | svgElement.setAttribute('transform', 'scale(-1,1) rotate(180)'); 61 | } else { 62 | svgElement.setAttribute('transform', 'scale(1,1) rotate(180)'); 63 | } 64 | 65 | const bounds = getPolygonBounds( 66 | pcb.ecad.cadData.steps[0]!.profile!.polygon 67 | ); 68 | svgElement.setAttribute( 69 | 'viewBox', 70 | `${bounds.offsetX - this._renderProperties.padding} ${ 71 | bounds.offsetY - this._renderProperties.padding 72 | } ${bounds.width + this._renderProperties.padding * 2} ${ 73 | bounds.height + this._renderProperties.padding * 2 74 | }` 75 | ); 76 | 77 | const profileLayer = new Layer(); 78 | profileLayer.name = 'PROFILE'; 79 | const profileLayerElement = new ProfileLayer( 80 | profileLayer, 81 | step, 82 | renderContext 83 | ).render(); 84 | 85 | if (this._renderProperties.dropShadow) { 86 | const filterElement = document.createElementNS( 87 | 'http://www.w3.org/2000/svg', 88 | 'filter' 89 | ); 90 | filterElement.setAttribute('id', 'board-shadow'); 91 | const dropShadowElement = document.createElementNS( 92 | 'http://www.w3.org/2000/svg', 93 | 'feDropShadow' 94 | ); 95 | dropShadowElement.setAttribute( 96 | 'dx', 97 | this._renderProperties.dropShadow.dx.toString() 98 | ); 99 | dropShadowElement.setAttribute( 100 | 'dy', 101 | this._renderProperties.dropShadow.dy.toString() 102 | ); 103 | dropShadowElement.setAttribute( 104 | 'stdDeviation', 105 | this._renderProperties.dropShadow.stdDeviation.toString() 106 | ); 107 | filterElement.appendChild(dropShadowElement); 108 | defsElement.appendChild(filterElement); 109 | profileLayerElement.setAttribute('filter', 'url(#board-shadow)'); 110 | } 111 | svgElement.appendChild(defsElement); 112 | 113 | const layers = pcb.content.layerRefs 114 | .map((x) => x.name) 115 | .map((x) => renderContext.reusablesProvider.getLayerByName(x)) 116 | .filter( 117 | (layer) => layer.side === side || layer.layerFunction === 'DRILL' 118 | ); 119 | const conductorLayers = layers.filter( 120 | (layer) => layer.layerFunction === 'CONDUCTOR' 121 | ); 122 | const componentLayers = conductorLayers.map((conductorLayer) => { 123 | const componentLayer = new Layer(); 124 | componentLayer.name = `${conductorLayer.name}`; 125 | componentLayer.layerFunction = 'COMPONENT'; 126 | return componentLayer; 127 | }); 128 | const drillLayers = layers.filter( 129 | (layer) => layer.layerFunction === 'DRILL' 130 | ); 131 | const silkscreenLayers = layers.filter( 132 | (layer) => layer.layerFunction === 'SILKSCREEN' 133 | ); 134 | console.table([ 135 | { layerType: 'CONDUCTOR', count: conductorLayers.length }, 136 | { layerType: 'DRILL', count: drillLayers.length }, 137 | { layerType: 'SILKSCREEN', count: silkscreenLayers.length }, 138 | { layerType: 'COMPONENT', count: silkscreenLayers.length }, 139 | ]); 140 | 141 | const conductorLayerElements = conductorLayers 142 | .map( 143 | (layer) => 144 | new ConductorLayer( 145 | layer, 146 | step, 147 | renderContext, 148 | step.layerFeatures.find((x) => x.layerRef === layer.name)! 149 | ) 150 | ) 151 | .map<[Layer, SVGElement]>((x) => [x.layer, x.render()]); 152 | 153 | const componentLayerElements = componentLayers 154 | .map((layer) => new ComponentLayer(layer, step, renderContext)) 155 | .map<[Layer, SVGElement]>((x) => [x.layer, x.render()]); 156 | 157 | const drillLayerElements = drillLayers 158 | .map( 159 | (layer) => 160 | new DrillLayer( 161 | layer, 162 | step, 163 | renderContext, 164 | step.layerFeatures.find((x) => x.layerRef === layer.name)!, 165 | bounds 166 | ) 167 | ) 168 | .map<[Layer, SVGElement, DrillLayer]>((x) => [x.layer, x.render(), x]); 169 | 170 | const silkscreenLayerElements = silkscreenLayers 171 | .map( 172 | (layer) => 173 | new SilkscreenLayer( 174 | layer, 175 | step, 176 | renderContext, 177 | step.layerFeatures.find((x) => x.layerRef === layer.name)! 178 | ) 179 | ) 180 | .map<[Layer, SVGElement]>((x) => [x.layer, x.render()]); 181 | 182 | for (const drillLayer of drillLayerElements.map((x) => x[2])) { 183 | drillLayer.apply([ 184 | [profileLayer, profileLayerElement], 185 | ...conductorLayerElements, 186 | ...componentLayerElements, 187 | ...silkscreenLayerElements, 188 | ]); 189 | } 190 | 191 | svgElement.append( 192 | profileLayerElement, 193 | ...conductorLayerElements.map((x) => x[1]), 194 | ...drillLayerElements.map((x) => x[1]), 195 | ...silkscreenLayerElements.map((x) => x[1]), 196 | ...componentLayerElements.map((x) => x[1]) 197 | ); 198 | 199 | return svgElement; 200 | } 201 | } 202 | --------------------------------------------------------------------------------