moving wiring to pxt
This commit is contained in:
		
							
								
								
									
										672
									
								
								sim/allocator.ts
									
									
									
									
									
								
							
							
						
						
									
										672
									
								
								sim/allocator.ts
									
									
									
									
									
								
							@@ -1,672 +0,0 @@
 | 
				
			|||||||
namespace pxsim {
 | 
					 | 
				
			||||||
    const GROUND_COLOR = "blue";
 | 
					 | 
				
			||||||
    const POWER_COLOR = "red";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
     export interface AllocatorOpts {
 | 
					 | 
				
			||||||
        boardDef: BoardDefinition,
 | 
					 | 
				
			||||||
        partDefs: Map<PartDefinition>,
 | 
					 | 
				
			||||||
        partsList: string[]
 | 
					 | 
				
			||||||
        fnArgs: any,
 | 
					 | 
				
			||||||
        // Used for finding the nearest available power pins
 | 
					 | 
				
			||||||
        getBBCoord: (loc: BBLoc) => visuals.Coord,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    export interface AllocatorResult {
 | 
					 | 
				
			||||||
        partsAndWires: PartAndWiresInst[],
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export interface PartInst {
 | 
					 | 
				
			||||||
        name: string,
 | 
					 | 
				
			||||||
        simulationBehavior?: string,
 | 
					 | 
				
			||||||
        visual: PartVisualDefinition,
 | 
					 | 
				
			||||||
        bbFit: PartBBFit,
 | 
					 | 
				
			||||||
        startColumnIdx: number,
 | 
					 | 
				
			||||||
        startRowIdx: number,
 | 
					 | 
				
			||||||
        breadboardConnections: BBLoc[],
 | 
					 | 
				
			||||||
        params: Map<string>,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export interface WireInst {
 | 
					 | 
				
			||||||
        start: Loc,
 | 
					 | 
				
			||||||
        end: Loc,
 | 
					 | 
				
			||||||
        color: string,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    export interface AssemblyStep {
 | 
					 | 
				
			||||||
        part?: boolean,
 | 
					 | 
				
			||||||
        wireIndices?: number[],
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export interface PartAndWiresInst {
 | 
					 | 
				
			||||||
        part?: PartInst,
 | 
					 | 
				
			||||||
        wires?: WireInst[],
 | 
					 | 
				
			||||||
        assembly: AssemblyStep[],
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export interface PartBBFit {
 | 
					 | 
				
			||||||
        xOffset: number,
 | 
					 | 
				
			||||||
        yOffset: number,
 | 
					 | 
				
			||||||
        rowCount: number,
 | 
					 | 
				
			||||||
        colCount: number,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    interface PinBBFit {
 | 
					 | 
				
			||||||
        partRelativeColIdx: number,
 | 
					 | 
				
			||||||
        partRelativeRowIdx: number,
 | 
					 | 
				
			||||||
        xOffset: number,
 | 
					 | 
				
			||||||
        yOffset: number,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    interface PinIR {
 | 
					 | 
				
			||||||
        loc: XY,
 | 
					 | 
				
			||||||
        def: PartPinDefinition,
 | 
					 | 
				
			||||||
        target: PinTarget,
 | 
					 | 
				
			||||||
        bbFit: PinBBFit,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    interface PartIR {
 | 
					 | 
				
			||||||
        name: string,
 | 
					 | 
				
			||||||
        def: PartDefinition,
 | 
					 | 
				
			||||||
        partParams: Map<string>,
 | 
					 | 
				
			||||||
        pins: PinIR[],
 | 
					 | 
				
			||||||
        bbFit: PartBBFit,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    interface PartPlacement extends PartIR {
 | 
					 | 
				
			||||||
        startColumnIdx: number,
 | 
					 | 
				
			||||||
        startRowIdx: number,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    type WireIRLoc = PinTarget | BBLoc;
 | 
					 | 
				
			||||||
    interface WireIR {
 | 
					 | 
				
			||||||
        pinIdx: number,
 | 
					 | 
				
			||||||
        start: WireIRLoc,
 | 
					 | 
				
			||||||
        end: WireIRLoc,
 | 
					 | 
				
			||||||
        color: string,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    interface PartIRAndWireIRs extends PartPlacement {
 | 
					 | 
				
			||||||
        wires: WireIR[],
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    interface PowerUsage {
 | 
					 | 
				
			||||||
        topGround: boolean,
 | 
					 | 
				
			||||||
        topThreeVolt: boolean,
 | 
					 | 
				
			||||||
        bottomGround: boolean,
 | 
					 | 
				
			||||||
        bottomThreeVolt: boolean,
 | 
					 | 
				
			||||||
        singleGround: boolean,
 | 
					 | 
				
			||||||
        singleThreeVolt: boolean,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    interface AllocLocOpts {
 | 
					 | 
				
			||||||
        referenceBBPin?: BBLoc,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    interface AllocWireOpts {
 | 
					 | 
				
			||||||
        //TODO: port
 | 
					 | 
				
			||||||
        startColumn: number,
 | 
					 | 
				
			||||||
        partGPIOPins: string[],
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function isOnBreadboardBottom(location: WireIRLoc) {
 | 
					 | 
				
			||||||
        let isBot = false;
 | 
					 | 
				
			||||||
        if (typeof location !== "string" && (<BBLoc>location).type === "breadboard") {
 | 
					 | 
				
			||||||
            let bbLoc = <BBLoc>location;
 | 
					 | 
				
			||||||
            let row = bbLoc.row;
 | 
					 | 
				
			||||||
            isBot = 0 <= ["a", "b", "c", "d", "e"].indexOf(row);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return isBot;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    const arrCount = (a: boolean[]) => a.reduce((p, n) => p + (n ? 1 : 0), 0);
 | 
					 | 
				
			||||||
    const arrAny = (a: boolean[]) => arrCount(a) > 0;
 | 
					 | 
				
			||||||
    function computePowerUsage(wire: WireIR): PowerUsage {
 | 
					 | 
				
			||||||
        let ends = [wire.start, wire.end];
 | 
					 | 
				
			||||||
        let endIsGround = ends.map(e => e === "ground");
 | 
					 | 
				
			||||||
        let endIsThreeVolt = ends.map(e => e === "threeVolt");
 | 
					 | 
				
			||||||
        let endIsBot = ends.map(e => isOnBreadboardBottom(e));
 | 
					 | 
				
			||||||
        let hasGround = arrAny(endIsGround);
 | 
					 | 
				
			||||||
        let hasThreeVolt = arrAny(endIsThreeVolt);
 | 
					 | 
				
			||||||
        let hasBot = arrAny(endIsBot);
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            topGround: hasGround && !hasBot,
 | 
					 | 
				
			||||||
            topThreeVolt: hasThreeVolt && !hasBot,
 | 
					 | 
				
			||||||
            bottomGround: hasGround && hasBot,
 | 
					 | 
				
			||||||
            bottomThreeVolt: hasThreeVolt && hasBot,
 | 
					 | 
				
			||||||
            singleGround: hasGround,
 | 
					 | 
				
			||||||
            singleThreeVolt: hasThreeVolt
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function mergePowerUsage(powerUsages: PowerUsage[]) {
 | 
					 | 
				
			||||||
        let finalPowerUsage = powerUsages.reduce((p, n) => ({
 | 
					 | 
				
			||||||
                topGround: p.topGround || n.topGround,
 | 
					 | 
				
			||||||
                topThreeVolt: p.topThreeVolt || n.topThreeVolt,
 | 
					 | 
				
			||||||
                bottomGround: p.bottomGround || n.bottomGround,
 | 
					 | 
				
			||||||
                bottomThreeVolt: p.bottomThreeVolt || n.bottomThreeVolt,
 | 
					 | 
				
			||||||
                singleGround: n.singleGround ? p.singleGround === null : p.singleGround,
 | 
					 | 
				
			||||||
                singleThreeVolt: n.singleThreeVolt ? p.singleThreeVolt === null : p.singleThreeVolt,
 | 
					 | 
				
			||||||
            }), {
 | 
					 | 
				
			||||||
                topGround: false,
 | 
					 | 
				
			||||||
                topThreeVolt: false,
 | 
					 | 
				
			||||||
                bottomGround: false,
 | 
					 | 
				
			||||||
                bottomThreeVolt: false,
 | 
					 | 
				
			||||||
                singleGround: null,
 | 
					 | 
				
			||||||
                singleThreeVolt: null,
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        if (finalPowerUsage.singleGround)
 | 
					 | 
				
			||||||
            finalPowerUsage.topGround = finalPowerUsage.bottomGround = false;
 | 
					 | 
				
			||||||
        if (finalPowerUsage.singleThreeVolt)
 | 
					 | 
				
			||||||
            finalPowerUsage.topThreeVolt = finalPowerUsage.bottomThreeVolt = false;
 | 
					 | 
				
			||||||
        return finalPowerUsage;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function copyDoubleArray(a: string[][]) {
 | 
					 | 
				
			||||||
         return a.map(b => b.map(p => p));
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function merge2<A, B>(a: A, b: B): A & B {
 | 
					 | 
				
			||||||
        let res: any = {};
 | 
					 | 
				
			||||||
        for (let aKey in a)
 | 
					 | 
				
			||||||
            res[aKey] = (<any>a)[aKey];
 | 
					 | 
				
			||||||
        for (let bKey in b)
 | 
					 | 
				
			||||||
            res[bKey] = (<any>b)[bKey];
 | 
					 | 
				
			||||||
        return <A & B>res;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function merge3<A, B, C>(a: A, b: B, c: C): A & B & C {
 | 
					 | 
				
			||||||
        return merge2(merge2(a, b), c);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function readPin(arg: string): MicrobitPin {
 | 
					 | 
				
			||||||
        U.assert(!!arg, "Invalid pin: " + arg);
 | 
					 | 
				
			||||||
        let pin = arg.split("DigitalPin.")[1];
 | 
					 | 
				
			||||||
        return <MicrobitPin>pin;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function mkReverseMap(map: {[key: string]: string}) {
 | 
					 | 
				
			||||||
        let origKeys: string[] = [];
 | 
					 | 
				
			||||||
        let origVals: string[] = [];
 | 
					 | 
				
			||||||
        for (let key in map) {
 | 
					 | 
				
			||||||
            origKeys.push(key);
 | 
					 | 
				
			||||||
            origVals.push(map[key]);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        let newMap: {[key: string]: string} = {};
 | 
					 | 
				
			||||||
        for (let i = 0; i < origKeys.length; i++) {
 | 
					 | 
				
			||||||
            let newKey = origVals[i];
 | 
					 | 
				
			||||||
            let newVal = origKeys[i];
 | 
					 | 
				
			||||||
            newMap[newKey] = newVal;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return newMap;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function isConnectedToBB(pin: PartPinDefinition): boolean {
 | 
					 | 
				
			||||||
        return pin.orientation === "-Z" && pin.style === "male";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    class Allocator {
 | 
					 | 
				
			||||||
        //TODO: better handling of allocation errors
 | 
					 | 
				
			||||||
        private opts: AllocatorOpts;
 | 
					 | 
				
			||||||
        private availablePowerPins = {
 | 
					 | 
				
			||||||
            top: {
 | 
					 | 
				
			||||||
                threeVolt: mkRange(26, 51).map(n => <BBLoc>{type: "breadboard", row: "+", col: `${n}`}),
 | 
					 | 
				
			||||||
                ground: mkRange(26, 51).map(n => <BBLoc>{type: "breadboard", row: "-", col: `${n}`}),
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
            bottom: {
 | 
					 | 
				
			||||||
                threeVolt: mkRange(1, 26).map(n => <BBLoc>{type: "breadboard", row: "+", col: `${n}`}),
 | 
					 | 
				
			||||||
                ground: mkRange(1, 26).map(n => <BBLoc>{type: "breadboard", row: "-", col: `${n}`}),
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        private powerUsage: PowerUsage;
 | 
					 | 
				
			||||||
        private availableWireColors: string[];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        constructor(opts: AllocatorOpts) {
 | 
					 | 
				
			||||||
            this.opts = opts;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private allocPartIRs(def: PartDefinition, name: string, bbFit: PartBBFit): PartIR[] {
 | 
					 | 
				
			||||||
            let partIRs: PartIR[] = [];
 | 
					 | 
				
			||||||
            let mkIR = (def: PartDefinition, name: string, instPins?: PinTarget[], partParams?: Map<string>): PartIR => {
 | 
					 | 
				
			||||||
                let pinIRs: PinIR[] = [];
 | 
					 | 
				
			||||||
                for (let i = 0; i < def.numberOfPins; i++) {
 | 
					 | 
				
			||||||
                    let pinDef = def.pinDefinitions[i];
 | 
					 | 
				
			||||||
                    let pinTarget: PinTarget;
 | 
					 | 
				
			||||||
                    if (typeof pinDef.target === "string") {
 | 
					 | 
				
			||||||
                        pinTarget = <PinTarget>pinDef.target;
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        let instIdx = (<PinInstantiationIdx>pinDef.target).pinInstantiationIdx;
 | 
					 | 
				
			||||||
                        U.assert(!!instPins && instPins[instIdx] !== undefined,
 | 
					 | 
				
			||||||
                            `No pin found for PinInstantiationIdx: ${instIdx}. (Is the part missing an ArguementRole or "trackArgs=" annotations?)`);
 | 
					 | 
				
			||||||
                        pinTarget = instPins[instIdx];
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                    let pinLoc = def.visual.pinLocations[i];
 | 
					 | 
				
			||||||
                    let adjustedY = bbFit.yOffset + pinLoc.y;
 | 
					 | 
				
			||||||
                    let relativeRowIdx = Math.round(adjustedY / def.visual.pinDistance);
 | 
					 | 
				
			||||||
                    let relativeYOffset = adjustedY - relativeRowIdx * def.visual.pinDistance;
 | 
					 | 
				
			||||||
                    let adjustedX = bbFit.xOffset + pinLoc.x;
 | 
					 | 
				
			||||||
                    let relativeColIdx = Math.round(adjustedX / def.visual.pinDistance);
 | 
					 | 
				
			||||||
                    let relativeXOffset = adjustedX - relativeColIdx * def.visual.pinDistance;
 | 
					 | 
				
			||||||
                    let pinBBFit: PinBBFit = {
 | 
					 | 
				
			||||||
                        partRelativeRowIdx: relativeRowIdx,
 | 
					 | 
				
			||||||
                        partRelativeColIdx: relativeColIdx,
 | 
					 | 
				
			||||||
                        xOffset: relativeXOffset,
 | 
					 | 
				
			||||||
                        yOffset: relativeYOffset
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                    pinIRs.push({
 | 
					 | 
				
			||||||
                        def: pinDef,
 | 
					 | 
				
			||||||
                        loc: pinLoc,
 | 
					 | 
				
			||||||
                        target: pinTarget,
 | 
					 | 
				
			||||||
                        bbFit: pinBBFit,
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return {
 | 
					 | 
				
			||||||
                    name: name,
 | 
					 | 
				
			||||||
                    def: def,
 | 
					 | 
				
			||||||
                    pins: pinIRs,
 | 
					 | 
				
			||||||
                    partParams: partParams || {},
 | 
					 | 
				
			||||||
                    bbFit: bbFit
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            if (def.instantiation.kind === "singleton") {
 | 
					 | 
				
			||||||
                partIRs.push(mkIR(def, name));
 | 
					 | 
				
			||||||
            } else if (def.instantiation.kind === "function") {
 | 
					 | 
				
			||||||
                let fnAlloc = def.instantiation as PartFunctionDefinition;
 | 
					 | 
				
			||||||
                let fnNm = fnAlloc.fullyQualifiedName;
 | 
					 | 
				
			||||||
                let callsitesTrackedArgs = <string[]>this.opts.fnArgs[fnNm];
 | 
					 | 
				
			||||||
                U.assert(!!callsitesTrackedArgs && !!callsitesTrackedArgs.length, "Failed to read pin(s) from callsite for: " + fnNm);
 | 
					 | 
				
			||||||
                callsitesTrackedArgs.forEach(fnArgsStr => {
 | 
					 | 
				
			||||||
                    let fnArgsSplit = fnArgsStr.split(",");
 | 
					 | 
				
			||||||
                    U.assert(fnArgsSplit.length === fnAlloc.argumentRoles.length,
 | 
					 | 
				
			||||||
                        `Mismatch between number of arguments at callsite (function name: ${fnNm}) vs number of argument roles in part definition (part: ${name}).`);
 | 
					 | 
				
			||||||
                    let instPins: PinTarget[] = [];
 | 
					 | 
				
			||||||
                    let paramArgs: Map<string> = {};
 | 
					 | 
				
			||||||
                    fnArgsSplit.forEach((arg, idx) => {
 | 
					 | 
				
			||||||
                        let role = fnAlloc.argumentRoles[idx];
 | 
					 | 
				
			||||||
                        if (role.partParameter !== undefined) {
 | 
					 | 
				
			||||||
                            paramArgs[role.partParameter] = arg;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                        if (role.pinInstantiationIdx !== undefined) {
 | 
					 | 
				
			||||||
                            let instIdx = role.pinInstantiationIdx;
 | 
					 | 
				
			||||||
                            let pin = readPin(arg);
 | 
					 | 
				
			||||||
                            instPins[instIdx] = pin;
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                    partIRs.push(mkIR(def, name, instPins, paramArgs));
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return partIRs;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private computePartDimensions(def: PartDefinition, name: string): PartBBFit {
 | 
					 | 
				
			||||||
            let pinLocs = def.visual.pinLocations;
 | 
					 | 
				
			||||||
            let pinDefs = def.pinDefinitions;
 | 
					 | 
				
			||||||
            let numPins = def.numberOfPins;
 | 
					 | 
				
			||||||
            U.assert(pinLocs.length === numPins, `Mismatch between "numberOfPins" and length of "visual.pinLocations" for "${name}"`);
 | 
					 | 
				
			||||||
            U.assert(pinDefs.length === numPins, `Mismatch between "numberOfPins" and length of "pinDefinitions" for "${name}"`);
 | 
					 | 
				
			||||||
            U.assert(numPins > 0, `Part "${name}" has no pins`);
 | 
					 | 
				
			||||||
            let pins = pinLocs.map((loc, idx) => merge3({idx: idx}, loc, pinDefs[idx]));
 | 
					 | 
				
			||||||
            let bbPins = pins.filter(p => p.orientation === "-Z");
 | 
					 | 
				
			||||||
            let hasBBPins = bbPins.length > 0;
 | 
					 | 
				
			||||||
            let pinDist = def.visual.pinDistance;
 | 
					 | 
				
			||||||
            let xOff: number;
 | 
					 | 
				
			||||||
            let yOff: number;
 | 
					 | 
				
			||||||
            let colCount: number;
 | 
					 | 
				
			||||||
            let rowCount: number;
 | 
					 | 
				
			||||||
            if (hasBBPins) {
 | 
					 | 
				
			||||||
                let refPin = bbPins[0];
 | 
					 | 
				
			||||||
                let refPinColIdx = Math.ceil(refPin.x / pinDist);
 | 
					 | 
				
			||||||
                let refPinRowIdx = Math.ceil(refPin.y / pinDist);
 | 
					 | 
				
			||||||
                xOff = refPinColIdx * pinDist - refPin.x;
 | 
					 | 
				
			||||||
                yOff = refPinRowIdx * pinDist - refPin.y;
 | 
					 | 
				
			||||||
                colCount = Math.ceil((xOff + def.visual.width) / pinDist) + 1;
 | 
					 | 
				
			||||||
                rowCount = Math.ceil((yOff + def.visual.height) / pinDist) + 1;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                colCount = Math.ceil(def.visual.width / pinDist);
 | 
					 | 
				
			||||||
                rowCount = Math.ceil(def.visual.height / pinDist);
 | 
					 | 
				
			||||||
                xOff = colCount * pinDist - def.visual.width;
 | 
					 | 
				
			||||||
                yOff = rowCount * pinDist - def.visual.height;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return {
 | 
					 | 
				
			||||||
                xOffset: xOff,
 | 
					 | 
				
			||||||
                yOffset: yOff,
 | 
					 | 
				
			||||||
                rowCount: rowCount,
 | 
					 | 
				
			||||||
                colCount: colCount
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private allocColumns(colCounts: {colCount: number}[]): number[] {
 | 
					 | 
				
			||||||
            let partsCount = colCounts.length;
 | 
					 | 
				
			||||||
            const totalColumnsCount = visuals.BREADBOARD_MID_COLS; //TODO allow multiple breadboards
 | 
					 | 
				
			||||||
            let totalSpaceNeeded = colCounts.map(d => d.colCount).reduce((p, n) => p + n, 0);
 | 
					 | 
				
			||||||
            let extraSpace = totalColumnsCount - totalSpaceNeeded;
 | 
					 | 
				
			||||||
            if (extraSpace <= 0) {
 | 
					 | 
				
			||||||
                console.log("Not enough breadboard space!");
 | 
					 | 
				
			||||||
                //TODO
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            let padding = Math.floor(extraSpace / (partsCount - 1 + 2));
 | 
					 | 
				
			||||||
            let partSpacing = padding; //Math.floor(extraSpace/(partsCount-1));
 | 
					 | 
				
			||||||
            let totalPartPadding = extraSpace - partSpacing * (partsCount - 1);
 | 
					 | 
				
			||||||
            let leftPadding = Math.floor(totalPartPadding / 2);
 | 
					 | 
				
			||||||
            let rightPadding = Math.ceil(totalPartPadding / 2);
 | 
					 | 
				
			||||||
            let nextAvailableCol = 1 + leftPadding;
 | 
					 | 
				
			||||||
            let partStartCol = colCounts.map(part => {
 | 
					 | 
				
			||||||
                let col = nextAvailableCol;
 | 
					 | 
				
			||||||
                nextAvailableCol += part.colCount + partSpacing;
 | 
					 | 
				
			||||||
                return col;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            return partStartCol;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private placeParts(parts: PartIR[]): PartPlacement[] {
 | 
					 | 
				
			||||||
            const totalRowsCount = visuals.BREADBOARD_MID_ROWS + 2; // 10 letters + 2 for the middle gap
 | 
					 | 
				
			||||||
            let startColumnIndices = this.allocColumns(parts.map(p => p.bbFit));
 | 
					 | 
				
			||||||
            let startRowIndicies = parts.map(p => {
 | 
					 | 
				
			||||||
                let extraRows = totalRowsCount - p.bbFit.rowCount;
 | 
					 | 
				
			||||||
                let topPad = Math.floor(extraRows / 2);
 | 
					 | 
				
			||||||
                let startIdx = topPad;
 | 
					 | 
				
			||||||
                if (startIdx > 4)
 | 
					 | 
				
			||||||
                    startIdx = 4;
 | 
					 | 
				
			||||||
                if (startIdx < 1)
 | 
					 | 
				
			||||||
                    startIdx = 1;
 | 
					 | 
				
			||||||
                return startIdx;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            let placements = parts.map((p, idx) => {
 | 
					 | 
				
			||||||
                let row = startRowIndicies[idx];
 | 
					 | 
				
			||||||
                let col = startColumnIndices[idx];
 | 
					 | 
				
			||||||
                return merge2({startColumnIdx: col, startRowIdx: row}, p);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            return placements;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private nextColor(): string {
 | 
					 | 
				
			||||||
            if (!this.availableWireColors || this.availableWireColors.length <= 0) {
 | 
					 | 
				
			||||||
                this.availableWireColors = visuals.GPIO_WIRE_COLORS.map(c => c);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return this.availableWireColors.pop();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private allocWireIRs(part: PartPlacement): PartIRAndWireIRs {
 | 
					 | 
				
			||||||
            let groupToColor: string[] = [];
 | 
					 | 
				
			||||||
            let wires: WireIR[] = part.pins.map((pin, pinIdx) => {
 | 
					 | 
				
			||||||
                let end = pin.target;
 | 
					 | 
				
			||||||
                let start: WireIRLoc;
 | 
					 | 
				
			||||||
                let colIdx = part.startColumnIdx + pin.bbFit.partRelativeColIdx;
 | 
					 | 
				
			||||||
                let colName = visuals.getColumnName(colIdx);
 | 
					 | 
				
			||||||
                let pinRowIdx = part.startRowIdx + pin.bbFit.partRelativeRowIdx;
 | 
					 | 
				
			||||||
                if (pinRowIdx >= 7) //account for middle gap
 | 
					 | 
				
			||||||
                    pinRowIdx -= 2;
 | 
					 | 
				
			||||||
                if (isConnectedToBB(pin.def)) {
 | 
					 | 
				
			||||||
                    //make a wire from bb top or bottom to target
 | 
					 | 
				
			||||||
                    let connectedToTop = pinRowIdx < 5;
 | 
					 | 
				
			||||||
                    let rowName = connectedToTop ? "j" : "a";
 | 
					 | 
				
			||||||
                    start = {
 | 
					 | 
				
			||||||
                        type: "breadboard",
 | 
					 | 
				
			||||||
                        row: rowName,
 | 
					 | 
				
			||||||
                        col: colName,
 | 
					 | 
				
			||||||
                    };
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    //make a wire directly from pin to target
 | 
					 | 
				
			||||||
                    let rowName = visuals.getRowName(pinRowIdx);
 | 
					 | 
				
			||||||
                    start = {
 | 
					 | 
				
			||||||
                        type: "breadboard",
 | 
					 | 
				
			||||||
                        row: rowName,
 | 
					 | 
				
			||||||
                        col: colName,
 | 
					 | 
				
			||||||
                        xOffset: pin.bbFit.xOffset / part.def.visual.pinDistance,
 | 
					 | 
				
			||||||
                        yOffset: pin.bbFit.yOffset / part.def.visual.pinDistance
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                let color: string;
 | 
					 | 
				
			||||||
                if (end === "ground") {
 | 
					 | 
				
			||||||
                    color = GROUND_COLOR;
 | 
					 | 
				
			||||||
                } else if (end === "threeVolt") {
 | 
					 | 
				
			||||||
                    color = POWER_COLOR;
 | 
					 | 
				
			||||||
                } else if (typeof pin.def.colorGroup === "number") {
 | 
					 | 
				
			||||||
                    if (groupToColor[pin.def.colorGroup]) {
 | 
					 | 
				
			||||||
                        color = groupToColor[pin.def.colorGroup];
 | 
					 | 
				
			||||||
                    } else {
 | 
					 | 
				
			||||||
                        color = groupToColor[pin.def.colorGroup] = this.nextColor();
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    color = this.nextColor()
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return {
 | 
					 | 
				
			||||||
                    start: start,
 | 
					 | 
				
			||||||
                    end: end,
 | 
					 | 
				
			||||||
                    color: color,
 | 
					 | 
				
			||||||
                    pinIdx: pinIdx,
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            return merge2(part, {wires: wires});
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private allocLocation(location: WireIRLoc, opts: AllocLocOpts): Loc {
 | 
					 | 
				
			||||||
            if (location === "ground" || location === "threeVolt") {
 | 
					 | 
				
			||||||
                //special case if there is only a single ground or three volt pin in the whole build
 | 
					 | 
				
			||||||
                if (location === "ground" && this.powerUsage.singleGround) {
 | 
					 | 
				
			||||||
                    let boardGroundPin = this.getBoardGroundPin();
 | 
					 | 
				
			||||||
                    return {type: "dalboard", pin: boardGroundPin};
 | 
					 | 
				
			||||||
                } else if (location === "threeVolt" && this.powerUsage.singleThreeVolt) {
 | 
					 | 
				
			||||||
                    let boardThreeVoltPin = this.getBoardThreeVoltPin();
 | 
					 | 
				
			||||||
                    return {type: "dalboard", pin: boardThreeVoltPin};
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                U.assert(!!opts.referenceBBPin);
 | 
					 | 
				
			||||||
                let nearestCoord = this.opts.getBBCoord(opts.referenceBBPin);
 | 
					 | 
				
			||||||
                let firstTopAndBot = [
 | 
					 | 
				
			||||||
                    this.availablePowerPins.top.ground[0] || this.availablePowerPins.top.threeVolt[0],
 | 
					 | 
				
			||||||
                    this.availablePowerPins.bottom.ground[0] || this.availablePowerPins.bottom.threeVolt[0]
 | 
					 | 
				
			||||||
                ].map(loc => {
 | 
					 | 
				
			||||||
                    return this.opts.getBBCoord(loc);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                if (!firstTopAndBot[0] || !firstTopAndBot[1]) {
 | 
					 | 
				
			||||||
                    console.debug(`No more available "${location}" locations!`);
 | 
					 | 
				
			||||||
                    //TODO
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                let nearTop = visuals.findClosestCoordIdx(nearestCoord, firstTopAndBot) == 0;
 | 
					 | 
				
			||||||
                let barPins: BBLoc[];
 | 
					 | 
				
			||||||
                if (nearTop) {
 | 
					 | 
				
			||||||
                    if (location === "ground") {
 | 
					 | 
				
			||||||
                        barPins = this.availablePowerPins.top.ground;
 | 
					 | 
				
			||||||
                    } else if (location === "threeVolt") {
 | 
					 | 
				
			||||||
                        barPins = this.availablePowerPins.top.threeVolt;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    if (location === "ground") {
 | 
					 | 
				
			||||||
                        barPins = this.availablePowerPins.bottom.ground;
 | 
					 | 
				
			||||||
                    } else if (location === "threeVolt") {
 | 
					 | 
				
			||||||
                        barPins = this.availablePowerPins.bottom.threeVolt;
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                let pinCoords = barPins.map(rowCol => {
 | 
					 | 
				
			||||||
                    return this.opts.getBBCoord(rowCol);
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                let closestPinIdx = visuals.findClosestCoordIdx(nearestCoord, pinCoords);
 | 
					 | 
				
			||||||
                let pin = barPins[closestPinIdx];
 | 
					 | 
				
			||||||
                if (nearTop) {
 | 
					 | 
				
			||||||
                    this.availablePowerPins.top.ground.splice(closestPinIdx, 1);
 | 
					 | 
				
			||||||
                    this.availablePowerPins.top.threeVolt.splice(closestPinIdx, 1);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    this.availablePowerPins.bottom.ground.splice(closestPinIdx, 1);
 | 
					 | 
				
			||||||
                    this.availablePowerPins.bottom.threeVolt.splice(closestPinIdx, 1);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                return pin;
 | 
					 | 
				
			||||||
            } else if ((<BBLoc>location).type === "breadboard") {
 | 
					 | 
				
			||||||
                return <BBLoc>location;
 | 
					 | 
				
			||||||
            } else if (location === "MOSI" || location === "MISO" || location === "SCK") {
 | 
					 | 
				
			||||||
                if (!this.opts.boardDef.spiPins)
 | 
					 | 
				
			||||||
                    console.debug("No SPI pin mappings found!");
 | 
					 | 
				
			||||||
                let pin = (<any>this.opts.boardDef.spiPins)[location as string] as string;
 | 
					 | 
				
			||||||
                return {type: "dalboard", pin: pin};
 | 
					 | 
				
			||||||
            } else if (location === "SDA" || location === "SCL") {
 | 
					 | 
				
			||||||
                if (!this.opts.boardDef.i2cPins)
 | 
					 | 
				
			||||||
                    console.debug("No I2C pin mappings found!");
 | 
					 | 
				
			||||||
                let pin = (<any>this.opts.boardDef.i2cPins)[location as string] as string;
 | 
					 | 
				
			||||||
                return {type: "dalboard", pin: pin};
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                //it must be a MicrobitPin
 | 
					 | 
				
			||||||
                U.assert(typeof location === "string", "Unknown location type: " + location);
 | 
					 | 
				
			||||||
                let mbPin = <MicrobitPin>location;
 | 
					 | 
				
			||||||
                let boardPin = this.opts.boardDef.gpioPinMap[mbPin];
 | 
					 | 
				
			||||||
                U.assert(!!boardPin, "Unknown pin: " + location);
 | 
					 | 
				
			||||||
                return {type: "dalboard", pin: boardPin};
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private getBoardGroundPin(): string {
 | 
					 | 
				
			||||||
            let boardGround = this.opts.boardDef.groundPins[0] || null;
 | 
					 | 
				
			||||||
            if (!boardGround) {
 | 
					 | 
				
			||||||
                console.log("No available ground pin on board!");
 | 
					 | 
				
			||||||
                //TODO
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return boardGround;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private getBoardThreeVoltPin(): string {
 | 
					 | 
				
			||||||
            let threeVoltPin = this.opts.boardDef.threeVoltPins[0] || null;
 | 
					 | 
				
			||||||
            if (!threeVoltPin) {
 | 
					 | 
				
			||||||
                console.log("No available 3.3V pin on board!");
 | 
					 | 
				
			||||||
                //TODO
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return threeVoltPin;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private allocPowerWires(powerUsage: PowerUsage): PartAndWiresInst {
 | 
					 | 
				
			||||||
            let boardGroundPin = this.getBoardGroundPin();
 | 
					 | 
				
			||||||
            let threeVoltPin = this.getBoardThreeVoltPin();
 | 
					 | 
				
			||||||
            const topLeft: BBLoc = {type: "breadboard", row: "-", col: "26"};
 | 
					 | 
				
			||||||
            const botLeft: BBLoc = {type: "breadboard", row: "-", col: "1"};
 | 
					 | 
				
			||||||
            const topRight: BBLoc = {type: "breadboard", row: "-", col: "50"};
 | 
					 | 
				
			||||||
            const botRight: BBLoc = {type: "breadboard", row: "-", col: "25"};
 | 
					 | 
				
			||||||
            let top: BBLoc, bot: BBLoc;
 | 
					 | 
				
			||||||
            if (this.opts.boardDef.attachPowerOnRight) {
 | 
					 | 
				
			||||||
                top = topRight;
 | 
					 | 
				
			||||||
                bot = botRight;
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                top = topLeft;
 | 
					 | 
				
			||||||
                bot = botLeft;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            let groundWires: WireInst[] = [];
 | 
					 | 
				
			||||||
            let threeVoltWires: WireInst[] = [];
 | 
					 | 
				
			||||||
            if (powerUsage.bottomGround && powerUsage.topGround) {
 | 
					 | 
				
			||||||
                //bb top - <==> bb bot -
 | 
					 | 
				
			||||||
                groundWires.push({
 | 
					 | 
				
			||||||
                    start: this.allocLocation("ground", {referenceBBPin: top}),
 | 
					 | 
				
			||||||
                    end: this.allocLocation("ground", {referenceBBPin: bot}),
 | 
					 | 
				
			||||||
                    color: GROUND_COLOR,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (powerUsage.topGround) {
 | 
					 | 
				
			||||||
                //board - <==> bb top -
 | 
					 | 
				
			||||||
                groundWires.push({
 | 
					 | 
				
			||||||
                    start: this.allocLocation("ground", {referenceBBPin: top}),
 | 
					 | 
				
			||||||
                    end: {type: "dalboard", pin: boardGroundPin},
 | 
					 | 
				
			||||||
                    color: GROUND_COLOR,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            } else if (powerUsage.bottomGround) {
 | 
					 | 
				
			||||||
                //board - <==> bb bot -
 | 
					 | 
				
			||||||
                groundWires.push({
 | 
					 | 
				
			||||||
                    start: this.allocLocation("ground", {referenceBBPin: bot}),
 | 
					 | 
				
			||||||
                    end: {type: "dalboard", pin: boardGroundPin},
 | 
					 | 
				
			||||||
                    color: GROUND_COLOR,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (powerUsage.bottomThreeVolt && powerUsage.bottomGround) {
 | 
					 | 
				
			||||||
                //bb top + <==> bb bot +
 | 
					 | 
				
			||||||
                threeVoltWires.push({
 | 
					 | 
				
			||||||
                    start: this.allocLocation("threeVolt", {referenceBBPin: top}),
 | 
					 | 
				
			||||||
                    end: this.allocLocation("threeVolt", {referenceBBPin: bot}),
 | 
					 | 
				
			||||||
                    color: POWER_COLOR,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            if (powerUsage.topThreeVolt) {
 | 
					 | 
				
			||||||
                //board + <==> bb top +
 | 
					 | 
				
			||||||
                threeVoltWires.push({
 | 
					 | 
				
			||||||
                    start: this.allocLocation("threeVolt", {referenceBBPin: top}),
 | 
					 | 
				
			||||||
                    end: {type: "dalboard", pin: threeVoltPin},
 | 
					 | 
				
			||||||
                    color: POWER_COLOR,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            } else if (powerUsage.bottomThreeVolt) {
 | 
					 | 
				
			||||||
                //board + <==> bb bot +
 | 
					 | 
				
			||||||
                threeVoltWires.push({
 | 
					 | 
				
			||||||
                    start: this.allocLocation("threeVolt", {referenceBBPin: bot}),
 | 
					 | 
				
			||||||
                    end: {type: "dalboard", pin: threeVoltPin},
 | 
					 | 
				
			||||||
                    color: POWER_COLOR,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            let assembly: AssemblyStep[] = [];
 | 
					 | 
				
			||||||
            if (groundWires.length > 0)
 | 
					 | 
				
			||||||
                assembly.push({wireIndices: groundWires.map((w, i) => i)});
 | 
					 | 
				
			||||||
            let numGroundWires = groundWires.length;
 | 
					 | 
				
			||||||
            if (threeVoltWires.length > 0)
 | 
					 | 
				
			||||||
                assembly.push({wireIndices: threeVoltWires.map((w, i) => i + numGroundWires)});
 | 
					 | 
				
			||||||
            return {
 | 
					 | 
				
			||||||
                wires: groundWires.concat(threeVoltWires),
 | 
					 | 
				
			||||||
                assembly: assembly
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private allocWire(wireIR: WireIR): WireInst {
 | 
					 | 
				
			||||||
            let ends = [wireIR.start, wireIR.end];
 | 
					 | 
				
			||||||
            let endIsPower = ends.map(e => e === "ground" || e === "threeVolt");
 | 
					 | 
				
			||||||
            //allocate non-power first so we know the nearest pin for the power end
 | 
					 | 
				
			||||||
            let endInsts = ends.map((e, idx) => !endIsPower[idx] ? this.allocLocation(e, {}) : null)
 | 
					 | 
				
			||||||
            //allocate power pins closest to the other end of the wire
 | 
					 | 
				
			||||||
            endInsts = endInsts.map((e, idx) => {
 | 
					 | 
				
			||||||
                if (e)
 | 
					 | 
				
			||||||
                    return e;
 | 
					 | 
				
			||||||
                let locInst = <BBLoc>endInsts[1 - idx]; // non-power end
 | 
					 | 
				
			||||||
                let l = this.allocLocation(ends[idx], {
 | 
					 | 
				
			||||||
                    referenceBBPin: locInst,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                return l;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            return {start: endInsts[0], end: endInsts[1], color: wireIR.color};
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private allocPart(ir: PartPlacement): PartInst {
 | 
					 | 
				
			||||||
            let bbConnections = ir.pins
 | 
					 | 
				
			||||||
                .filter(p => isConnectedToBB(p.def))
 | 
					 | 
				
			||||||
                .map(p => {
 | 
					 | 
				
			||||||
                    let rowIdx = ir.startRowIdx + p.bbFit.partRelativeRowIdx;
 | 
					 | 
				
			||||||
                    if (rowIdx >= 7) //account for middle gap
 | 
					 | 
				
			||||||
                        rowIdx -= 2;
 | 
					 | 
				
			||||||
                    let rowName = visuals.getRowName(rowIdx);
 | 
					 | 
				
			||||||
                    let colIdx = ir.startColumnIdx + p.bbFit.partRelativeColIdx;
 | 
					 | 
				
			||||||
                    let colName = visuals.getColumnName(colIdx);
 | 
					 | 
				
			||||||
                    return <BBLoc>{
 | 
					 | 
				
			||||||
                        type: "breadboard",
 | 
					 | 
				
			||||||
                        row: rowName,
 | 
					 | 
				
			||||||
                        col: colName,
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
            let part: PartInst = {
 | 
					 | 
				
			||||||
                name: ir.name,
 | 
					 | 
				
			||||||
                visual: ir.def.visual,
 | 
					 | 
				
			||||||
                bbFit: ir.bbFit,
 | 
					 | 
				
			||||||
                startColumnIdx: ir.startColumnIdx,
 | 
					 | 
				
			||||||
                startRowIdx: ir.startRowIdx,
 | 
					 | 
				
			||||||
                breadboardConnections: bbConnections,
 | 
					 | 
				
			||||||
                params: ir.partParams,
 | 
					 | 
				
			||||||
                simulationBehavior: ir.def.simulationBehavior
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return part;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        public allocAll(): AllocatorResult {
 | 
					 | 
				
			||||||
            let partNmAndDefs = this.opts.partsList
 | 
					 | 
				
			||||||
                .map(partName => {return {name: partName, def: this.opts.partDefs[partName]}})
 | 
					 | 
				
			||||||
                .filter(d => !!d.def);
 | 
					 | 
				
			||||||
            if (partNmAndDefs.length > 0) {
 | 
					 | 
				
			||||||
                let partNmsList = partNmAndDefs.map(p => p.name);
 | 
					 | 
				
			||||||
                let partDefsList = partNmAndDefs.map(p => p.def);
 | 
					 | 
				
			||||||
                let dimensions = partNmAndDefs.map(nmAndPart => this.computePartDimensions(nmAndPart.def, nmAndPart.name));
 | 
					 | 
				
			||||||
                let partIRs: PartIR[] = [];
 | 
					 | 
				
			||||||
                partNmAndDefs.forEach((nmAndDef, idx) => {
 | 
					 | 
				
			||||||
                    let dims = dimensions[idx];
 | 
					 | 
				
			||||||
                    let irs = this.allocPartIRs(nmAndDef.def, nmAndDef.name, dims);
 | 
					 | 
				
			||||||
                    partIRs = partIRs.concat(irs);
 | 
					 | 
				
			||||||
                })
 | 
					 | 
				
			||||||
                let partPlacements = this.placeParts(partIRs);
 | 
					 | 
				
			||||||
                let partsAndWireIRs = partPlacements.map(p => this.allocWireIRs(p));
 | 
					 | 
				
			||||||
                let allWireIRs = partsAndWireIRs.map(p => p.wires).reduce((p, n) => p.concat(n), []);
 | 
					 | 
				
			||||||
                let allPowerUsage = allWireIRs.map(w => computePowerUsage(w));
 | 
					 | 
				
			||||||
                this.powerUsage = mergePowerUsage(allPowerUsage);
 | 
					 | 
				
			||||||
                let basicWires = this.allocPowerWires(this.powerUsage);
 | 
					 | 
				
			||||||
                let partsAndWires: PartAndWiresInst[] = partsAndWireIRs.map((irs, idx) => {
 | 
					 | 
				
			||||||
                    let part = this.allocPart(irs);
 | 
					 | 
				
			||||||
                    let wires = irs.wires.map(w => this.allocWire(w));
 | 
					 | 
				
			||||||
                    let pinIdxToWireIdx: number[] = [];
 | 
					 | 
				
			||||||
                    irs.wires.forEach((wIR, idx) => {
 | 
					 | 
				
			||||||
                        pinIdxToWireIdx[wIR.pinIdx] = idx;
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                    let assembly: AssemblyStep[] = irs.def.assembly.map(stepDef => {
 | 
					 | 
				
			||||||
                        return {
 | 
					 | 
				
			||||||
                            part: stepDef.part,
 | 
					 | 
				
			||||||
                            wireIndices: (stepDef.pinIndices || []).map(i => pinIdxToWireIdx[i])
 | 
					 | 
				
			||||||
                        }
 | 
					 | 
				
			||||||
                    });
 | 
					 | 
				
			||||||
                    return {
 | 
					 | 
				
			||||||
                        part: part,
 | 
					 | 
				
			||||||
                        wires: wires,
 | 
					 | 
				
			||||||
                        assembly: assembly
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                let all = [basicWires].concat(partsAndWires);
 | 
					 | 
				
			||||||
                return {
 | 
					 | 
				
			||||||
                    partsAndWires: all
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                return {
 | 
					 | 
				
			||||||
                    partsAndWires: []
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export function allocateDefinitions(opts: AllocatorOpts): AllocatorResult {
 | 
					 | 
				
			||||||
        return new Allocator(opts).allocAll();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -5,7 +5,7 @@ namespace pxsim {
 | 
				
			|||||||
        id: string;
 | 
					        id: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // the bus
 | 
					        // the bus
 | 
				
			||||||
        bus: EventBus;
 | 
					        bus: pxsim.EventBus;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // state & update logic for component services
 | 
					        // state & update logic for component services
 | 
				
			||||||
        ledMatrixState: LedMatrixState;
 | 
					        ledMatrixState: LedMatrixState;
 | 
				
			||||||
@@ -25,7 +25,7 @@ namespace pxsim {
 | 
				
			|||||||
        constructor() {
 | 
					        constructor() {
 | 
				
			||||||
            super()
 | 
					            super()
 | 
				
			||||||
            this.id = "b" + Math_.random(2147483647);
 | 
					            this.id = "b" + Math_.random(2147483647);
 | 
				
			||||||
            this.bus = new EventBus(runtime);
 | 
					            this.bus = new pxsim.EventBus(runtime);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // components
 | 
					            // components
 | 
				
			||||||
            this.ledMatrixState = new LedMatrixState(runtime);
 | 
					            this.ledMatrixState = new LedMatrixState(runtime);
 | 
				
			||||||
@@ -97,4 +97,37 @@ namespace pxsim {
 | 
				
			|||||||
            return Promise.resolve();
 | 
					            return Promise.resolve();
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export function initRuntimeWithDalBoard() {
 | 
				
			||||||
 | 
					        U.assert(!runtime.board);
 | 
				
			||||||
 | 
					        let b = new DalBoard();
 | 
				
			||||||
 | 
					        runtime.board = b;
 | 
				
			||||||
 | 
					        runtime.postError = (e) => {
 | 
				
			||||||
 | 
					            led.setBrightness(255);
 | 
				
			||||||
 | 
					            let img = board().ledMatrixState.image;
 | 
				
			||||||
 | 
					            img.clear();
 | 
				
			||||||
 | 
					            img.set(0, 4, 255);
 | 
				
			||||||
 | 
					            img.set(1, 3, 255);
 | 
				
			||||||
 | 
					            img.set(2, 3, 255);
 | 
				
			||||||
 | 
					            img.set(3, 3, 255);
 | 
				
			||||||
 | 
					            img.set(4, 4, 255);
 | 
				
			||||||
 | 
					            img.set(0, 0, 255);
 | 
				
			||||||
 | 
					            img.set(1, 0, 255);
 | 
				
			||||||
 | 
					            img.set(0, 1, 255);
 | 
				
			||||||
 | 
					            img.set(1, 1, 255);
 | 
				
			||||||
 | 
					            img.set(3, 0, 255);
 | 
				
			||||||
 | 
					            img.set(4, 0, 255);
 | 
				
			||||||
 | 
					            img.set(3, 1, 255);
 | 
				
			||||||
 | 
					            img.set(4, 1, 255);
 | 
				
			||||||
 | 
					            runtime.updateDisplay();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!pxsim.initCurrentRuntime) {
 | 
				
			||||||
 | 
					        pxsim.initCurrentRuntime = initRuntimeWithDalBoard;
 | 
				
			||||||
 | 
					    }    
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export function board() {
 | 
				
			||||||
 | 
					        return runtime.board as DalBoard;
 | 
				
			||||||
 | 
					    } 
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -1,8 +1,6 @@
 | 
				
			|||||||
/// <reference path="../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
 | 
					/// <reference path="../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
 | 
				
			||||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
 | 
					/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
 | 
				
			||||||
/// <reference path="../node_modules/pxt-core/built/pxtrunner.d.ts"/>
 | 
					/// <reference path="../node_modules/pxt-core/built/pxtrunner.d.ts"/>
 | 
				
			||||||
/// <reference path="visuals/genericboard.ts"/>
 | 
					 | 
				
			||||||
/// <reference path="visuals/wiring.ts"/>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
//HACK: allows instructions.html to access pxtblocks without requiring simulator.html to import blocks as well
 | 
					//HACK: allows instructions.html to access pxtblocks without requiring simulator.html to import blocks as well
 | 
				
			||||||
if (!(<any>window).pxt) (<any>window).pxt = {};
 | 
					if (!(<any>window).pxt) (<any>window).pxt = {};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -226,7 +226,14 @@ namespace pxsim.visuals {
 | 
				
			|||||||
        wireframe?: boolean;
 | 
					        wireframe?: boolean;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const pointerEvents = !!(window as any).PointerEvent ? {
 | 
					    export interface IPointerEvents {
 | 
				
			||||||
 | 
					        up: string,
 | 
				
			||||||
 | 
					        down: string,
 | 
				
			||||||
 | 
					        move: string,
 | 
				
			||||||
 | 
					        leave: string
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    export const pointerEvents = !!(window as any).PointerEvent ? {
 | 
				
			||||||
        up: "pointerup",
 | 
					        up: "pointerup",
 | 
				
			||||||
        down: "pointerdown",
 | 
					        down: "pointerdown",
 | 
				
			||||||
        move: "pointermove",
 | 
					        move: "pointermove",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										271
									
								
								sim/simlib.ts
									
									
									
									
									
								
							
							
						
						
									
										271
									
								
								sim/simlib.ts
									
									
									
									
									
								
							@@ -1,271 +0,0 @@
 | 
				
			|||||||
/// <reference path="../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
 | 
					 | 
				
			||||||
/// <reference path="../node_modules/pxt-core/built/pxtsim.d.ts"/>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace pxsim {
 | 
					 | 
				
			||||||
    export type BoardPin = string;
 | 
					 | 
				
			||||||
    export interface BBLoc {
 | 
					 | 
				
			||||||
        type: "breadboard",
 | 
					 | 
				
			||||||
        row: string,
 | 
					 | 
				
			||||||
        col: string
 | 
					 | 
				
			||||||
        xOffset?: number,
 | 
					 | 
				
			||||||
        yOffset?: number
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    export interface BoardLoc {
 | 
					 | 
				
			||||||
        type: "dalboard",
 | 
					 | 
				
			||||||
        pin: BoardPin
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    export type Loc = BBLoc | BoardLoc;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export function initRuntimeWithDalBoard() {
 | 
					 | 
				
			||||||
        U.assert(!runtime.board);
 | 
					 | 
				
			||||||
        let b = new DalBoard();
 | 
					 | 
				
			||||||
        runtime.board = b;
 | 
					 | 
				
			||||||
        runtime.postError = (e) => {
 | 
					 | 
				
			||||||
            led.setBrightness(255);
 | 
					 | 
				
			||||||
            let img = board().ledMatrixState.image;
 | 
					 | 
				
			||||||
            img.clear();
 | 
					 | 
				
			||||||
            img.set(0, 4, 255);
 | 
					 | 
				
			||||||
            img.set(1, 3, 255);
 | 
					 | 
				
			||||||
            img.set(2, 3, 255);
 | 
					 | 
				
			||||||
            img.set(3, 3, 255);
 | 
					 | 
				
			||||||
            img.set(4, 4, 255);
 | 
					 | 
				
			||||||
            img.set(0, 0, 255);
 | 
					 | 
				
			||||||
            img.set(1, 0, 255);
 | 
					 | 
				
			||||||
            img.set(0, 1, 255);
 | 
					 | 
				
			||||||
            img.set(1, 1, 255);
 | 
					 | 
				
			||||||
            img.set(3, 0, 255);
 | 
					 | 
				
			||||||
            img.set(4, 0, 255);
 | 
					 | 
				
			||||||
            img.set(3, 1, 255);
 | 
					 | 
				
			||||||
            img.set(4, 1, 255);
 | 
					 | 
				
			||||||
            runtime.updateDisplay();
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    if (!pxsim.initCurrentRuntime) {
 | 
					 | 
				
			||||||
        pxsim.initCurrentRuntime = initRuntimeWithDalBoard;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export function board() {
 | 
					 | 
				
			||||||
        return runtime.board as DalBoard;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export function mkRange(a: number, b: number): number[] {
 | 
					 | 
				
			||||||
        let res: number[] = [];
 | 
					 | 
				
			||||||
        for (; a < b; a++)
 | 
					 | 
				
			||||||
            res.push(a);
 | 
					 | 
				
			||||||
        return res;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export function parseQueryString(): (key: string) => string {
 | 
					 | 
				
			||||||
        let qs = window.location.search.substring(1);
 | 
					 | 
				
			||||||
        let getQsVal = (key: string) => decodeURIComponent((qs.split(`${key}=`)[1] || "").split("&")[0] || ""); //.replace(/\+/g, " ");
 | 
					 | 
				
			||||||
        return getQsVal;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace pxsim.visuals {
 | 
					 | 
				
			||||||
    export interface IPointerEvents {
 | 
					 | 
				
			||||||
        up: string,
 | 
					 | 
				
			||||||
        down: string,
 | 
					 | 
				
			||||||
        move: string,
 | 
					 | 
				
			||||||
        leave: string
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export const pointerEvents: IPointerEvents = !!(window as any).PointerEvent ? {
 | 
					 | 
				
			||||||
        up: "pointerup",
 | 
					 | 
				
			||||||
        down: "pointerdown",
 | 
					 | 
				
			||||||
        move: "pointermove",
 | 
					 | 
				
			||||||
        leave: "pointerleave"
 | 
					 | 
				
			||||||
    } : {
 | 
					 | 
				
			||||||
            up: "mouseup",
 | 
					 | 
				
			||||||
            down: "mousedown",
 | 
					 | 
				
			||||||
            move: "mousemove",
 | 
					 | 
				
			||||||
            leave: "mouseleave"
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export function translateEl(el: SVGElement, xy: [number, number]) {
 | 
					 | 
				
			||||||
        //TODO append translation instead of replacing the full transform
 | 
					 | 
				
			||||||
        svg.hydrate(el, { transform: `translate(${xy[0]} ${xy[1]})` });
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export interface ComposeOpts {
 | 
					 | 
				
			||||||
        el1: SVGAndSize<SVGSVGElement>,
 | 
					 | 
				
			||||||
        scaleUnit1: number,
 | 
					 | 
				
			||||||
        el2: SVGAndSize<SVGSVGElement>,
 | 
					 | 
				
			||||||
        scaleUnit2: number,
 | 
					 | 
				
			||||||
        margin: [number, number, number, number],
 | 
					 | 
				
			||||||
        middleMargin: number,
 | 
					 | 
				
			||||||
        maxWidth?: string,
 | 
					 | 
				
			||||||
        maxHeight?: string,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export interface ComposeResult {
 | 
					 | 
				
			||||||
        host: SVGSVGElement,
 | 
					 | 
				
			||||||
        scaleUnit: number,
 | 
					 | 
				
			||||||
        under: SVGGElement,
 | 
					 | 
				
			||||||
        over: SVGGElement,
 | 
					 | 
				
			||||||
        edges: number[],
 | 
					 | 
				
			||||||
        toHostCoord1: (xy: Coord) => Coord,
 | 
					 | 
				
			||||||
        toHostCoord2: (xy: Coord) => Coord,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export function composeSVG(opts: ComposeOpts): ComposeResult {
 | 
					 | 
				
			||||||
        let [a, b] = [opts.el1, opts.el2];
 | 
					 | 
				
			||||||
        U.assert(a.x == 0 && a.y == 0 && b.x == 0 && b.y == 0, "el1 and el2 x,y offsets not supported");
 | 
					 | 
				
			||||||
        let setXY = (e: SVGSVGElement, x: number, y: number) => svg.hydrate(e, { x: x, y: y });
 | 
					 | 
				
			||||||
        let setWH = (e: SVGSVGElement, w: string, h: string) => {
 | 
					 | 
				
			||||||
            if (w)
 | 
					 | 
				
			||||||
                svg.hydrate(e, { width: w });
 | 
					 | 
				
			||||||
            if (h)
 | 
					 | 
				
			||||||
                svg.hydrate(e, { height: h });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        let setWHpx = (e: SVGSVGElement, w: number, h: number) => svg.hydrate(e, { width: `${w}px`, height: `${h}px` });
 | 
					 | 
				
			||||||
        let scaleUnit = opts.scaleUnit2;
 | 
					 | 
				
			||||||
        let aScalar = opts.scaleUnit2 / opts.scaleUnit1;
 | 
					 | 
				
			||||||
        let bScalar = 1.0;
 | 
					 | 
				
			||||||
        let aw = a.w * aScalar;
 | 
					 | 
				
			||||||
        let ah = a.h * aScalar;
 | 
					 | 
				
			||||||
        setWHpx(a.el, aw, ah);
 | 
					 | 
				
			||||||
        let bw = b.w * bScalar;
 | 
					 | 
				
			||||||
        let bh = b.h * bScalar;
 | 
					 | 
				
			||||||
        setWHpx(b.el, bw, bh);
 | 
					 | 
				
			||||||
        let [mt, mr, mb, ml] = opts.margin;
 | 
					 | 
				
			||||||
        let mm = opts.middleMargin;
 | 
					 | 
				
			||||||
        let innerW = Math.max(aw, bw);
 | 
					 | 
				
			||||||
        let ax = mr + (innerW - aw) / 2.0;
 | 
					 | 
				
			||||||
        let ay = mt;
 | 
					 | 
				
			||||||
        setXY(a.el, ax, ay);
 | 
					 | 
				
			||||||
        let bx = mr + (innerW - bw) / 2.0;
 | 
					 | 
				
			||||||
        let by = ay + ah + mm;
 | 
					 | 
				
			||||||
        setXY(b.el, bx, by);
 | 
					 | 
				
			||||||
        let edges = [ay, ay + ah, by, by + bh];
 | 
					 | 
				
			||||||
        let w = mr + innerW + ml;
 | 
					 | 
				
			||||||
        let h = mt + ah + mm + bh + mb;
 | 
					 | 
				
			||||||
        let host = <SVGSVGElement>svg.elt("svg", {
 | 
					 | 
				
			||||||
            "version": "1.0",
 | 
					 | 
				
			||||||
            "viewBox": `0 0 ${w} ${h}`,
 | 
					 | 
				
			||||||
            "class": `sim-bb`,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        setWH(host, opts.maxWidth, opts.maxHeight);
 | 
					 | 
				
			||||||
        setXY(host, 0, 0);
 | 
					 | 
				
			||||||
        let under = <SVGGElement>svg.child(host, "g");
 | 
					 | 
				
			||||||
        host.appendChild(a.el);
 | 
					 | 
				
			||||||
        host.appendChild(b.el);
 | 
					 | 
				
			||||||
        let over = <SVGGElement>svg.child(host, "g");
 | 
					 | 
				
			||||||
        let toHostCoord1 = (xy: Coord): Coord => {
 | 
					 | 
				
			||||||
            let [x, y] = xy;
 | 
					 | 
				
			||||||
            return [x * aScalar + ax, y * aScalar + ay];
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        let toHostCoord2 = (xy: Coord): Coord => {
 | 
					 | 
				
			||||||
            let [x, y] = xy;
 | 
					 | 
				
			||||||
            return [x * bScalar + bx, y * bScalar + by];
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        return {
 | 
					 | 
				
			||||||
            under: under,
 | 
					 | 
				
			||||||
            over: over,
 | 
					 | 
				
			||||||
            host: host,
 | 
					 | 
				
			||||||
            edges: edges,
 | 
					 | 
				
			||||||
            scaleUnit: scaleUnit,
 | 
					 | 
				
			||||||
            toHostCoord1: toHostCoord1,
 | 
					 | 
				
			||||||
            toHostCoord2: toHostCoord2,
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export function mkScaleFn(originUnit: number, targetUnit: number): (n: number) => number {
 | 
					 | 
				
			||||||
        return (n: number) => n * (targetUnit / originUnit);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export interface MkImageOpts {
 | 
					 | 
				
			||||||
        image: string,
 | 
					 | 
				
			||||||
        width: number,
 | 
					 | 
				
			||||||
        height: number,
 | 
					 | 
				
			||||||
        imageUnitDist: number,
 | 
					 | 
				
			||||||
        targetUnitDist: number
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export function mkImageSVG(opts: MkImageOpts): SVGAndSize<SVGImageElement> {
 | 
					 | 
				
			||||||
        let scaleFn = mkScaleFn(opts.imageUnitDist, opts.targetUnitDist);
 | 
					 | 
				
			||||||
        let w = scaleFn(opts.width);
 | 
					 | 
				
			||||||
        let h = scaleFn(opts.height);
 | 
					 | 
				
			||||||
        let img = <SVGImageElement>svg.elt("image", {
 | 
					 | 
				
			||||||
            width: w,
 | 
					 | 
				
			||||||
            height: h,
 | 
					 | 
				
			||||||
            "href": `${opts.image}`
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        return { el: img, w: w, h: h, x: 0, y: 0 };
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export type Coord = [number, number];
 | 
					 | 
				
			||||||
    export function findDistSqrd(a: Coord, b: Coord): number {
 | 
					 | 
				
			||||||
        let x = a[0] - b[0];
 | 
					 | 
				
			||||||
        let y = a[1] - b[1];
 | 
					 | 
				
			||||||
        return x * x + y * y;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export function findClosestCoordIdx(a: Coord, bs: Coord[]): number {
 | 
					 | 
				
			||||||
        let dists = bs.map(b => findDistSqrd(a, b));
 | 
					 | 
				
			||||||
        let minIdx = dists.reduce((prevIdx, currDist, currIdx, arr) => {
 | 
					 | 
				
			||||||
            return currDist < arr[prevIdx] ? currIdx : prevIdx;
 | 
					 | 
				
			||||||
        }, 0);
 | 
					 | 
				
			||||||
        return minIdx;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export interface IBoardPart<T> {
 | 
					 | 
				
			||||||
        style: string,
 | 
					 | 
				
			||||||
        element: SVGElement,
 | 
					 | 
				
			||||||
        overElement?: SVGElement,
 | 
					 | 
				
			||||||
        defs: SVGElement[],
 | 
					 | 
				
			||||||
        init(bus: EventBus, state: T, svgEl: SVGSVGElement, otherParams: Map<string>): void, //NOTE: constructors not supported in interfaces
 | 
					 | 
				
			||||||
        moveToCoord(xy: Coord): void,
 | 
					 | 
				
			||||||
        updateState(): void,
 | 
					 | 
				
			||||||
        updateTheme(): void,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export function mkTxt(cx: number, cy: number, size: number, rot: number, txt: string, txtXOffFactor?: number, txtYOffFactor?: number): SVGTextElement {
 | 
					 | 
				
			||||||
        let el = <SVGTextElement>svg.elt("text")
 | 
					 | 
				
			||||||
        //HACK: these constants (txtXOffFactor, txtYOffFactor) tweak the way this algorithm knows how to center the text
 | 
					 | 
				
			||||||
        txtXOffFactor = txtXOffFactor || -0.33333;
 | 
					 | 
				
			||||||
        txtYOffFactor = txtYOffFactor || 0.3;
 | 
					 | 
				
			||||||
        const xOff = txtXOffFactor * size * txt.length;
 | 
					 | 
				
			||||||
        const yOff = txtYOffFactor * size;
 | 
					 | 
				
			||||||
        svg.hydrate(el, {
 | 
					 | 
				
			||||||
            style: `font-size:${size}px;`,
 | 
					 | 
				
			||||||
            transform: `translate(${cx} ${cy}) rotate(${rot}) translate(${xOff} ${yOff})`
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        svg.addClass(el, "noselect");
 | 
					 | 
				
			||||||
        el.textContent = txt;
 | 
					 | 
				
			||||||
        return el;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export type WireColor =
 | 
					 | 
				
			||||||
        "black" | "white" | "gray" | "purple" | "blue" | "green" | "yellow" | "orange" | "red" | "brown" | "pink";
 | 
					 | 
				
			||||||
    export const GPIO_WIRE_COLORS = ["pink", "green", "purple", "orange", "yellow"];
 | 
					 | 
				
			||||||
    export const WIRE_COLOR_MAP: Map<string> = {
 | 
					 | 
				
			||||||
        black: "#514f4d",
 | 
					 | 
				
			||||||
        white: "#fcfdfc",
 | 
					 | 
				
			||||||
        gray: "#acabab",
 | 
					 | 
				
			||||||
        purple: "#a772a1",
 | 
					 | 
				
			||||||
        blue: "#01a6e8",
 | 
					 | 
				
			||||||
        green: "#3cce73",
 | 
					 | 
				
			||||||
        yellow: "#ece600",
 | 
					 | 
				
			||||||
        orange: "#fdb262",
 | 
					 | 
				
			||||||
        red: "#f44f43",
 | 
					 | 
				
			||||||
        brown: "#c89764",
 | 
					 | 
				
			||||||
        pink: "#ff80fa"
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export function mapWireColor(clr: WireColor | string): string {
 | 
					 | 
				
			||||||
        return WIRE_COLOR_MAP[clr] || clr;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export interface SVGAndSize<T extends SVGElement> {
 | 
					 | 
				
			||||||
        el: T,
 | 
					 | 
				
			||||||
        y: number,
 | 
					 | 
				
			||||||
        x: number,
 | 
					 | 
				
			||||||
        w: number,
 | 
					 | 
				
			||||||
        h: number
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    export type SVGElAndSize = SVGAndSize<SVGElement>;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export const PIN_DIST = 15;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export interface BoardView {
 | 
					 | 
				
			||||||
        getView(): SVGAndSize<SVGSVGElement>;
 | 
					 | 
				
			||||||
        getCoord(pinNm: string): Coord;
 | 
					 | 
				
			||||||
        getPinDist(): number;
 | 
					 | 
				
			||||||
        highlightPin(pinNm: string): void;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -77,25 +77,6 @@ namespace pxsim {
 | 
				
			|||||||
    export interface RuntimeOptions {
 | 
					    export interface RuntimeOptions {
 | 
				
			||||||
        theme: string;
 | 
					        theme: string;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    export class EventBus {
 | 
					 | 
				
			||||||
        private queues: Map<EventQueue<number>> = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        constructor(private runtime: Runtime) { }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        listen(id: number, evid: number, handler: RefAction) {
 | 
					 | 
				
			||||||
            let k = id + ":" + evid;
 | 
					 | 
				
			||||||
            let queue = this.queues[k];
 | 
					 | 
				
			||||||
            if (!queue) queue = this.queues[k] = new EventQueue<number>(this.runtime);
 | 
					 | 
				
			||||||
            queue.handler = handler;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        queue(id: number, evid: number, value: number = 0) {
 | 
					 | 
				
			||||||
            let k = id + ":" + evid;
 | 
					 | 
				
			||||||
            let queue = this.queues[k];
 | 
					 | 
				
			||||||
            if (queue) queue.push(value);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace pxsim.basic {
 | 
					namespace pxsim.basic {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,653 +0,0 @@
 | 
				
			|||||||
namespace pxsim.visuals {
 | 
					 | 
				
			||||||
    // The distance between the center of two pins. This is the constant on which everything else is based.
 | 
					 | 
				
			||||||
    const PIN_DIST = 15;
 | 
					 | 
				
			||||||
    // CSS styling for the breadboard
 | 
					 | 
				
			||||||
    const BLUE = "#1AA5D7";
 | 
					 | 
				
			||||||
    const RED = "#DD4BA0";
 | 
					 | 
				
			||||||
    const BREADBOARD_CSS = `
 | 
					 | 
				
			||||||
        /* bread board */
 | 
					 | 
				
			||||||
        .sim-bb-background {
 | 
					 | 
				
			||||||
            fill:#E0E0E0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-pin {
 | 
					 | 
				
			||||||
            fill:#999;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-pin-hover {
 | 
					 | 
				
			||||||
            visibility: hidden;
 | 
					 | 
				
			||||||
            pointer-events: all;
 | 
					 | 
				
			||||||
            stroke-width: ${PIN_DIST / 2}px;
 | 
					 | 
				
			||||||
            stroke: transparent;
 | 
					 | 
				
			||||||
            fill: #777;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-pin-hover:hover {
 | 
					 | 
				
			||||||
            visibility: visible;
 | 
					 | 
				
			||||||
            fill:#444;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-group-wire {
 | 
					 | 
				
			||||||
            stroke: #999;
 | 
					 | 
				
			||||||
            stroke-width: ${PIN_DIST / 4}px;
 | 
					 | 
				
			||||||
            visibility: hidden;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-pin-group {
 | 
					 | 
				
			||||||
            pointer-events: all;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-label,
 | 
					 | 
				
			||||||
        .sim-bb-label-hover {
 | 
					 | 
				
			||||||
            font-family:"Lucida Console", Monaco, monospace;
 | 
					 | 
				
			||||||
            fill:#555;
 | 
					 | 
				
			||||||
            pointer-events: all;
 | 
					 | 
				
			||||||
            stroke-width: 0;
 | 
					 | 
				
			||||||
            cursor: default;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-label-hover {
 | 
					 | 
				
			||||||
            visibility: hidden;
 | 
					 | 
				
			||||||
            fill:#000;
 | 
					 | 
				
			||||||
            font-weight: bold;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-bar {
 | 
					 | 
				
			||||||
            stroke-width: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-blue {
 | 
					 | 
				
			||||||
            fill:${BLUE};
 | 
					 | 
				
			||||||
            stroke:${BLUE}
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-red {
 | 
					 | 
				
			||||||
            fill:${RED};
 | 
					 | 
				
			||||||
            stroke:${RED};
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-pin-group:hover .sim-bb-pin-hover,
 | 
					 | 
				
			||||||
        .sim-bb-pin-group:hover .sim-bb-group-wire,
 | 
					 | 
				
			||||||
        .sim-bb-pin-group:hover .sim-bb-label-hover {
 | 
					 | 
				
			||||||
            visibility: visible;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-pin-group:hover .sim-bb-label {
 | 
					 | 
				
			||||||
            visibility: hidden;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        /* outline mode */
 | 
					 | 
				
			||||||
        .sim-bb-outline .sim-bb-background {
 | 
					 | 
				
			||||||
            stroke-width: ${PIN_DIST / 7}px;
 | 
					 | 
				
			||||||
            fill: #FFF;
 | 
					 | 
				
			||||||
            stroke: #000;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-outline .sim-bb-mid-channel {
 | 
					 | 
				
			||||||
            fill: #FFF;
 | 
					 | 
				
			||||||
            stroke: #888;
 | 
					 | 
				
			||||||
            stroke-width: 1px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        /* grayed out */
 | 
					 | 
				
			||||||
        .grayed .sim-bb-red,
 | 
					 | 
				
			||||||
        .grayed .sim-bb-blue {
 | 
					 | 
				
			||||||
            fill: #BBB;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .grayed .sim-bb-pin {
 | 
					 | 
				
			||||||
            fill:none;
 | 
					 | 
				
			||||||
            stroke: #BBB;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .grayed .sim-bb-label {
 | 
					 | 
				
			||||||
            fill: #BBB;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .grayed .sim-bb-background {
 | 
					 | 
				
			||||||
            stroke: #BBB;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .grayed .sim-bb-group-wire {
 | 
					 | 
				
			||||||
            stroke: #DDD;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        /* highlighted */
 | 
					 | 
				
			||||||
        .sim-bb-label.highlight {
 | 
					 | 
				
			||||||
            visibility: hidden;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-label-hover.highlight {
 | 
					 | 
				
			||||||
            visibility: visible;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-blue.highlight {
 | 
					 | 
				
			||||||
            fill:${BLUE};
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-red.highlight {
 | 
					 | 
				
			||||||
            fill:${RED};
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        `
 | 
					 | 
				
			||||||
    // Pin rows and coluns
 | 
					 | 
				
			||||||
    export const BREADBOARD_MID_ROWS = 10;
 | 
					 | 
				
			||||||
    export const BREADBOARD_MID_COLS = 30;
 | 
					 | 
				
			||||||
    const MID_ROW_GAPS = [4, 4];
 | 
					 | 
				
			||||||
    const MID_ROW_AND_GAPS = BREADBOARD_MID_ROWS + MID_ROW_GAPS.length;
 | 
					 | 
				
			||||||
    const BAR_ROWS = 2;
 | 
					 | 
				
			||||||
    const BAR_COLS = 25;
 | 
					 | 
				
			||||||
    const POWER_ROWS = BAR_ROWS * 2;
 | 
					 | 
				
			||||||
    const POWER_COLS = BAR_COLS * 2;
 | 
					 | 
				
			||||||
    const BAR_COL_GAPS = [4, 9, 14, 19];
 | 
					 | 
				
			||||||
    const BAR_COL_AND_GAPS = BAR_COLS + BAR_COL_GAPS.length;
 | 
					 | 
				
			||||||
    // Essential dimensions
 | 
					 | 
				
			||||||
    const WIDTH = PIN_DIST * (BREADBOARD_MID_COLS + 3);
 | 
					 | 
				
			||||||
    const HEIGHT = PIN_DIST * (MID_ROW_AND_GAPS + POWER_ROWS + 5.5);
 | 
					 | 
				
			||||||
    const MID_RATIO = 2.0 / 3.0;
 | 
					 | 
				
			||||||
    const BAR_RATIO = (1.0 - MID_RATIO) * 0.5;
 | 
					 | 
				
			||||||
    const MID_HEIGHT = HEIGHT * MID_RATIO;
 | 
					 | 
				
			||||||
    const BAR_HEIGHT = HEIGHT * BAR_RATIO;
 | 
					 | 
				
			||||||
    // Pin grids
 | 
					 | 
				
			||||||
    const MID_GRID_WIDTH = (BREADBOARD_MID_COLS - 1) * PIN_DIST;
 | 
					 | 
				
			||||||
    const MID_GRID_HEIGHT = (MID_ROW_AND_GAPS - 1) * PIN_DIST;
 | 
					 | 
				
			||||||
    const MID_GRID_X = (WIDTH - MID_GRID_WIDTH) / 2.0;
 | 
					 | 
				
			||||||
    const MID_GRID_Y = BAR_HEIGHT + (MID_HEIGHT - MID_GRID_HEIGHT) / 2.0;
 | 
					 | 
				
			||||||
    const BAR_GRID_HEIGHT = (BAR_ROWS - 1) * PIN_DIST;
 | 
					 | 
				
			||||||
    const BAR_GRID_WIDTH = (BAR_COL_AND_GAPS - 1) * PIN_DIST;
 | 
					 | 
				
			||||||
    const BAR_TOP_GRID_X = (WIDTH - BAR_GRID_WIDTH) / 2.0;
 | 
					 | 
				
			||||||
    const BAR_TOP_GRID_Y = (BAR_HEIGHT - BAR_GRID_HEIGHT) / 2.0;
 | 
					 | 
				
			||||||
    const BAR_BOT_GRID_X = BAR_TOP_GRID_X;
 | 
					 | 
				
			||||||
    const BAR_BOT_GRID_Y = BAR_TOP_GRID_Y + BAR_HEIGHT + MID_HEIGHT;
 | 
					 | 
				
			||||||
    // Individual pins
 | 
					 | 
				
			||||||
    const PIN_HOVER_SCALAR = 1.3;
 | 
					 | 
				
			||||||
    const PIN_WIDTH = PIN_DIST / 2.5;
 | 
					 | 
				
			||||||
    const PIN_ROUNDING = PIN_DIST / 7.5;
 | 
					 | 
				
			||||||
    // Labels
 | 
					 | 
				
			||||||
    const PIN_LBL_SIZE = PIN_DIST * 0.7;
 | 
					 | 
				
			||||||
    const PIN_LBL_HOVER_SCALAR = 1.3;
 | 
					 | 
				
			||||||
    const PLUS_LBL_SIZE = PIN_DIST * 1.7;
 | 
					 | 
				
			||||||
    const MINUS_LBL_SIZE = PIN_DIST * 2;
 | 
					 | 
				
			||||||
    const POWER_LBL_OFFSET = PIN_DIST * 0.8;
 | 
					 | 
				
			||||||
    const MINUS_LBL_EXTRA_OFFSET = PIN_DIST * 0.07;
 | 
					 | 
				
			||||||
    const LBL_ROTATION = -90;
 | 
					 | 
				
			||||||
    // Channels
 | 
					 | 
				
			||||||
    const CHANNEL_HEIGHT = PIN_DIST * 1.0;
 | 
					 | 
				
			||||||
    const SMALL_CHANNEL_HEIGHT = PIN_DIST * 0.05;
 | 
					 | 
				
			||||||
    // Background
 | 
					 | 
				
			||||||
    const BACKGROUND_ROUNDING = PIN_DIST * 0.3;
 | 
					 | 
				
			||||||
    // Row and column helpers
 | 
					 | 
				
			||||||
    const alphabet = "abcdefghij".split("").reverse();
 | 
					 | 
				
			||||||
    export function getColumnName(colIdx: number): string { return `${colIdx + 1}` };
 | 
					 | 
				
			||||||
    export function getRowName(rowIdx: number): string { return alphabet[rowIdx] };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export interface GridPin {
 | 
					 | 
				
			||||||
        el: SVGElement,
 | 
					 | 
				
			||||||
        hoverEl: SVGElement,
 | 
					 | 
				
			||||||
        cx: number,
 | 
					 | 
				
			||||||
        cy: number,
 | 
					 | 
				
			||||||
        row: string,
 | 
					 | 
				
			||||||
        col: string,
 | 
					 | 
				
			||||||
        group?: string
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    export interface GridOptions {
 | 
					 | 
				
			||||||
        xOffset?: number,
 | 
					 | 
				
			||||||
        yOffset?: number,
 | 
					 | 
				
			||||||
        rowCount: number,
 | 
					 | 
				
			||||||
        colCount: number,
 | 
					 | 
				
			||||||
        rowStartIdx?: number,
 | 
					 | 
				
			||||||
        colStartIdx?: number,
 | 
					 | 
				
			||||||
        pinDist: number,
 | 
					 | 
				
			||||||
        mkPin: () => SVGElAndSize,
 | 
					 | 
				
			||||||
        mkHoverPin: () => SVGElAndSize,
 | 
					 | 
				
			||||||
        getRowName: (rowIdx: number) => string,
 | 
					 | 
				
			||||||
        getColName: (colIdx: number) => string,
 | 
					 | 
				
			||||||
        getGroupName?: (rowIdx: number, colIdx: number) => string,
 | 
					 | 
				
			||||||
        rowIdxsWithGap?: number[],
 | 
					 | 
				
			||||||
        colIdxsWithGap?: number[],
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    export interface GridResult {
 | 
					 | 
				
			||||||
        g: SVGGElement,
 | 
					 | 
				
			||||||
        allPins: GridPin[],
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export function mkGrid(opts: GridOptions): GridResult {
 | 
					 | 
				
			||||||
        let xOff = opts.xOffset || 0;
 | 
					 | 
				
			||||||
        let yOff = opts.yOffset || 0;
 | 
					 | 
				
			||||||
        let allPins: GridPin[] = [];
 | 
					 | 
				
			||||||
        let grid = <SVGGElement>svg.elt("g");
 | 
					 | 
				
			||||||
        let colIdxOffset = opts.colStartIdx || 0;
 | 
					 | 
				
			||||||
        let rowIdxOffset = opts.rowStartIdx || 0;
 | 
					 | 
				
			||||||
        let copyArr = <T>(arr: T[]): T[] => arr ? arr.slice(0, arr.length) : [];
 | 
					 | 
				
			||||||
        let removeAll = <T>(arr: T[], e: T): number => {
 | 
					 | 
				
			||||||
            let res = 0;
 | 
					 | 
				
			||||||
            let idx: number;
 | 
					 | 
				
			||||||
            while (0 <= (idx = arr.indexOf(e))) {
 | 
					 | 
				
			||||||
                arr.splice(idx, 1);
 | 
					 | 
				
			||||||
                res += 1;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return res;
 | 
					 | 
				
			||||||
        };
 | 
					 | 
				
			||||||
        let rowGaps = 0;
 | 
					 | 
				
			||||||
        let rowIdxsWithGap = copyArr(opts.rowIdxsWithGap)
 | 
					 | 
				
			||||||
        for (let i = 0; i < opts.rowCount; i++) {
 | 
					 | 
				
			||||||
            let colGaps = 0;
 | 
					 | 
				
			||||||
            let colIdxsWithGap = copyArr(opts.colIdxsWithGap)
 | 
					 | 
				
			||||||
            let cy = yOff + i * opts.pinDist + rowGaps * opts.pinDist;
 | 
					 | 
				
			||||||
            let rowIdx = i + rowIdxOffset;
 | 
					 | 
				
			||||||
            for (let j = 0; j < opts.colCount; j++) {
 | 
					 | 
				
			||||||
                let cx = xOff + j * opts.pinDist + colGaps * opts.pinDist;
 | 
					 | 
				
			||||||
                let colIdx = j + colIdxOffset;
 | 
					 | 
				
			||||||
                const addEl = (pin: SVGElAndSize) => {
 | 
					 | 
				
			||||||
                    let pinX = cx - pin.w * 0.5;
 | 
					 | 
				
			||||||
                    let pinY = cy - pin.h * 0.5;
 | 
					 | 
				
			||||||
                    svg.hydrate(pin.el, {x: pinX, y: pinY});
 | 
					 | 
				
			||||||
                    grid.appendChild(pin.el);
 | 
					 | 
				
			||||||
                    return pin.el;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                let el = addEl(opts.mkPin());
 | 
					 | 
				
			||||||
                let hoverEl = addEl(opts.mkHoverPin());
 | 
					 | 
				
			||||||
                let row = opts.getRowName(rowIdx);
 | 
					 | 
				
			||||||
                let col = opts.getColName(colIdx);
 | 
					 | 
				
			||||||
                let group = opts.getGroupName ? opts.getGroupName(rowIdx, colIdx) : null;
 | 
					 | 
				
			||||||
                let gridPin: GridPin = {el: el, hoverEl: hoverEl, cx: cx, cy: cy, row: row, col: col, group: group};
 | 
					 | 
				
			||||||
                allPins.push(gridPin);
 | 
					 | 
				
			||||||
                //column gaps
 | 
					 | 
				
			||||||
                colGaps += removeAll(colIdxsWithGap, colIdx);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            //row gaps
 | 
					 | 
				
			||||||
            rowGaps += removeAll(rowIdxsWithGap, rowIdx);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        return {g: grid, allPins: allPins};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function mkBBPin(): SVGElAndSize {
 | 
					 | 
				
			||||||
        let el = svg.elt("rect");
 | 
					 | 
				
			||||||
        let width = PIN_WIDTH;
 | 
					 | 
				
			||||||
        svg.hydrate(el, {
 | 
					 | 
				
			||||||
            class: "sim-bb-pin",
 | 
					 | 
				
			||||||
            rx: PIN_ROUNDING,
 | 
					 | 
				
			||||||
            ry: PIN_ROUNDING,
 | 
					 | 
				
			||||||
            width: width,
 | 
					 | 
				
			||||||
            height: width
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        return {el: el, w: width, h: width, x: 0, y: 0};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function mkBBHoverPin(): SVGElAndSize {
 | 
					 | 
				
			||||||
        let el = svg.elt("rect");
 | 
					 | 
				
			||||||
        let width = PIN_WIDTH * PIN_HOVER_SCALAR;
 | 
					 | 
				
			||||||
        svg.hydrate(el, {
 | 
					 | 
				
			||||||
            class: "sim-bb-pin-hover",
 | 
					 | 
				
			||||||
            rx: PIN_ROUNDING,
 | 
					 | 
				
			||||||
            ry: PIN_ROUNDING,
 | 
					 | 
				
			||||||
            width: width,
 | 
					 | 
				
			||||||
            height: width,
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        return {el: el, w: width, h: width, x: 0, y: 0};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export interface GridLabel {
 | 
					 | 
				
			||||||
        el: SVGTextElement,
 | 
					 | 
				
			||||||
        hoverEl: SVGTextElement,
 | 
					 | 
				
			||||||
        txt: string,
 | 
					 | 
				
			||||||
        group?: string,
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
    function mkBBLabel(cx: number, cy: number, size: number, rotation: number, txt: string, group: string, extraClasses?: string[]): GridLabel {
 | 
					 | 
				
			||||||
        //lbl
 | 
					 | 
				
			||||||
        let el = mkTxt(cx, cy, size, rotation, txt);
 | 
					 | 
				
			||||||
        svg.addClass(el, "sim-bb-label");
 | 
					 | 
				
			||||||
        if (extraClasses)
 | 
					 | 
				
			||||||
            extraClasses.forEach(c => svg.addClass(el, c));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //hover lbl
 | 
					 | 
				
			||||||
        let hoverEl = mkTxt(cx, cy, size * PIN_LBL_HOVER_SCALAR, rotation, txt);
 | 
					 | 
				
			||||||
        svg.addClass(hoverEl, "sim-bb-label-hover");
 | 
					 | 
				
			||||||
        if (extraClasses)
 | 
					 | 
				
			||||||
            extraClasses.forEach(c => svg.addClass(hoverEl, c));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let lbl = {el: el, hoverEl: hoverEl, txt: txt, group: group};
 | 
					 | 
				
			||||||
        return lbl;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    interface BBBar {
 | 
					 | 
				
			||||||
        el: SVGRectElement,
 | 
					 | 
				
			||||||
        group?: string
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export interface BreadboardOpts {
 | 
					 | 
				
			||||||
        wireframe?: boolean,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export class Breadboard {
 | 
					 | 
				
			||||||
        public bb: SVGSVGElement;
 | 
					 | 
				
			||||||
        private styleEl: SVGStyleElement;
 | 
					 | 
				
			||||||
        private defs: SVGDefsElement;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //truth
 | 
					 | 
				
			||||||
        private allPins: GridPin[] = [];
 | 
					 | 
				
			||||||
        private allLabels: GridLabel[] = [];
 | 
					 | 
				
			||||||
        private allPowerBars: BBBar[] = [];
 | 
					 | 
				
			||||||
        //quick lookup caches
 | 
					 | 
				
			||||||
        private rowColToPin: Map<Map<GridPin>> = {};
 | 
					 | 
				
			||||||
        private rowColToLbls: Map<Map<GridLabel[]>> = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        constructor(opts: BreadboardOpts) {
 | 
					 | 
				
			||||||
            this.buildDom();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if (opts.wireframe)
 | 
					 | 
				
			||||||
                svg.addClass(this.bb, "sim-bb-outline");
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public updateLocation(x: number, y: number) {
 | 
					 | 
				
			||||||
            svg.hydrate(this.bb, {
 | 
					 | 
				
			||||||
                x: `${x}px`,
 | 
					 | 
				
			||||||
                y: `${y}px`,
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public getPin(row: string, col: string): GridPin {
 | 
					 | 
				
			||||||
            let colToPin = this.rowColToPin[row];
 | 
					 | 
				
			||||||
            if (!colToPin)
 | 
					 | 
				
			||||||
                return null;
 | 
					 | 
				
			||||||
            let pin = colToPin[col];
 | 
					 | 
				
			||||||
            if (!pin)
 | 
					 | 
				
			||||||
                return null;
 | 
					 | 
				
			||||||
            return pin;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        public getCoord(rowCol: BBLoc): Coord {
 | 
					 | 
				
			||||||
            let {row, col, xOffset, yOffset} = rowCol;
 | 
					 | 
				
			||||||
            let pin = this.getPin(row, col);
 | 
					 | 
				
			||||||
            if (!pin)
 | 
					 | 
				
			||||||
                return null;
 | 
					 | 
				
			||||||
            let xOff = (xOffset || 0) * PIN_DIST;
 | 
					 | 
				
			||||||
            let yOff = (yOffset || 0) * PIN_DIST;
 | 
					 | 
				
			||||||
            return [pin.cx + xOff, pin.cy + yOff];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public getPinDist() {
 | 
					 | 
				
			||||||
            return PIN_DIST;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private buildDom() {
 | 
					 | 
				
			||||||
            this.bb = <SVGSVGElement>svg.elt("svg", {
 | 
					 | 
				
			||||||
                "version": "1.0",
 | 
					 | 
				
			||||||
                "viewBox": `0 0 ${WIDTH} ${HEIGHT}`,
 | 
					 | 
				
			||||||
                "class": `sim-bb`,
 | 
					 | 
				
			||||||
                "width": WIDTH + "px",
 | 
					 | 
				
			||||||
                "height": HEIGHT + "px",
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            this.styleEl = <SVGStyleElement>svg.child(this.bb, "style", {});
 | 
					 | 
				
			||||||
            this.styleEl.textContent += BREADBOARD_CSS;
 | 
					 | 
				
			||||||
            this.defs = <SVGDefsElement>svg.child(this.bb, "defs", {});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //background
 | 
					 | 
				
			||||||
            svg.child(this.bb, "rect", { class: "sim-bb-background", width: WIDTH, height: HEIGHT, rx: BACKGROUND_ROUNDING, ry: BACKGROUND_ROUNDING});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //mid channel
 | 
					 | 
				
			||||||
            let channelGid = "sim-bb-channel-grad";
 | 
					 | 
				
			||||||
            let channelGrad = <SVGLinearGradientElement>svg.elt("linearGradient")
 | 
					 | 
				
			||||||
            svg.hydrate(channelGrad, { id: channelGid, x1: "0%", y1: "0%", x2: "0%", y2: "100%" });
 | 
					 | 
				
			||||||
            this.defs.appendChild(channelGrad);
 | 
					 | 
				
			||||||
            let channelDark = "#AAA";
 | 
					 | 
				
			||||||
            let channelLight = "#CCC";
 | 
					 | 
				
			||||||
            let stop1 = svg.child(channelGrad, "stop", { offset: "0%", style: `stop-color: ${channelDark};` })
 | 
					 | 
				
			||||||
            let stop2 = svg.child(channelGrad, "stop", { offset: "20%", style: `stop-color: ${channelLight};` })
 | 
					 | 
				
			||||||
            let stop3 = svg.child(channelGrad, "stop", { offset: "80%", style: `stop-color: ${channelLight};` })
 | 
					 | 
				
			||||||
            let stop4 = svg.child(channelGrad, "stop", { offset: "100%", style: `stop-color: ${channelDark};` })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            const mkChannel = (cy: number, h: number, cls?: string) => {
 | 
					 | 
				
			||||||
                let channel = svg.child(this.bb, "rect", { class: `sim-bb-channel ${cls || ""}`, y: cy - h / 2, width: WIDTH, height: h});
 | 
					 | 
				
			||||||
                channel.setAttribute("fill", `url(#${channelGid})`);
 | 
					 | 
				
			||||||
                return channel;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            mkChannel(BAR_HEIGHT + MID_HEIGHT / 2, CHANNEL_HEIGHT, "sim-bb-mid-channel");
 | 
					 | 
				
			||||||
            mkChannel(BAR_HEIGHT, SMALL_CHANNEL_HEIGHT);
 | 
					 | 
				
			||||||
            mkChannel(BAR_HEIGHT + MID_HEIGHT, SMALL_CHANNEL_HEIGHT);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //-----pins
 | 
					 | 
				
			||||||
            const getMidTopOrBot = (rowIdx: number) => rowIdx < BREADBOARD_MID_ROWS / 2.0 ? "b" : "t";
 | 
					 | 
				
			||||||
            const getBarTopOrBot = (colIdx: number) => colIdx < POWER_COLS / 2.0 ? "b" : "t";
 | 
					 | 
				
			||||||
            const getMidGroupName = (rowIdx: number, colIdx: number) => {
 | 
					 | 
				
			||||||
                let botOrTop = getMidTopOrBot(rowIdx);
 | 
					 | 
				
			||||||
                let colNm = getColumnName(colIdx);
 | 
					 | 
				
			||||||
                return `${botOrTop}${colNm}`;
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            const getBarRowName = (rowIdx: number) => rowIdx === 0 ? "-" : "+";
 | 
					 | 
				
			||||||
            const getBarGroupName = (rowIdx: number, colIdx: number) => {
 | 
					 | 
				
			||||||
                let botOrTop = getBarTopOrBot(colIdx);
 | 
					 | 
				
			||||||
                let rowName = getBarRowName(rowIdx);
 | 
					 | 
				
			||||||
                return `${rowName}${botOrTop}`;
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //mid grid
 | 
					 | 
				
			||||||
            let midGridRes = mkGrid({
 | 
					 | 
				
			||||||
                xOffset: MID_GRID_X,
 | 
					 | 
				
			||||||
                yOffset: MID_GRID_Y,
 | 
					 | 
				
			||||||
                rowCount: BREADBOARD_MID_ROWS,
 | 
					 | 
				
			||||||
                colCount: BREADBOARD_MID_COLS,
 | 
					 | 
				
			||||||
                pinDist: PIN_DIST,
 | 
					 | 
				
			||||||
                mkPin: mkBBPin,
 | 
					 | 
				
			||||||
                mkHoverPin: mkBBHoverPin,
 | 
					 | 
				
			||||||
                getRowName: getRowName,
 | 
					 | 
				
			||||||
                getColName: getColumnName,
 | 
					 | 
				
			||||||
                getGroupName: getMidGroupName,
 | 
					 | 
				
			||||||
                rowIdxsWithGap: MID_ROW_GAPS,
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            let midGridG = midGridRes.g;
 | 
					 | 
				
			||||||
            this.allPins = this.allPins.concat(midGridRes.allPins);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //bot bar
 | 
					 | 
				
			||||||
            let botBarGridRes = mkGrid({
 | 
					 | 
				
			||||||
                xOffset: BAR_BOT_GRID_X,
 | 
					 | 
				
			||||||
                yOffset: BAR_BOT_GRID_Y,
 | 
					 | 
				
			||||||
                rowCount: BAR_ROWS,
 | 
					 | 
				
			||||||
                colCount: BAR_COLS,
 | 
					 | 
				
			||||||
                pinDist: PIN_DIST,
 | 
					 | 
				
			||||||
                mkPin: mkBBPin,
 | 
					 | 
				
			||||||
                mkHoverPin: mkBBHoverPin,
 | 
					 | 
				
			||||||
                getRowName: getBarRowName,
 | 
					 | 
				
			||||||
                getColName: getColumnName,
 | 
					 | 
				
			||||||
                getGroupName: getBarGroupName,
 | 
					 | 
				
			||||||
                colIdxsWithGap: BAR_COL_GAPS,
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            let botBarGridG = botBarGridRes.g;
 | 
					 | 
				
			||||||
            this.allPins = this.allPins.concat(botBarGridRes.allPins);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //top bar
 | 
					 | 
				
			||||||
            let topBarGridRes = mkGrid({
 | 
					 | 
				
			||||||
                xOffset: BAR_TOP_GRID_X,
 | 
					 | 
				
			||||||
                yOffset: BAR_TOP_GRID_Y,
 | 
					 | 
				
			||||||
                rowCount: BAR_ROWS,
 | 
					 | 
				
			||||||
                colCount: BAR_COLS,
 | 
					 | 
				
			||||||
                colStartIdx: BAR_COLS,
 | 
					 | 
				
			||||||
                pinDist: PIN_DIST,
 | 
					 | 
				
			||||||
                mkPin: mkBBPin,
 | 
					 | 
				
			||||||
                mkHoverPin: mkBBHoverPin,
 | 
					 | 
				
			||||||
                getRowName: getBarRowName,
 | 
					 | 
				
			||||||
                getColName: getColumnName,
 | 
					 | 
				
			||||||
                getGroupName: getBarGroupName,
 | 
					 | 
				
			||||||
                colIdxsWithGap: BAR_COL_GAPS.map(g => g + BAR_COLS),
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            let topBarGridG = topBarGridRes.g;
 | 
					 | 
				
			||||||
            this.allPins = this.allPins.concat(topBarGridRes.allPins);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //tooltip
 | 
					 | 
				
			||||||
            this.allPins.forEach(pin => {
 | 
					 | 
				
			||||||
                let {el, row, col, hoverEl} = pin
 | 
					 | 
				
			||||||
                let title = `(${row},${col})`;
 | 
					 | 
				
			||||||
                svg.hydrate(el, {title: title});
 | 
					 | 
				
			||||||
                svg.hydrate(hoverEl, {title: title});
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //catalog pins
 | 
					 | 
				
			||||||
            this.allPins.forEach(pin => {
 | 
					 | 
				
			||||||
                let colToPin = this.rowColToPin[pin.row];
 | 
					 | 
				
			||||||
                if (!colToPin)
 | 
					 | 
				
			||||||
                    colToPin = this.rowColToPin[pin.row] = {};
 | 
					 | 
				
			||||||
                colToPin[pin.col] = pin;
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //-----labels
 | 
					 | 
				
			||||||
            const mkBBLabelAtPin = (row: string, col: string, xOffset: number, yOffset: number, txt: string, group?: string): GridLabel => {
 | 
					 | 
				
			||||||
                let size = PIN_LBL_SIZE;
 | 
					 | 
				
			||||||
                let rotation = LBL_ROTATION;
 | 
					 | 
				
			||||||
                let loc = this.getCoord({type: "breadboard", row: row, col: col});
 | 
					 | 
				
			||||||
                let [cx, cy] = loc;
 | 
					 | 
				
			||||||
                let t = mkBBLabel(cx + xOffset, cy + yOffset, size, rotation, txt, group);
 | 
					 | 
				
			||||||
                return t;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //columns
 | 
					 | 
				
			||||||
            for (let colIdx = 0; colIdx < BREADBOARD_MID_COLS; colIdx++) {
 | 
					 | 
				
			||||||
                let colNm = getColumnName(colIdx);
 | 
					 | 
				
			||||||
                //top
 | 
					 | 
				
			||||||
                let rowTIdx = 0;
 | 
					 | 
				
			||||||
                let rowTNm = getRowName(rowTIdx);
 | 
					 | 
				
			||||||
                let groupT = getMidGroupName(rowTIdx, colIdx);
 | 
					 | 
				
			||||||
                let lblT = mkBBLabelAtPin(rowTNm, colNm, 0, -PIN_DIST, colNm, groupT);
 | 
					 | 
				
			||||||
                this.allLabels.push(lblT);
 | 
					 | 
				
			||||||
                //bottom
 | 
					 | 
				
			||||||
                let rowBIdx = BREADBOARD_MID_ROWS - 1;
 | 
					 | 
				
			||||||
                let rowBNm = getRowName(rowBIdx);
 | 
					 | 
				
			||||||
                let groupB = getMidGroupName(rowBIdx, colIdx);
 | 
					 | 
				
			||||||
                let lblB = mkBBLabelAtPin(rowBNm, colNm, 0, +PIN_DIST, colNm, groupB);
 | 
					 | 
				
			||||||
                this.allLabels.push(lblB);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            //rows
 | 
					 | 
				
			||||||
            for (let rowIdx = 0; rowIdx < BREADBOARD_MID_ROWS; rowIdx++) {
 | 
					 | 
				
			||||||
                let rowNm = getRowName(rowIdx);
 | 
					 | 
				
			||||||
                //top
 | 
					 | 
				
			||||||
                let colTIdx = 0;
 | 
					 | 
				
			||||||
                let colTNm = getColumnName(colTIdx);
 | 
					 | 
				
			||||||
                let lblT = mkBBLabelAtPin(rowNm, colTNm, -PIN_DIST, 0, rowNm);
 | 
					 | 
				
			||||||
                this.allLabels.push(lblT);
 | 
					 | 
				
			||||||
                //top
 | 
					 | 
				
			||||||
                let colBIdx = BREADBOARD_MID_COLS - 1;
 | 
					 | 
				
			||||||
                let colBNm = getColumnName(colBIdx);
 | 
					 | 
				
			||||||
                let lblB = mkBBLabelAtPin(rowNm, colBNm, +PIN_DIST, 0, rowNm);
 | 
					 | 
				
			||||||
                this.allLabels.push(lblB);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //+- labels
 | 
					 | 
				
			||||||
            let botPowerLabels = [
 | 
					 | 
				
			||||||
                //BL
 | 
					 | 
				
			||||||
                mkBBLabel(0 + POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, BAR_HEIGHT + MID_HEIGHT + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, 0), [`sim-bb-blue`]),
 | 
					 | 
				
			||||||
                mkBBLabel(0 + POWER_LBL_OFFSET, BAR_HEIGHT + MID_HEIGHT + BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, 0), [`sim-bb-red`]),
 | 
					 | 
				
			||||||
                //BR
 | 
					 | 
				
			||||||
                mkBBLabel(WIDTH - POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, BAR_HEIGHT + MID_HEIGHT + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, BAR_COLS - 1), [`sim-bb-blue`]),
 | 
					 | 
				
			||||||
                mkBBLabel(WIDTH - POWER_LBL_OFFSET, BAR_HEIGHT + MID_HEIGHT + BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, BAR_COLS - 1), [`sim-bb-red`]),
 | 
					 | 
				
			||||||
            ];
 | 
					 | 
				
			||||||
            this.allLabels = this.allLabels.concat(botPowerLabels);
 | 
					 | 
				
			||||||
            let topPowerLabels = [
 | 
					 | 
				
			||||||
                //TL
 | 
					 | 
				
			||||||
                mkBBLabel(0 + POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, 0 + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, BAR_COLS), [`sim-bb-blue`]),
 | 
					 | 
				
			||||||
                mkBBLabel(0 + POWER_LBL_OFFSET, BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, BAR_COLS), [`sim-bb-red`]),
 | 
					 | 
				
			||||||
                //TR
 | 
					 | 
				
			||||||
                mkBBLabel(WIDTH - POWER_LBL_OFFSET + MINUS_LBL_EXTRA_OFFSET, 0 + POWER_LBL_OFFSET, MINUS_LBL_SIZE, LBL_ROTATION, `-`, getBarGroupName(0, POWER_COLS - 1), [`sim-bb-blue`]),
 | 
					 | 
				
			||||||
                mkBBLabel(WIDTH - POWER_LBL_OFFSET, BAR_HEIGHT - POWER_LBL_OFFSET, PLUS_LBL_SIZE, LBL_ROTATION, `+`, getBarGroupName(1, POWER_COLS - 1), [`sim-bb-red`]),
 | 
					 | 
				
			||||||
            ];
 | 
					 | 
				
			||||||
            this.allLabels = this.allLabels.concat(topPowerLabels);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //catalog lbls
 | 
					 | 
				
			||||||
            let lblNmToLbls: Map<GridLabel[]> = {};
 | 
					 | 
				
			||||||
            this.allLabels.forEach(lbl => {
 | 
					 | 
				
			||||||
                let {el, txt} = lbl;
 | 
					 | 
				
			||||||
                let lbls = lblNmToLbls[txt] = lblNmToLbls[txt] || []
 | 
					 | 
				
			||||||
                lbls.push(lbl);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            const isPowerPin = (pin: GridPin) => pin.row === "-" || pin.row === "+";
 | 
					 | 
				
			||||||
            this.allPins.forEach(pin => {
 | 
					 | 
				
			||||||
                let {row, col, group} = pin;
 | 
					 | 
				
			||||||
                let colToLbls = this.rowColToLbls[row] || (this.rowColToLbls[row] = {});
 | 
					 | 
				
			||||||
                let lbls = colToLbls[col] || (colToLbls[col] = []);
 | 
					 | 
				
			||||||
                if (isPowerPin(pin)) {
 | 
					 | 
				
			||||||
                    //power pins
 | 
					 | 
				
			||||||
                    let isBot = Number(col) <= BAR_COLS;
 | 
					 | 
				
			||||||
                    if (isBot)
 | 
					 | 
				
			||||||
                        botPowerLabels.filter(l => l.group == pin.group).forEach(l => lbls.push(l));
 | 
					 | 
				
			||||||
                    else
 | 
					 | 
				
			||||||
                        topPowerLabels.filter(l => l.group == pin.group).forEach(l => lbls.push(l));
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    //mid pins
 | 
					 | 
				
			||||||
                    let rowLbls = lblNmToLbls[row];
 | 
					 | 
				
			||||||
                    rowLbls.forEach(l => lbls.push(l));
 | 
					 | 
				
			||||||
                    let colLbls = lblNmToLbls[col];
 | 
					 | 
				
			||||||
                    colLbls.forEach(l => lbls.push(l));
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //-----blue & red lines
 | 
					 | 
				
			||||||
            const lnLen = BAR_GRID_WIDTH + PIN_DIST * 1.5;
 | 
					 | 
				
			||||||
            const lnThickness = PIN_DIST / 5.0;
 | 
					 | 
				
			||||||
            const lnYOff = PIN_DIST * 0.6;
 | 
					 | 
				
			||||||
            const lnXOff = (lnLen - BAR_GRID_WIDTH) / 2.0;
 | 
					 | 
				
			||||||
            const mkPowerLine = (x: number, y: number, group: string, cls: string): BBBar => {
 | 
					 | 
				
			||||||
                let ln = <SVGRectElement>svg.elt("rect");
 | 
					 | 
				
			||||||
                svg.hydrate(ln, {
 | 
					 | 
				
			||||||
                    class: `sim-bb-bar ${cls}`,
 | 
					 | 
				
			||||||
                    x: x,
 | 
					 | 
				
			||||||
                    y: y - lnThickness / 2.0,
 | 
					 | 
				
			||||||
                    width: lnLen,
 | 
					 | 
				
			||||||
                    height: lnThickness});
 | 
					 | 
				
			||||||
                let bar: BBBar = {el: ln, group: group};
 | 
					 | 
				
			||||||
                return bar;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            let barLines = [
 | 
					 | 
				
			||||||
                //top
 | 
					 | 
				
			||||||
                mkPowerLine(BAR_BOT_GRID_X - lnXOff, BAR_BOT_GRID_Y - lnYOff, getBarGroupName(0, POWER_COLS - 1), "sim-bb-blue"),
 | 
					 | 
				
			||||||
                mkPowerLine(BAR_BOT_GRID_X - lnXOff, BAR_BOT_GRID_Y + PIN_DIST + lnYOff, getBarGroupName(1, POWER_COLS - 1), "sim-bb-red"),
 | 
					 | 
				
			||||||
                //bot
 | 
					 | 
				
			||||||
                mkPowerLine(BAR_TOP_GRID_X - lnXOff, BAR_TOP_GRID_Y - lnYOff, getBarGroupName(0, 0), "sim-bb-blue"),
 | 
					 | 
				
			||||||
                mkPowerLine(BAR_TOP_GRID_X - lnXOff, BAR_TOP_GRID_Y + PIN_DIST + lnYOff, getBarGroupName(1, 0), "sim-bb-red"),
 | 
					 | 
				
			||||||
            ];
 | 
					 | 
				
			||||||
            this.allPowerBars = this.allPowerBars.concat(barLines);
 | 
					 | 
				
			||||||
            //attach power bars
 | 
					 | 
				
			||||||
            this.allPowerBars.forEach(b => this.bb.appendChild(b.el));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //-----electrically connected groups
 | 
					 | 
				
			||||||
            //make groups
 | 
					 | 
				
			||||||
            let allGrpNms = this.allPins.map(p => p.group).filter((g, i, a) => a.indexOf(g) == i);
 | 
					 | 
				
			||||||
            let groups: SVGGElement[] = allGrpNms.map(grpNm => {
 | 
					 | 
				
			||||||
                let g = <SVGGElement>svg.elt("g");
 | 
					 | 
				
			||||||
                return g;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            groups.forEach(g => svg.addClass(g, "sim-bb-pin-group"));
 | 
					 | 
				
			||||||
            groups.forEach((g, i) => svg.addClass(g, `group-${allGrpNms[i]}`));
 | 
					 | 
				
			||||||
            let grpNmToGroup: Map<SVGGElement> = {};
 | 
					 | 
				
			||||||
            allGrpNms.forEach((g, i) => grpNmToGroup[g] = groups[i]);
 | 
					 | 
				
			||||||
            //group pins and add connecting wire
 | 
					 | 
				
			||||||
            let grpNmToPins: Map<GridPin[]> = {};
 | 
					 | 
				
			||||||
            this.allPins.forEach((p, i) => {
 | 
					 | 
				
			||||||
                let g = p.group;
 | 
					 | 
				
			||||||
                let pins = grpNmToPins[g] || (grpNmToPins[g] = []);
 | 
					 | 
				
			||||||
                pins.push(p);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            //connecting wire
 | 
					 | 
				
			||||||
            allGrpNms.forEach(grpNm => {
 | 
					 | 
				
			||||||
                let pins = grpNmToPins[grpNm];
 | 
					 | 
				
			||||||
                let [xs, ys] = [pins.map(p => p.cx), pins.map(p => p.cy)];
 | 
					 | 
				
			||||||
                let minFn = (arr: number[]) => arr.reduce((a, b) => a < b ? a : b);
 | 
					 | 
				
			||||||
                let maxFn = (arr: number[]) => arr.reduce((a, b) => a > b ? a : b);
 | 
					 | 
				
			||||||
                let [minX, maxX, minY, maxY] = [minFn(xs), maxFn(xs), minFn(ys), maxFn(ys)];
 | 
					 | 
				
			||||||
                let wire = svg.elt("rect");
 | 
					 | 
				
			||||||
                let width = Math.max(maxX - minX, 0.0001/*rects with no width aren't displayed*/);
 | 
					 | 
				
			||||||
                let height =  Math.max(maxY - minY, 0.0001);
 | 
					 | 
				
			||||||
                svg.hydrate(wire, {x: minX, y: minY, width: width, height: height});
 | 
					 | 
				
			||||||
                svg.addClass(wire, "sim-bb-group-wire")
 | 
					 | 
				
			||||||
                let g = grpNmToGroup[grpNm];
 | 
					 | 
				
			||||||
                g.appendChild(wire);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            //group pins
 | 
					 | 
				
			||||||
            this.allPins.forEach(p => {
 | 
					 | 
				
			||||||
                let g = grpNmToGroup[p.group];
 | 
					 | 
				
			||||||
                g.appendChild(p.el);
 | 
					 | 
				
			||||||
                g.appendChild(p.hoverEl);
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
            //group lbls
 | 
					 | 
				
			||||||
            let miscLblGroup = <SVGGElement>svg.elt("g");
 | 
					 | 
				
			||||||
            svg.hydrate(miscLblGroup, {class: "sim-bb-group-misc"});
 | 
					 | 
				
			||||||
            groups.push(miscLblGroup);
 | 
					 | 
				
			||||||
            this.allLabels.forEach(l => {
 | 
					 | 
				
			||||||
                if (l.group) {
 | 
					 | 
				
			||||||
                    let g = grpNmToGroup[l.group];
 | 
					 | 
				
			||||||
                    g.appendChild(l.el);
 | 
					 | 
				
			||||||
                    g.appendChild(l.hoverEl);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    miscLblGroup.appendChild(l.el);
 | 
					 | 
				
			||||||
                    miscLblGroup.appendChild(l.hoverEl);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            })
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            //attach to bb
 | 
					 | 
				
			||||||
            groups.forEach(g => this.bb.appendChild(g)); //attach to breadboard
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public getSVGAndSize(): SVGAndSize<SVGSVGElement> {
 | 
					 | 
				
			||||||
            return {el: this.bb, y: 0, x: 0, w: WIDTH, h: HEIGHT};
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public highlightLoc(rowCol: BBLoc) {
 | 
					 | 
				
			||||||
            let {row, col} = rowCol;
 | 
					 | 
				
			||||||
            let pin = this.rowColToPin[row][col];
 | 
					 | 
				
			||||||
            let {cx, cy} = pin;
 | 
					 | 
				
			||||||
            let lbls = this.rowColToLbls[row][col];
 | 
					 | 
				
			||||||
            const highlightLbl = (lbl: GridLabel) => {
 | 
					 | 
				
			||||||
                svg.addClass(lbl.el, "highlight");
 | 
					 | 
				
			||||||
                svg.addClass(lbl.hoverEl, "highlight");
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            lbls.forEach(highlightLbl);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,305 +0,0 @@
 | 
				
			|||||||
/// <reference path="../../node_modules/pxt-core/typings/bluebird/bluebird.d.ts"/>
 | 
					 | 
				
			||||||
/// <reference path="../../node_modules/pxt-core/built/pxtsim.d.ts"/>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace pxsim.visuals {
 | 
					 | 
				
			||||||
    export const BOARD_SYTLE = `
 | 
					 | 
				
			||||||
        .noselect {
 | 
					 | 
				
			||||||
            -webkit-touch-callout: none; /* iOS Safari */
 | 
					 | 
				
			||||||
            -webkit-user-select: none;   /* Chrome/Safari/Opera */
 | 
					 | 
				
			||||||
            -khtml-user-select: none;    /* Konqueror */
 | 
					 | 
				
			||||||
            -moz-user-select: none;      /* Firefox */
 | 
					 | 
				
			||||||
            -ms-user-select: none;       /* Internet Explorer/Edge */
 | 
					 | 
				
			||||||
            user-select: none;           /* Non-prefixed version, currently
 | 
					 | 
				
			||||||
                                            not supported by any browser */
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .sim-board-pin {
 | 
					 | 
				
			||||||
            fill:#999;
 | 
					 | 
				
			||||||
            stroke:#000;
 | 
					 | 
				
			||||||
            stroke-width:${PIN_DIST / 3.0}px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-pin-lbl {
 | 
					 | 
				
			||||||
            fill: #333;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .gray-cover {
 | 
					 | 
				
			||||||
            fill:#FFF;
 | 
					 | 
				
			||||||
            opacity: 0.7;
 | 
					 | 
				
			||||||
            stroke-width:0;
 | 
					 | 
				
			||||||
            visibility: hidden;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-pin-hover {
 | 
					 | 
				
			||||||
            visibility: hidden;
 | 
					 | 
				
			||||||
            pointer-events: all;
 | 
					 | 
				
			||||||
            stroke-width:${PIN_DIST / 6.0}px;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-pin-hover:hover {
 | 
					 | 
				
			||||||
            visibility: visible;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-pin-lbl {
 | 
					 | 
				
			||||||
            visibility: hidden;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-outline .sim-board-pin-lbl {
 | 
					 | 
				
			||||||
            visibility: visible;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-pin-lbl {
 | 
					 | 
				
			||||||
            fill: #555;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-pin-lbl-hover {
 | 
					 | 
				
			||||||
            fill: red;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-outline .sim-board-pin-lbl-hover {
 | 
					 | 
				
			||||||
            fill: black;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-pin-lbl,
 | 
					 | 
				
			||||||
        .sim-board-pin-lbl-hover {
 | 
					 | 
				
			||||||
            font-family:"Lucida Console", Monaco, monospace;
 | 
					 | 
				
			||||||
            pointer-events: all;
 | 
					 | 
				
			||||||
            stroke-width: 0;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-pin-lbl-hover {
 | 
					 | 
				
			||||||
            visibility: hidden;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-outline .sim-board-pin-hover:hover + .sim-board-pin-lbl,
 | 
					 | 
				
			||||||
        .sim-board-pin-lbl.highlight {
 | 
					 | 
				
			||||||
            visibility: hidden;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-outline .sim-board-pin-hover:hover + * + .sim-board-pin-lbl-hover,
 | 
					 | 
				
			||||||
        .sim-board-pin-lbl-hover.highlight {
 | 
					 | 
				
			||||||
            visibility: visible;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        /* Graying out */
 | 
					 | 
				
			||||||
        .grayed .sim-board-pin-lbl:not(.highlight) {
 | 
					 | 
				
			||||||
            fill: #AAA;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .grayed .sim-board-pin:not(.highlight) {
 | 
					 | 
				
			||||||
            fill:#BBB;
 | 
					 | 
				
			||||||
            stroke:#777;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .grayed .gray-cover {
 | 
					 | 
				
			||||||
            visibility: inherit;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .grayed .sim-cmp:not(.notgrayed) {
 | 
					 | 
				
			||||||
            opacity: 0.3;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        /* Highlighting */
 | 
					 | 
				
			||||||
        .sim-board-pin-lbl.highlight {
 | 
					 | 
				
			||||||
            fill: #000;
 | 
					 | 
				
			||||||
            font-weight: bold;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-board-pin.highlight {
 | 
					 | 
				
			||||||
            fill:#999;
 | 
					 | 
				
			||||||
            stroke:#000;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        `;
 | 
					 | 
				
			||||||
    const PIN_LBL_SIZE = PIN_DIST * 0.7;
 | 
					 | 
				
			||||||
    const PIN_LBL_HOVER_SIZE = PIN_LBL_SIZE * 1.5;
 | 
					 | 
				
			||||||
    const SQUARE_PIN_WIDTH = PIN_DIST * 0.66666;
 | 
					 | 
				
			||||||
    const SQUARE_PIN_HOVER_WIDTH = PIN_DIST * 0.66666 + PIN_DIST / 3.0;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export interface GenericBoardProps {
 | 
					 | 
				
			||||||
        visualDef: BoardImageDefinition;
 | 
					 | 
				
			||||||
        wireframe?: boolean;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let nextBoardId = 0;
 | 
					 | 
				
			||||||
    export class GenericBoardSvg implements BoardView {
 | 
					 | 
				
			||||||
        private element: SVGSVGElement;
 | 
					 | 
				
			||||||
        private style: SVGStyleElement;
 | 
					 | 
				
			||||||
        private defs: SVGDefsElement;
 | 
					 | 
				
			||||||
        private g: SVGGElement;
 | 
					 | 
				
			||||||
        private background: SVGElement;
 | 
					 | 
				
			||||||
        private width: number;
 | 
					 | 
				
			||||||
        private height: number;
 | 
					 | 
				
			||||||
        private id: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // pins & labels
 | 
					 | 
				
			||||||
        //(truth)
 | 
					 | 
				
			||||||
        private allPins: GridPin[] = [];
 | 
					 | 
				
			||||||
        private allLabels: GridLabel[] = [];
 | 
					 | 
				
			||||||
        //(cache)
 | 
					 | 
				
			||||||
        private pinNmToLbl: Map<GridLabel> = {};
 | 
					 | 
				
			||||||
        private pinNmToPin: Map<GridPin> = {};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        constructor(public props: GenericBoardProps) {
 | 
					 | 
				
			||||||
            //TODO: handle wireframe mode
 | 
					 | 
				
			||||||
            this.id = nextBoardId++;
 | 
					 | 
				
			||||||
            let visDef = props.visualDef;
 | 
					 | 
				
			||||||
            let imgHref = props.wireframe ? visDef.outlineImage : visDef.image;
 | 
					 | 
				
			||||||
            let boardImgAndSize = mkImageSVG({
 | 
					 | 
				
			||||||
                image: imgHref,
 | 
					 | 
				
			||||||
                width: visDef.width,
 | 
					 | 
				
			||||||
                height: visDef.height,
 | 
					 | 
				
			||||||
                imageUnitDist: visDef.pinDist,
 | 
					 | 
				
			||||||
                targetUnitDist: PIN_DIST
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            let scaleFn = mkScaleFn(visDef.pinDist, PIN_DIST);
 | 
					 | 
				
			||||||
            this.width = boardImgAndSize.w;
 | 
					 | 
				
			||||||
            this.height = boardImgAndSize.h;
 | 
					 | 
				
			||||||
            let img = boardImgAndSize.el;
 | 
					 | 
				
			||||||
            this.element = <SVGSVGElement>svg.elt("svg");
 | 
					 | 
				
			||||||
            svg.hydrate(this.element, {
 | 
					 | 
				
			||||||
                "version": "1.0",
 | 
					 | 
				
			||||||
                "viewBox": `0 0 ${this.width} ${this.height}`,
 | 
					 | 
				
			||||||
                "class": `sim sim-board-id-${this.id}`,
 | 
					 | 
				
			||||||
                "x": "0px",
 | 
					 | 
				
			||||||
                "y": "0px"
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            if (props.wireframe)
 | 
					 | 
				
			||||||
                svg.addClass(this.element, "sim-board-outline")
 | 
					 | 
				
			||||||
            this.style = <SVGStyleElement>svg.child(this.element, "style", {});
 | 
					 | 
				
			||||||
            this.style.textContent += BOARD_SYTLE;
 | 
					 | 
				
			||||||
            this.defs = <SVGDefsElement>svg.child(this.element, "defs", {});
 | 
					 | 
				
			||||||
            this.g = <SVGGElement>svg.elt("g");
 | 
					 | 
				
			||||||
            this.element.appendChild(this.g);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // main board
 | 
					 | 
				
			||||||
            this.g.appendChild(img);
 | 
					 | 
				
			||||||
            this.background = img;
 | 
					 | 
				
			||||||
            svg.hydrate(img, { class: "sim-board" });
 | 
					 | 
				
			||||||
            let backgroundCover = this.mkGrayCover(0, 0, this.width, this.height);
 | 
					 | 
				
			||||||
            this.g.appendChild(backgroundCover);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // ----- pins
 | 
					 | 
				
			||||||
            const mkSquarePin = (): SVGElAndSize => {
 | 
					 | 
				
			||||||
                let el = svg.elt("rect");
 | 
					 | 
				
			||||||
                let width = SQUARE_PIN_WIDTH;
 | 
					 | 
				
			||||||
                svg.hydrate(el, {
 | 
					 | 
				
			||||||
                    class: "sim-board-pin",
 | 
					 | 
				
			||||||
                    width: width,
 | 
					 | 
				
			||||||
                    height: width,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                return {el: el, w: width, h: width, x: 0, y: 0};
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            const mkSquareHoverPin = (): SVGElAndSize => {
 | 
					 | 
				
			||||||
                let el = svg.elt("rect");
 | 
					 | 
				
			||||||
                let width = SQUARE_PIN_HOVER_WIDTH;
 | 
					 | 
				
			||||||
                svg.hydrate(el, {
 | 
					 | 
				
			||||||
                    class: "sim-board-pin-hover",
 | 
					 | 
				
			||||||
                    width: width,
 | 
					 | 
				
			||||||
                    height: width
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                return {el: el, w: width, h: width, x: 0, y: 0};
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            const mkPinBlockGrid = (pinBlock: PinBlockDefinition, blockIdx: number) => {
 | 
					 | 
				
			||||||
                let xOffset = scaleFn(pinBlock.x) + PIN_DIST / 2.0;
 | 
					 | 
				
			||||||
                let yOffset = scaleFn(pinBlock.y) + PIN_DIST / 2.0;
 | 
					 | 
				
			||||||
                let rowCount = 1;
 | 
					 | 
				
			||||||
                let colCount = pinBlock.labels.length;
 | 
					 | 
				
			||||||
                let getColName = (colIdx: number) => pinBlock.labels[colIdx];
 | 
					 | 
				
			||||||
                let getRowName = () => `${blockIdx + 1}`
 | 
					 | 
				
			||||||
                let getGroupName = () => pinBlock.labels.join(" ");
 | 
					 | 
				
			||||||
                let gridRes = mkGrid({
 | 
					 | 
				
			||||||
                    xOffset: xOffset,
 | 
					 | 
				
			||||||
                    yOffset: yOffset,
 | 
					 | 
				
			||||||
                    rowCount: rowCount,
 | 
					 | 
				
			||||||
                    colCount: colCount,
 | 
					 | 
				
			||||||
                    pinDist: PIN_DIST,
 | 
					 | 
				
			||||||
                    mkPin: mkSquarePin,
 | 
					 | 
				
			||||||
                    mkHoverPin: mkSquareHoverPin,
 | 
					 | 
				
			||||||
                    getRowName: getRowName,
 | 
					 | 
				
			||||||
                    getColName: getColName,
 | 
					 | 
				
			||||||
                    getGroupName: getGroupName,
 | 
					 | 
				
			||||||
                });
 | 
					 | 
				
			||||||
                let pins = gridRes.allPins;
 | 
					 | 
				
			||||||
                let pinsG = gridRes.g;
 | 
					 | 
				
			||||||
                svg.addClass(gridRes.g, "sim-board-pin-group");
 | 
					 | 
				
			||||||
                return gridRes;
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            let pinBlocks = visDef.pinBlocks.map(mkPinBlockGrid);
 | 
					 | 
				
			||||||
            let pinToBlockDef: PinBlockDefinition[] = [];
 | 
					 | 
				
			||||||
            pinBlocks.forEach((blk, blkIdx) => blk.allPins.forEach((p, pIdx) => {
 | 
					 | 
				
			||||||
                this.allPins.push(p);
 | 
					 | 
				
			||||||
                pinToBlockDef.push(visDef.pinBlocks[blkIdx]);
 | 
					 | 
				
			||||||
            }));
 | 
					 | 
				
			||||||
            //tooltip
 | 
					 | 
				
			||||||
            this.allPins.forEach(p => {
 | 
					 | 
				
			||||||
                let tooltip = p.col;
 | 
					 | 
				
			||||||
                svg.hydrate(p.el, {title: tooltip});
 | 
					 | 
				
			||||||
                svg.hydrate(p.hoverEl, {title: tooltip});
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            //attach pins
 | 
					 | 
				
			||||||
            this.allPins.forEach(p => {
 | 
					 | 
				
			||||||
                this.g.appendChild(p.el);
 | 
					 | 
				
			||||||
                this.g.appendChild(p.hoverEl);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            //catalog pins
 | 
					 | 
				
			||||||
            this.allPins.forEach(p => {
 | 
					 | 
				
			||||||
                this.pinNmToPin[p.col] = p;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // ----- labels
 | 
					 | 
				
			||||||
            const mkLabelTxtEl = (pinX: number, pinY: number, size: number, txt: string, pos: "above" | "below"): SVGTextElement => {
 | 
					 | 
				
			||||||
                //TODO: extract constants
 | 
					 | 
				
			||||||
                let lblY: number;
 | 
					 | 
				
			||||||
                let lblX: number;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                if (pos === "below") {
 | 
					 | 
				
			||||||
                    let lblLen = size * 0.25 * txt.length;
 | 
					 | 
				
			||||||
                    lblX = pinX;
 | 
					 | 
				
			||||||
                    lblY = pinY + 12 + lblLen;
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    let lblLen = size * 0.32 * txt.length;
 | 
					 | 
				
			||||||
                    lblX = pinX;
 | 
					 | 
				
			||||||
                    lblY = pinY - 11 - lblLen;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                let el = mkTxt(lblX, lblY, size, -90, txt);
 | 
					 | 
				
			||||||
                return el;
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
            const mkLabel = (pinX: number, pinY: number, txt: string, pos: "above" | "below"): GridLabel => {
 | 
					 | 
				
			||||||
                let el = mkLabelTxtEl(pinX, pinY, PIN_LBL_SIZE, txt, pos);
 | 
					 | 
				
			||||||
                svg.addClass(el, "sim-board-pin-lbl");
 | 
					 | 
				
			||||||
                let hoverEl = mkLabelTxtEl(pinX, pinY, PIN_LBL_HOVER_SIZE, txt, pos);
 | 
					 | 
				
			||||||
                svg.addClass(hoverEl, "sim-board-pin-lbl-hover");
 | 
					 | 
				
			||||||
                let label: GridLabel = {el: el, hoverEl: hoverEl, txt: txt};
 | 
					 | 
				
			||||||
                return label;
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            this.allLabels = this.allPins.map((p, pIdx) => {
 | 
					 | 
				
			||||||
                let blk = pinToBlockDef[pIdx];
 | 
					 | 
				
			||||||
                return mkLabel(p.cx, p.cy, p.col, blk.labelPosition);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            //attach labels
 | 
					 | 
				
			||||||
            this.allLabels.forEach(l => {
 | 
					 | 
				
			||||||
                this.g.appendChild(l.el);
 | 
					 | 
				
			||||||
                this.g.appendChild(l.hoverEl);
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
            //catalog labels
 | 
					 | 
				
			||||||
            this.allPins.forEach((pin, pinIdx) => {
 | 
					 | 
				
			||||||
                let lbl = this.allLabels[pinIdx];
 | 
					 | 
				
			||||||
                this.pinNmToLbl[pin.col] = lbl;
 | 
					 | 
				
			||||||
            });
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public getCoord(pinNm: string): Coord {
 | 
					 | 
				
			||||||
            let pin = this.pinNmToPin[pinNm];
 | 
					 | 
				
			||||||
            if (!pin)
 | 
					 | 
				
			||||||
                return null;
 | 
					 | 
				
			||||||
            return [pin.cx, pin.cy];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private mkGrayCover(x: number, y: number, w: number, h: number) {
 | 
					 | 
				
			||||||
            let rect = <SVGRectElement>svg.elt("rect");
 | 
					 | 
				
			||||||
            svg.hydrate(rect, {x: x, y: y, width: w, height: h, class: "gray-cover"});
 | 
					 | 
				
			||||||
            return rect;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public getView(): SVGAndSize<SVGSVGElement> {
 | 
					 | 
				
			||||||
            return {el: this.element, w: this.width, h: this.height, x: 0, y: 0};
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public getPinDist() {
 | 
					 | 
				
			||||||
            return PIN_DIST;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public highlightPin(pinNm: string) {
 | 
					 | 
				
			||||||
            let lbl = this.pinNmToLbl[pinNm];
 | 
					 | 
				
			||||||
            let pin = this.pinNmToPin[pinNm];
 | 
					 | 
				
			||||||
            if (lbl && pin) {
 | 
					 | 
				
			||||||
                svg.addClass(lbl.el, "highlight");
 | 
					 | 
				
			||||||
                svg.addClass(lbl.hoverEl, "highlight");
 | 
					 | 
				
			||||||
                svg.addClass(pin.el, "highlight");
 | 
					 | 
				
			||||||
                svg.addClass(pin.hoverEl, "highlight");
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -1,35 +0,0 @@
 | 
				
			|||||||
 | 
					 | 
				
			||||||
namespace pxsim.visuals {
 | 
					 | 
				
			||||||
    export function mkGenericPartSVG(partVisual: PartVisualDefinition): SVGAndSize<SVGImageElement> {
 | 
					 | 
				
			||||||
        let imgAndSize = mkImageSVG({
 | 
					 | 
				
			||||||
            image: partVisual.image,
 | 
					 | 
				
			||||||
            width: partVisual.width,
 | 
					 | 
				
			||||||
            height: partVisual.height,
 | 
					 | 
				
			||||||
            imageUnitDist: partVisual.pinDistance,
 | 
					 | 
				
			||||||
            targetUnitDist: PIN_DIST
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        return imgAndSize;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export class GenericPart implements IBoardPart<any> {
 | 
					 | 
				
			||||||
        public style: string = "";
 | 
					 | 
				
			||||||
        public element: SVGElement;
 | 
					 | 
				
			||||||
        defs: SVGElement[] = [];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        constructor(partVisual: PartVisualDefinition) {
 | 
					 | 
				
			||||||
            let imgAndSize = mkGenericPartSVG(partVisual);
 | 
					 | 
				
			||||||
            let img = imgAndSize.el;
 | 
					 | 
				
			||||||
            this.element = svg.elt("g");
 | 
					 | 
				
			||||||
            this.element.appendChild(img);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        moveToCoord(xy: Coord): void {
 | 
					 | 
				
			||||||
            translateEl(this.element, xy);
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        //unused
 | 
					 | 
				
			||||||
        init(bus: EventBus, state: any, svgEl: SVGSVGElement): void { }
 | 
					 | 
				
			||||||
        updateState(): void { }
 | 
					 | 
				
			||||||
        updateTheme(): void { }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -2,8 +2,6 @@
 | 
				
			|||||||
/// <reference path="../../libs/microbit/dal.d.ts"/>
 | 
					/// <reference path="../../libs/microbit/dal.d.ts"/>
 | 
				
			||||||
/// <reference path="../../libs/microbit/shims.d.ts"/>
 | 
					/// <reference path="../../libs/microbit/shims.d.ts"/>
 | 
				
			||||||
/// <reference path="../../libs/microbit/enums.d.ts"/>
 | 
					/// <reference path="../../libs/microbit/enums.d.ts"/>
 | 
				
			||||||
/// <reference path="../state/neopixel.ts"/>
 | 
					 | 
				
			||||||
/// <reference path="../simlib.ts"/>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
//TODO move to utils
 | 
					//TODO move to utils
 | 
				
			||||||
namespace pxsim.visuals {
 | 
					namespace pxsim.visuals {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,470 +0,0 @@
 | 
				
			|||||||
namespace pxsim.visuals {
 | 
					 | 
				
			||||||
    const WIRE_WIDTH = PIN_DIST / 2.5;
 | 
					 | 
				
			||||||
    const BB_WIRE_SMOOTH = 0.7;
 | 
					 | 
				
			||||||
    const INSTR_WIRE_SMOOTH = 0.8;
 | 
					 | 
				
			||||||
    const WIRE_PART_CURVE_OFF = 15;
 | 
					 | 
				
			||||||
    const WIRE_PART_LENGTH = 100;
 | 
					 | 
				
			||||||
    export const WIRES_CSS = `
 | 
					 | 
				
			||||||
        .sim-bb-wire {
 | 
					 | 
				
			||||||
            fill:none;
 | 
					 | 
				
			||||||
            stroke-linecap: round;
 | 
					 | 
				
			||||||
            stroke-width:${WIRE_WIDTH}px;
 | 
					 | 
				
			||||||
            pointer-events: none;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-wire-end {
 | 
					 | 
				
			||||||
            stroke:#333;
 | 
					 | 
				
			||||||
            fill:#333;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-wire-bare-end {
 | 
					 | 
				
			||||||
            fill: #ccc;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-wire-hover {
 | 
					 | 
				
			||||||
            stroke-width: ${WIRE_WIDTH}px;
 | 
					 | 
				
			||||||
            visibility: hidden;
 | 
					 | 
				
			||||||
            stroke-dasharray: ${PIN_DIST / 10.0},${PIN_DIST / 1.5};
 | 
					 | 
				
			||||||
            /*stroke-opacity: 0.4;*/
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .grayed .sim-bb-wire-ends-g:not(.highlight) .sim-bb-wire-end {
 | 
					 | 
				
			||||||
            stroke: #777;
 | 
					 | 
				
			||||||
            fill: #777;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .grayed .sim-bb-wire:not(.highlight) {
 | 
					 | 
				
			||||||
            stroke: #CCC;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-wire-ends-g:hover .sim-bb-wire-end {
 | 
					 | 
				
			||||||
            stroke: red;
 | 
					 | 
				
			||||||
            fill: red;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        .sim-bb-wire-ends-g:hover .sim-bb-wire-bare-end {
 | 
					 | 
				
			||||||
            stroke: #FFF;
 | 
					 | 
				
			||||||
            fill: #FFF;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        `;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    export interface Wire {
 | 
					 | 
				
			||||||
        endG: SVGGElement;
 | 
					 | 
				
			||||||
        end1: SVGElement;
 | 
					 | 
				
			||||||
        end2: SVGElement;
 | 
					 | 
				
			||||||
        wires: SVGElement[];
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    function cssEncodeColor(color: string): string {
 | 
					 | 
				
			||||||
        //HACK/TODO: do real CSS encoding.
 | 
					 | 
				
			||||||
        return color
 | 
					 | 
				
			||||||
            .replace(/\#/g, "-")
 | 
					 | 
				
			||||||
            .replace(/\(/g, "-")
 | 
					 | 
				
			||||||
            .replace(/\)/g, "-")
 | 
					 | 
				
			||||||
            .replace(/\,/g, "-")
 | 
					 | 
				
			||||||
            .replace(/\./g, "-")
 | 
					 | 
				
			||||||
            .replace(/\s/g, "");
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export enum WireEndStyle {
 | 
					 | 
				
			||||||
        BBJumper,
 | 
					 | 
				
			||||||
        OpenJumper,
 | 
					 | 
				
			||||||
        Croc,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export interface WireOpts { //TODO: use throughout
 | 
					 | 
				
			||||||
        color?: string,
 | 
					 | 
				
			||||||
        colorClass?: string,
 | 
					 | 
				
			||||||
        bendFactor?: number,
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    export function mkWirePart(cp: [number, number], clr: string, croc: boolean = false): visuals.SVGAndSize<SVGGElement> {
 | 
					 | 
				
			||||||
        let g = <SVGGElement>svg.elt("g");
 | 
					 | 
				
			||||||
        let [cx, cy] = cp;
 | 
					 | 
				
			||||||
        let offset = WIRE_PART_CURVE_OFF;
 | 
					 | 
				
			||||||
        let p1: visuals.Coord = [cx - offset, cy - WIRE_PART_LENGTH / 2];
 | 
					 | 
				
			||||||
        let p2: visuals.Coord = [cx + offset, cy + WIRE_PART_LENGTH / 2];
 | 
					 | 
				
			||||||
        clr = visuals.mapWireColor(clr);
 | 
					 | 
				
			||||||
        let e1: SVGElAndSize;
 | 
					 | 
				
			||||||
        if (croc)
 | 
					 | 
				
			||||||
            e1 = mkCrocEnd(p1, true, clr);
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
            e1 = mkOpenJumperEnd(p1, true, clr);
 | 
					 | 
				
			||||||
        let s = mkWirePartSeg(p1, p2, clr);
 | 
					 | 
				
			||||||
        let e2 = mkOpenJumperEnd(p2, false, clr);
 | 
					 | 
				
			||||||
        g.appendChild(s.el);
 | 
					 | 
				
			||||||
        g.appendChild(e1.el);
 | 
					 | 
				
			||||||
        g.appendChild(e2.el);
 | 
					 | 
				
			||||||
        let l = Math.min(e1.x, e2.x);
 | 
					 | 
				
			||||||
        let r = Math.max(e1.x + e1.w, e2.x + e2.w);
 | 
					 | 
				
			||||||
        let t = Math.min(e1.y, e2.y);
 | 
					 | 
				
			||||||
        let b = Math.max(e1.y + e1.h, e2.y + e2.h);
 | 
					 | 
				
			||||||
        return {el: g, x: l, y: t, w: r - l, h: b - t};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function mkCurvedWireSeg(p1: [number, number], p2: [number, number], smooth: number, clrClass: string): SVGPathElement {
 | 
					 | 
				
			||||||
        const coordStr = (xy: [number, number]): string => {return `${xy[0]}, ${xy[1]}`};
 | 
					 | 
				
			||||||
        let [x1, y1] = p1;
 | 
					 | 
				
			||||||
        let [x2, y2] = p2
 | 
					 | 
				
			||||||
        let yLen = (y2 - y1);
 | 
					 | 
				
			||||||
        let c1: [number, number] = [x1, y1 + yLen * smooth];
 | 
					 | 
				
			||||||
        let c2: [number, number] = [x2, y2 - yLen * smooth];
 | 
					 | 
				
			||||||
        let w = <SVGPathElement>svg.mkPath("sim-bb-wire", `M${coordStr(p1)} C${coordStr(c1)} ${coordStr(c2)} ${coordStr(p2)}`);
 | 
					 | 
				
			||||||
        svg.addClass(w, `wire-stroke-${clrClass}`);
 | 
					 | 
				
			||||||
        return w;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function mkWirePartSeg(p1: [number, number], p2: [number, number], clr: string): visuals.SVGAndSize<SVGPathElement> {
 | 
					 | 
				
			||||||
        //TODO: merge with mkCurvedWireSeg
 | 
					 | 
				
			||||||
        const coordStr = (xy: [number, number]): string => {return `${xy[0]}, ${xy[1]}`};
 | 
					 | 
				
			||||||
        let [x1, y1] = p1;
 | 
					 | 
				
			||||||
        let [x2, y2] = p2
 | 
					 | 
				
			||||||
        let yLen = (y2 - y1);
 | 
					 | 
				
			||||||
        let c1: [number, number] = [x1, y1 + yLen * .8];
 | 
					 | 
				
			||||||
        let c2: [number, number] = [x2, y2 - yLen * .8];
 | 
					 | 
				
			||||||
        let e = <SVGPathElement>svg.mkPath("sim-bb-wire", `M${coordStr(p1)} C${coordStr(c1)} ${coordStr(c2)} ${coordStr(p2)}`);
 | 
					 | 
				
			||||||
        (<any>e).style["stroke"] = clr;
 | 
					 | 
				
			||||||
        return {el: e, x: Math.min(x1, x2), y: Math.min(y1, y2), w: Math.abs(x1 - x2), h: Math.abs(y1 - y2)};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function mkWireSeg(p1: [number, number], p2: [number, number], clrClass: string): SVGPathElement {
 | 
					 | 
				
			||||||
        const coordStr = (xy: [number, number]): string => {return `${xy[0]}, ${xy[1]}`};
 | 
					 | 
				
			||||||
        let w = <SVGPathElement>svg.mkPath("sim-bb-wire", `M${coordStr(p1)} L${coordStr(p2)}`);
 | 
					 | 
				
			||||||
        svg.addClass(w, `wire-stroke-${clrClass}`);
 | 
					 | 
				
			||||||
        return w;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function mkBBJumperEnd(p: [number, number], clrClass: string): SVGElement {
 | 
					 | 
				
			||||||
        const endW = PIN_DIST / 4;
 | 
					 | 
				
			||||||
        let w = svg.elt("circle");
 | 
					 | 
				
			||||||
        let x = p[0];
 | 
					 | 
				
			||||||
        let y = p[1];
 | 
					 | 
				
			||||||
        let r = WIRE_WIDTH / 2 + endW / 2;
 | 
					 | 
				
			||||||
        svg.hydrate(w, {cx: x, cy: y, r: r, class: "sim-bb-wire-end"});
 | 
					 | 
				
			||||||
        svg.addClass(w, `wire-fill-${clrClass}`);
 | 
					 | 
				
			||||||
        (<any>w).style["stroke-width"] = `${endW}px`;
 | 
					 | 
				
			||||||
        return w;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function mkOpenJumperEnd(p: [number, number], top: boolean, clr: string): visuals.SVGElAndSize {
 | 
					 | 
				
			||||||
        let k = visuals.PIN_DIST * 0.24;
 | 
					 | 
				
			||||||
        let plasticLength = k * 10;
 | 
					 | 
				
			||||||
        let plasticWidth = k * 2;
 | 
					 | 
				
			||||||
        let metalLength = k * 6;
 | 
					 | 
				
			||||||
        let metalWidth = k;
 | 
					 | 
				
			||||||
        const strokeWidth = visuals.PIN_DIST / 4.0;
 | 
					 | 
				
			||||||
        let [cx, cy] = p;
 | 
					 | 
				
			||||||
        let o = top ? -1 : 1;
 | 
					 | 
				
			||||||
        let g = svg.elt("g")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let el = svg.elt("rect");
 | 
					 | 
				
			||||||
        let h1 = plasticLength;
 | 
					 | 
				
			||||||
        let w1 = plasticWidth;
 | 
					 | 
				
			||||||
        let x1 = cx - w1 / 2;
 | 
					 | 
				
			||||||
        let y1 = cy - (h1 / 2);
 | 
					 | 
				
			||||||
        svg.hydrate(el, {x: x1, y: y1, width: w1, height: h1, rx: 0.5, ry: 0.5, class: "sim-bb-wire-end"});
 | 
					 | 
				
			||||||
        (<any>el).style["stroke-width"] = `${strokeWidth}px`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let el2 = svg.elt("rect");
 | 
					 | 
				
			||||||
        let h2 = metalLength;
 | 
					 | 
				
			||||||
        let w2 = metalWidth;
 | 
					 | 
				
			||||||
        let cy2 = cy + o * (h1 / 2 + h2 / 2);
 | 
					 | 
				
			||||||
        let x2 = cx - w2 / 2;
 | 
					 | 
				
			||||||
        let y2 = cy2 - (h2 / 2);
 | 
					 | 
				
			||||||
        svg.hydrate(el2, {x: x2, y: y2, width: w2, height: h2, class: "sim-bb-wire-bare-end"});
 | 
					 | 
				
			||||||
        (<any>el2).style["fill"] = `#bbb`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        g.appendChild(el2);
 | 
					 | 
				
			||||||
        g.appendChild(el);
 | 
					 | 
				
			||||||
        return {el: g, x: x1 - strokeWidth, y: Math.min(y1, y2), w: w1 + strokeWidth * 2, h: h1 + h2};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function mkSmallMBPinEnd(p: [number, number], top: boolean, clr: string): visuals.SVGElAndSize {
 | 
					 | 
				
			||||||
        //HACK
 | 
					 | 
				
			||||||
        //TODO: merge with mkOpenJumperEnd()
 | 
					 | 
				
			||||||
        let k = visuals.PIN_DIST * 0.24;
 | 
					 | 
				
			||||||
        let plasticLength = k * 4;
 | 
					 | 
				
			||||||
        let plasticWidth = k * 1.2;
 | 
					 | 
				
			||||||
        let metalLength = k * 10;
 | 
					 | 
				
			||||||
        let metalWidth = k;
 | 
					 | 
				
			||||||
        const strokeWidth = visuals.PIN_DIST / 4.0;
 | 
					 | 
				
			||||||
        let [cx, cy] = p;
 | 
					 | 
				
			||||||
        let yOffset = 10;
 | 
					 | 
				
			||||||
        let o = top ? -1 : 1;
 | 
					 | 
				
			||||||
        let g = svg.elt("g")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let el = svg.elt("rect");
 | 
					 | 
				
			||||||
        let h1 = plasticLength;
 | 
					 | 
				
			||||||
        let w1 = plasticWidth;
 | 
					 | 
				
			||||||
        let x1 = cx - w1 / 2;
 | 
					 | 
				
			||||||
        let y1 = cy + yOffset - (h1 / 2);
 | 
					 | 
				
			||||||
        svg.hydrate(el, {x: x1, y: y1, width: w1, height: h1, rx: 0.5, ry: 0.5, class: "sim-bb-wire-end"});
 | 
					 | 
				
			||||||
        (<any>el).style["stroke-width"] = `${strokeWidth}px`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let el2 = svg.elt("rect");
 | 
					 | 
				
			||||||
        let h2 = metalLength;
 | 
					 | 
				
			||||||
        let w2 = metalWidth;
 | 
					 | 
				
			||||||
        let cy2 = cy + yOffset + o * (h1 / 2 + h2 / 2);
 | 
					 | 
				
			||||||
        let x2 = cx - w2 / 2;
 | 
					 | 
				
			||||||
        let y2 = cy2 - (h2 / 2);
 | 
					 | 
				
			||||||
        svg.hydrate(el2, {x: x2, y: y2, width: w2, height: h2, class: "sim-bb-wire-bare-end"});
 | 
					 | 
				
			||||||
        (<any>el2).style["fill"] = `#bbb`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        g.appendChild(el2);
 | 
					 | 
				
			||||||
        g.appendChild(el);
 | 
					 | 
				
			||||||
        return {el: g, x: x1 - strokeWidth, y: Math.min(y1, y2), w: w1 + strokeWidth * 2, h: h1 + h2};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    function mkCrocEnd(p: [number, number], top: boolean, clr: string): SVGElAndSize {
 | 
					 | 
				
			||||||
        //TODO: merge with mkOpenJumperEnd()
 | 
					 | 
				
			||||||
        let k = visuals.PIN_DIST * 0.24;
 | 
					 | 
				
			||||||
        const plasticWidth = k * 4;
 | 
					 | 
				
			||||||
        const plasticLength = k * 10.0;
 | 
					 | 
				
			||||||
        const metalWidth = k * 3.5;
 | 
					 | 
				
			||||||
        const metalHeight = k * 3.5;
 | 
					 | 
				
			||||||
        const pointScalar = .15;
 | 
					 | 
				
			||||||
        const baseScalar = .3;
 | 
					 | 
				
			||||||
        const taperScalar = .7;
 | 
					 | 
				
			||||||
        const strokeWidth = visuals.PIN_DIST / 4.0;
 | 
					 | 
				
			||||||
        let [cx, cy] = p;
 | 
					 | 
				
			||||||
        let o = top ? -1 : 1;
 | 
					 | 
				
			||||||
        let g = svg.elt("g")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let el = svg.elt("polygon");
 | 
					 | 
				
			||||||
        let h1 = plasticLength;
 | 
					 | 
				
			||||||
        let w1 = plasticWidth;
 | 
					 | 
				
			||||||
        let x1 = cx - w1 / 2;
 | 
					 | 
				
			||||||
        let y1 = cy - (h1 / 2);
 | 
					 | 
				
			||||||
        let mkPnt = (xy: Coord) => `${xy[0]},${xy[1]}`;
 | 
					 | 
				
			||||||
        let mkPnts = (...xys: Coord[]) => xys.map(xy => mkPnt(xy)).join(" ");
 | 
					 | 
				
			||||||
        const topScalar = top ? pointScalar : baseScalar;
 | 
					 | 
				
			||||||
        const midScalar = top ? taperScalar : (1 - taperScalar);
 | 
					 | 
				
			||||||
        const botScalar = top ? baseScalar : pointScalar;
 | 
					 | 
				
			||||||
        svg.hydrate(el, {
 | 
					 | 
				
			||||||
            points: mkPnts(
 | 
					 | 
				
			||||||
                [x1 + w1 * topScalar, y1], //TL
 | 
					 | 
				
			||||||
                [x1 + w1 * (1 - topScalar), y1], //TR
 | 
					 | 
				
			||||||
                [x1 + w1, y1 + h1 * midScalar], //MR
 | 
					 | 
				
			||||||
                [x1 + w1 * (1 - botScalar), y1 + h1], //BR
 | 
					 | 
				
			||||||
                [x1 + w1 * botScalar, y1 + h1], //BL
 | 
					 | 
				
			||||||
                [x1, y1 + h1 * midScalar]) //ML
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
        svg.hydrate(el, {rx: 0.5, ry: 0.5, class: "sim-bb-wire-end"});
 | 
					 | 
				
			||||||
        (<any>el).style["stroke-width"] = `${strokeWidth}px`;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        let el2 = svg.elt("rect");
 | 
					 | 
				
			||||||
        let h2 = metalWidth;
 | 
					 | 
				
			||||||
        let w2 = metalHeight;
 | 
					 | 
				
			||||||
        let cy2 = cy + o * (h1 / 2 + h2 / 2);
 | 
					 | 
				
			||||||
        let x2 = cx - w2 / 2;
 | 
					 | 
				
			||||||
        let y2 = cy2 - (h2 / 2);
 | 
					 | 
				
			||||||
        svg.hydrate(el2, {x: x2, y: y2, width: w2, height: h2, class: "sim-bb-wire-bare-end"});
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        g.appendChild(el2);
 | 
					 | 
				
			||||||
        g.appendChild(el);
 | 
					 | 
				
			||||||
        return {el: g, x: x1 - strokeWidth, y: Math.min(y1, y2), w: w1 + strokeWidth * 2, h: h1 + h2};
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    //TODO: make this stupid class obsolete
 | 
					 | 
				
			||||||
    export class WireFactory {
 | 
					 | 
				
			||||||
        private underboard: SVGGElement;
 | 
					 | 
				
			||||||
        private overboard: SVGGElement;
 | 
					 | 
				
			||||||
        private boardEdges: number[];
 | 
					 | 
				
			||||||
        private getLocCoord: (loc: Loc) => Coord;
 | 
					 | 
				
			||||||
        public styleEl: SVGStyleElement;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        constructor(underboard: SVGGElement, overboard: SVGGElement, boardEdges: number[], styleEl: SVGStyleElement, getLocCoord: (loc: Loc) => Coord) {
 | 
					 | 
				
			||||||
            this.styleEl = styleEl;
 | 
					 | 
				
			||||||
            this.styleEl.textContent += WIRES_CSS;
 | 
					 | 
				
			||||||
            this.underboard = underboard;
 | 
					 | 
				
			||||||
            this.overboard = overboard;
 | 
					 | 
				
			||||||
            this.boardEdges = boardEdges;
 | 
					 | 
				
			||||||
            this.getLocCoord = getLocCoord;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private indexOfMin(vs: number[]): number {
 | 
					 | 
				
			||||||
            let minIdx = 0;
 | 
					 | 
				
			||||||
            let min = vs[0];
 | 
					 | 
				
			||||||
            for (let i = 1; i < vs.length; i++) {
 | 
					 | 
				
			||||||
                if (vs[i] < min) {
 | 
					 | 
				
			||||||
                    min = vs[i];
 | 
					 | 
				
			||||||
                    minIdx = i;
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return minIdx;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private closestEdgeIdx(p: [number, number]): number {
 | 
					 | 
				
			||||||
            let dists = this.boardEdges.map(e => Math.abs(p[1] - e));
 | 
					 | 
				
			||||||
            let edgeIdx = this.indexOfMin(dists);
 | 
					 | 
				
			||||||
            return edgeIdx;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private closestEdge(p: [number, number]): number {
 | 
					 | 
				
			||||||
            return this.boardEdges[this.closestEdgeIdx(p)];
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        private nextWireId = 0;
 | 
					 | 
				
			||||||
        private drawWire(pin1: Coord, pin2: Coord, color: string): Wire {
 | 
					 | 
				
			||||||
            let wires: SVGElement[] = [];
 | 
					 | 
				
			||||||
            let g = svg.child(this.overboard, "g", {class: "sim-bb-wire-group"});
 | 
					 | 
				
			||||||
            const closestPointOffBoard = (p: [number, number]): [number, number] => {
 | 
					 | 
				
			||||||
                const offset = PIN_DIST / 2;
 | 
					 | 
				
			||||||
                let e = this.closestEdge(p);
 | 
					 | 
				
			||||||
                let y: number;
 | 
					 | 
				
			||||||
                if (e - p[1] < 0)
 | 
					 | 
				
			||||||
                    y = e - offset;
 | 
					 | 
				
			||||||
                else
 | 
					 | 
				
			||||||
                    y = e + offset;
 | 
					 | 
				
			||||||
                return [p[0], y];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            let wireId = this.nextWireId++;
 | 
					 | 
				
			||||||
            let clrClass = cssEncodeColor(color);
 | 
					 | 
				
			||||||
            let end1 = mkBBJumperEnd(pin1, clrClass);
 | 
					 | 
				
			||||||
            let end2 = mkBBJumperEnd(pin2, clrClass);
 | 
					 | 
				
			||||||
            let endG = <SVGGElement>svg.child(g, "g", {class: "sim-bb-wire-ends-g"});
 | 
					 | 
				
			||||||
            endG.appendChild(end1);
 | 
					 | 
				
			||||||
            endG.appendChild(end2);
 | 
					 | 
				
			||||||
            let edgeIdx1 = this.closestEdgeIdx(pin1);
 | 
					 | 
				
			||||||
            let edgeIdx2 = this.closestEdgeIdx(pin2);
 | 
					 | 
				
			||||||
            if (edgeIdx1 == edgeIdx2) {
 | 
					 | 
				
			||||||
                let seg = mkWireSeg(pin1, pin2, clrClass);
 | 
					 | 
				
			||||||
                g.appendChild(seg);
 | 
					 | 
				
			||||||
                wires.push(seg);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                let offP1 = closestPointOffBoard(pin1);
 | 
					 | 
				
			||||||
                let offP2 = closestPointOffBoard(pin2);
 | 
					 | 
				
			||||||
                let offSeg1 = mkWireSeg(pin1, offP1, clrClass);
 | 
					 | 
				
			||||||
                let offSeg2 = mkWireSeg(pin2, offP2, clrClass);
 | 
					 | 
				
			||||||
                let midSeg: SVGElement;
 | 
					 | 
				
			||||||
                let midSegHover: SVGElement;
 | 
					 | 
				
			||||||
                let isBetweenMiddleTwoEdges = (edgeIdx1 == 1 || edgeIdx1 == 2) && (edgeIdx2 == 1 || edgeIdx2 == 2);
 | 
					 | 
				
			||||||
                if (isBetweenMiddleTwoEdges) {
 | 
					 | 
				
			||||||
                    midSeg = mkCurvedWireSeg(offP1, offP2, BB_WIRE_SMOOTH, clrClass);
 | 
					 | 
				
			||||||
                    midSegHover =  mkCurvedWireSeg(offP1, offP2, BB_WIRE_SMOOTH, clrClass);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    midSeg = mkWireSeg(offP1, offP2, clrClass);
 | 
					 | 
				
			||||||
                    midSegHover = mkWireSeg(offP1, offP2, clrClass);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                svg.addClass(midSegHover, "sim-bb-wire-hover");
 | 
					 | 
				
			||||||
                g.appendChild(offSeg1);
 | 
					 | 
				
			||||||
                wires.push(offSeg1);
 | 
					 | 
				
			||||||
                g.appendChild(offSeg2);
 | 
					 | 
				
			||||||
                wires.push(offSeg2);
 | 
					 | 
				
			||||||
                this.underboard.appendChild(midSeg);
 | 
					 | 
				
			||||||
                wires.push(midSeg);
 | 
					 | 
				
			||||||
                g.appendChild(midSegHover);
 | 
					 | 
				
			||||||
                wires.push(midSegHover);
 | 
					 | 
				
			||||||
                //set hover mechanism
 | 
					 | 
				
			||||||
                let wireIdClass = `sim-bb-wire-id-${wireId}`;
 | 
					 | 
				
			||||||
                const setId = (e: SVGElement) => svg.addClass(e, wireIdClass);
 | 
					 | 
				
			||||||
                setId(endG);
 | 
					 | 
				
			||||||
                setId(midSegHover);
 | 
					 | 
				
			||||||
                this.styleEl.textContent += `
 | 
					 | 
				
			||||||
                    .${wireIdClass}:hover ~ .${wireIdClass}.sim-bb-wire-hover {
 | 
					 | 
				
			||||||
                        visibility: visible;
 | 
					 | 
				
			||||||
                    }`
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // wire colors
 | 
					 | 
				
			||||||
            let colorCSS =  `
 | 
					 | 
				
			||||||
                .wire-stroke-${clrClass} {
 | 
					 | 
				
			||||||
                    stroke: ${mapWireColor(color)};
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                .wire-fill-${clrClass} {
 | 
					 | 
				
			||||||
                    fill: ${mapWireColor(color)};
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                `
 | 
					 | 
				
			||||||
            this.styleEl.textContent += colorCSS;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return {endG: endG, end1: end1, end2: end2, wires: wires};
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        private drawWireWithCrocs(pin1: Coord, pin2: Coord, color: string, smallPin: boolean = false): Wire {
 | 
					 | 
				
			||||||
            //TODO: merge with drawWire()
 | 
					 | 
				
			||||||
            const PIN_Y_OFF = 40;
 | 
					 | 
				
			||||||
            const CROC_Y_OFF = -17;
 | 
					 | 
				
			||||||
            let wires: SVGElement[] = [];
 | 
					 | 
				
			||||||
            let g = svg.child(this.overboard, "g", {class: "sim-bb-wire-group"});
 | 
					 | 
				
			||||||
            const closestPointOffBoard = (p: [number, number]): [number, number] => {
 | 
					 | 
				
			||||||
                const offset = PIN_DIST / 2;
 | 
					 | 
				
			||||||
                let e = this.closestEdge(p);
 | 
					 | 
				
			||||||
                let y: number;
 | 
					 | 
				
			||||||
                if (e - p[1] < 0)
 | 
					 | 
				
			||||||
                    y = e - offset;
 | 
					 | 
				
			||||||
                else
 | 
					 | 
				
			||||||
                    y = e + offset;
 | 
					 | 
				
			||||||
                return [p[0], y];
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            let wireId = this.nextWireId++;
 | 
					 | 
				
			||||||
            let clrClass = cssEncodeColor(color);
 | 
					 | 
				
			||||||
            let end1 = mkBBJumperEnd(pin1, clrClass);
 | 
					 | 
				
			||||||
            let pin2orig = pin2;
 | 
					 | 
				
			||||||
            let [x2, y2] = pin2;
 | 
					 | 
				
			||||||
            pin2 = [x2, y2 + PIN_Y_OFF];//HACK
 | 
					 | 
				
			||||||
            [x2, y2] = pin2;
 | 
					 | 
				
			||||||
            let endCoord2: Coord = [x2, y2 + CROC_Y_OFF]
 | 
					 | 
				
			||||||
            let end2AndSize: SVGElAndSize;
 | 
					 | 
				
			||||||
            if (smallPin)
 | 
					 | 
				
			||||||
                end2AndSize = mkSmallMBPinEnd(endCoord2, true, color);
 | 
					 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
                end2AndSize = mkCrocEnd(endCoord2, true, color);
 | 
					 | 
				
			||||||
            let end2 = end2AndSize.el;
 | 
					 | 
				
			||||||
            let endG = <SVGGElement>svg.child(g, "g", {class: "sim-bb-wire-ends-g"});
 | 
					 | 
				
			||||||
            endG.appendChild(end1);
 | 
					 | 
				
			||||||
            //endG.appendChild(end2);
 | 
					 | 
				
			||||||
            let edgeIdx1 = this.closestEdgeIdx(pin1);
 | 
					 | 
				
			||||||
            let edgeIdx2 = this.closestEdgeIdx(pin2orig);
 | 
					 | 
				
			||||||
            if (edgeIdx1 == edgeIdx2) {
 | 
					 | 
				
			||||||
                let seg = mkWireSeg(pin1, pin2, clrClass);
 | 
					 | 
				
			||||||
                g.appendChild(seg);
 | 
					 | 
				
			||||||
                wires.push(seg);
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                let offP1 = closestPointOffBoard(pin1);
 | 
					 | 
				
			||||||
                //let offP2 = closestPointOffBoard(pin2orig);
 | 
					 | 
				
			||||||
                let offSeg1 = mkWireSeg(pin1, offP1, clrClass);
 | 
					 | 
				
			||||||
                //let offSeg2 = mkWireSeg(pin2, offP2, clrClass);
 | 
					 | 
				
			||||||
                let midSeg: SVGElement;
 | 
					 | 
				
			||||||
                let midSegHover: SVGElement;
 | 
					 | 
				
			||||||
                let isBetweenMiddleTwoEdges = (edgeIdx1 == 1 || edgeIdx1 == 2) && (edgeIdx2 == 1 || edgeIdx2 == 2);
 | 
					 | 
				
			||||||
                if (isBetweenMiddleTwoEdges) {
 | 
					 | 
				
			||||||
                    midSeg = mkCurvedWireSeg(offP1, pin2, BB_WIRE_SMOOTH, clrClass);
 | 
					 | 
				
			||||||
                    midSegHover =  mkCurvedWireSeg(offP1, pin2, BB_WIRE_SMOOTH, clrClass);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    midSeg = mkWireSeg(offP1, pin2, clrClass);
 | 
					 | 
				
			||||||
                    midSegHover = mkWireSeg(offP1, pin2, clrClass);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                svg.addClass(midSegHover, "sim-bb-wire-hover");
 | 
					 | 
				
			||||||
                g.appendChild(offSeg1);
 | 
					 | 
				
			||||||
                wires.push(offSeg1);
 | 
					 | 
				
			||||||
                // g.appendChild(offSeg2);
 | 
					 | 
				
			||||||
                // wires.push(offSeg2);
 | 
					 | 
				
			||||||
                this.underboard.appendChild(midSeg);
 | 
					 | 
				
			||||||
                wires.push(midSeg);
 | 
					 | 
				
			||||||
                //g.appendChild(midSegHover);
 | 
					 | 
				
			||||||
                //wires.push(midSegHover);
 | 
					 | 
				
			||||||
                //set hover mechanism
 | 
					 | 
				
			||||||
                let wireIdClass = `sim-bb-wire-id-${wireId}`;
 | 
					 | 
				
			||||||
                const setId = (e: SVGElement) => svg.addClass(e, wireIdClass);
 | 
					 | 
				
			||||||
                setId(endG);
 | 
					 | 
				
			||||||
                setId(midSegHover);
 | 
					 | 
				
			||||||
                this.styleEl.textContent += `
 | 
					 | 
				
			||||||
                    .${wireIdClass}:hover ~ .${wireIdClass}.sim-bb-wire-hover {
 | 
					 | 
				
			||||||
                        visibility: visible;
 | 
					 | 
				
			||||||
                    }`
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            endG.appendChild(end2);//HACK
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            // wire colors
 | 
					 | 
				
			||||||
            let colorCSS =  `
 | 
					 | 
				
			||||||
                .wire-stroke-${clrClass} {
 | 
					 | 
				
			||||||
                    stroke: ${mapWireColor(color)};
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                .wire-fill-${clrClass} {
 | 
					 | 
				
			||||||
                    fill: ${mapWireColor(color)};
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
                `
 | 
					 | 
				
			||||||
            this.styleEl.textContent += colorCSS;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            return {endG: endG, end1: end1, end2: end2, wires: wires};
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        public addWire(start: Loc, end: Loc, color: string, withCrocs: boolean = false): Wire {
 | 
					 | 
				
			||||||
            let startLoc = this.getLocCoord(start);
 | 
					 | 
				
			||||||
            let endLoc = this.getLocCoord(end);
 | 
					 | 
				
			||||||
            let wireEls: Wire;
 | 
					 | 
				
			||||||
            if (withCrocs && end.type == "dalboard") {
 | 
					 | 
				
			||||||
                let boardPin = (<BoardLoc>end).pin;
 | 
					 | 
				
			||||||
                if (boardPin == "P0" || boardPin == "P1" || boardPin == "P2" || boardPin == "GND" || boardPin == "+3v3" ) {
 | 
					 | 
				
			||||||
                    //HACK
 | 
					 | 
				
			||||||
                    wireEls = this.drawWireWithCrocs(startLoc, endLoc, color);
 | 
					 | 
				
			||||||
                } else {
 | 
					 | 
				
			||||||
                    wireEls = this.drawWireWithCrocs(startLoc, endLoc, color, true);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            } else {
 | 
					 | 
				
			||||||
                wireEls = this.drawWire(startLoc, endLoc, color);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            return wireEls;
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
		Reference in New Issue
	
	Block a user