UNPKG

ecljs

Version:

electric circuits library

353 lines (352 loc) 12.7 kB
import { Rect } from "dabbjs/dist/lib/rect"; import { Type, Base } from "./interfaces"; import { Bond } from "./bonds"; import { Wire } from "./wire"; import { getItem } from "./extra"; import { dP } from "dabbjs/dist/lib/dab"; export class Container extends Base { get selected() { return this.$.selected; } get items() { return Array.from(this.$.itemMap.values()).map(item => item.t); } get wires() { return Array.from(this.$.wireMap.values()).map(item => item.t); } get all() { return this.items.concat(this.wires); } get empty() { return !(this.$.wireMap.size || this.$.itemMap.size); } get size() { return this.$.itemMap.size + this.$.wireMap.size; } get store() { return this.$.store; } /** * @description gets the component * @param id component's id */ get(id) { var _a; return (_a = getItem(this.$, id)) === null || _a === void 0 ? void 0 : _a.t; } /** * @description creates a library component container * @param options configurable options, see below: * * - store: CompStore; component store */ constructor(options) { super(options); //non-configurable properties this.$.counters = {}; this.$.components = new Map(); this.$.itemMap = new Map(); this.$.wireMap = new Map(); this.$.selected = []; } defaults() { return { store: void 0, }; } root(name) { return this.$.components.get(name); } hasItem(id) { return this.$.itemMap.has(id) || this.$.wireMap.has(id); } selectAll(value) { return this.$.selected = this.all .filter(comp => (comp.select(value), value)); } toggleSelect(comp) { comp.select(!comp.selected); this.$.selected = this.all.filter(c => c.selected); } selectThis(comp) { return comp && (this.selectAll(false).push(comp.select(true)), true); } unselectThis(comp) { comp.select(false); this.$.selected = this.all.filter(c => c.selected); } selectRect(rect) { (this.$.selected = this.all.filter((item) => { return rect.intersect(item.rect()); })) .forEach(item => item.select(true)); } deleteSelected() { let deletedCount = 0; this.$.selected = this.selected.filter((c) => { if (this.delete(c)) { deletedCount++; return false; } return true; }); return deletedCount; } destroy() { this.items.forEach(ec => this.delete(ec)); this.wires.forEach(wire => this.delete(wire)); //maps should be empty here this.$ = void 0; } boundariesRect() { let array = this.all, first = array.shift(), r = first ? first.rect() : Rect.empty; array.forEach(ec => r = r.add(ec.rect())); return r.grow(20, 20); } /** * @description adds a new component to this container * @param options disctionary of options */ add(options) { // let // comp: T | Wire = createBoardItem.call(this, this.$, options); // // if (comp.type != Type.WIRE && comp.base.library != this.name) // // throw `component incompatible type`; return createBoardItem(this, this.$, options); } /** * @description deletes a component from the board, and unbonds all nodes * @param comp component */ delete(comp) { if (comp == undefined) return false; let list = this.disconnect(comp); comp.remove(); list.forEach(id => { let nc = this.get(id); nc && (nc.type == Type.WIRE) && this.delete(nc); }); return (comp.type == Type.WIRE) ? this.$.wireMap.delete(comp.id) : this.$.itemMap.delete(comp.id); } /** * @description gets all bonds of a component * @param item component */ itemBonds(item) { var _a, _b; //"bonds" cannot be filtered so array node indexes don't get lost return ((_a = this.$.itemMap.get(item.id)) === null || _a === void 0 ? void 0 : _a.b) || ((_b = this.$.wireMap.get(item.id)) === null || _b === void 0 ? void 0 : _b.b); } /** * @description returns the bonds of a node * @param item board component * @param node 0-based node */ nodeBonds(item, node) { let bonds = this.itemBonds(item); return bonds && bonds[node]; } /** * @description bonds two components two-way * @param thisObj start component * @param node 0-based node * @param ic component to bond to * @param icNode component node */ bond(thisObj, thisNode, ic, icNode) { if (!this.hasItem(thisObj.id) || !this.hasItem(ic.id)) return false; return this.bondOneWay(thisObj, thisNode, ic, icNode, 0) // from A to B && this.bondOneWay(ic, icNode, thisObj, thisNode, 1); // back B to A } bondOneWay(thisObj, thisNode, ic, icNode, dir) { let item = getItem(this.$, thisObj.id), entry = item && item.b[thisNode]; if (!item) return false; if (!ic || (entry && entry.has(ic.id)) || !ic.valid(icNode)) return false; if (entry) { if (!entry.add(ic, icNode)) throw new Error(`duplicated bond`); } else { //this's the origin of the bond entry = new Bond(thisObj, thisNode, ic, icNode, dir); item.b[thisNode] = entry; } item.c++; thisObj.nodeRefresh(thisNode); return true; } /** * @description unbonds a node from a component * @param thisObj component to unbond * @param node 0-base node to unbond * @param id component to unbond from */ unbond(thisObj, node, id) { return unbond(this.$, thisObj.id, node, id, true); } /** * @description unbonds a component node * @param thisObj component to be unbonded * @param node 0-based node * @returns undefined if not bonded, otherwise thisObj::Bond.dir and list of disconnected wire ids */ unbondNode(thisObj, node) { let item = getItem(this.$, thisObj.id), bond = item && item.b[node], link = void 0, list = []; if (!bond || !item) return; //try later to use bond.to.forEach, it was giving an error with wire node selection, think it's fixed while (bond.to.length) { link = bond.to[0]; //arbitrarily unbond a node, no matter its direction, so "origin" must be true to go the other way unbond(this.$, link.id, link.ndx, thisObj.id, true); list.push({ id: link.id, node: link.ndx }); } return { dir: bond.dir, id: thisObj.id, node: node, bonds: list }; } /** * @description removes all bonds of a component * @param comp component to disconnect * @returns list of removed component's id */ disconnect(comp) { let list = []; for (let node = 0; node < comp.count; node++) { let data = this.unbondNode(comp, node); data && data.bonds.forEach(b => list.push(b.id)); } return list; } getAllBonds() { let bonds = [], keyDict = new Set(), findBonds = (bond) => { //always return only the origin Bond if (bond.dir === 0) { let fromId = bond.from.id, fromNdx = bond.from.ndx, keyRoot = `${fromId},${fromNdx}`; bond.to.forEach(b => { let otherRoot = `${b.id},${b.ndx}`, key0 = `${keyRoot},${otherRoot}`; if (!keyDict.has(key0)) { keyDict.add(key0).add(`${otherRoot},${keyRoot}`); bonds.push(key0); } }); } }; this.all .forEach(comp => { var _a; return (_a = comp.bonds) === null || _a === void 0 ? void 0 : _a.forEach(findBonds); }); return bonds; } moveBond(id, node, newIndex) { let item = getItem(this.$, id), wire = item === null || item === void 0 ? void 0 : item.t; if (!item || !wire || wire.type != Type.WIRE) return; let bond = this.nodeBonds(wire, node); if (bond) { //fix this from index bond.from.ndx = newIndex; //fix all incoming indexes bond.to.forEach(bond => { let compTo = wire.container.get(bond.id), compToBonds = compTo && this.nodeBonds(compTo, bond.ndx); compToBonds === null || compToBonds === void 0 ? void 0 : compToBonds.to.filter(b => b.id == wire.id).forEach(b => { b.ndx = newIndex; }); }); //move last bond entry delete item.b[node]; item.b[newIndex] = bond; } } } /** * @description unbonds two components, Comp with Wire, or Two Wires * @param container container * @param id component id * @param node id::node * @param toId the component id belonging to id::bonds * @param origin true to unbond the other way back * @returns BondDir of id Bond is any or undefined for not bonded */ function unbond($, id, node, toId, origin) { let item = getItem($, id), bond = item && item.b[node], b = bond === null || bond === void 0 ? void 0 : bond.remove(toId); if (bond && b && item) { delete item.b[node]; (--item.c == 0) && (item.b = []); if (origin) { unbond($, toId, b.ndx, id, false); } //return [id] bond direction for reference return { dir: bond.dir, id: id, node: node, toId: toId, toNode: b.ndx }; } else return; } function getBaseComp(that, $, name) { let obj = { comp: that.store.find(name) }; if (!obj.comp) throw new Error(`unregistered component: ${name}`); if ((obj.tmpl = obj.comp.meta.nameTmpl)) { dP(obj, "count", { get() { return $.counters[obj.tmpl]; }, set(val) { $.counters[obj.tmpl] = val; } }); isNaN(obj.count) && (obj.count = 0); } else obj.count = 0; return obj; } /** * @description creates a board component * @param container container * @param options options to create component */ function createBoardItem(that, $, options) { let base = void 0, item = void 0, setBase = () => { if (!(base = that.root(options.name))) { base = getBaseComp(that, $, options.name); $.components.set(options.name, base); } options.base = base.comp; }; if (options.id) { //this comes from a file read, get id counter //get name from id let match = (/^([\w-]+)(\d+)$/g).exec(options.id), count = 0; if (match == null) throw new Error(`invalid id: ${options.id}`); //name can't contain numbers at the end, // id = name[count] nand1 7408IC2 N555IC7 doesn't need name // C[count] name could be: capacitor, capacitor-polarized, etc. needs name if (!$.counters[match[1]]) options.name = match[1]; count = parseInt(match[2]); if (count <= 0 || !options.name) throw new Error(`invalid id: ${options.id}`); setBase(); //update internal component counter only if count > internal counter (count > base.count) && (base.count = count); } else if (options.name) { //this creates a component from option's name setBase(); base.count++; if (!base.comp.meta.nameTmpl) options.id = `${options.name}${base.count}`; else { options.id = `${base.comp.meta.nameTmpl}${base.count}`; } } else throw new Error('invalid component options'); !options.onProp && (options.onProp = function () { //this happens when this component is created... }); if (options.name == "wire") { item = new Wire(that, options); if ($.wireMap.has(item.id)) throw new Error('duplicated connector'); $.wireMap.set(item.id, { t: item, b: [], c: 0 }); } else { options.type = base.comp.type; item = that.createItem(options); if ($.itemMap.has(item.id)) throw new Error(`duplicated component: ${item.id}`); $.itemMap.set(item.id, { t: item, b: [], c: 0 }); } return item; }