diff --git a/sim/allocator.ts b/sim/allocator.ts deleted file mode 100644 index cb1acaaa..00000000 --- a/sim/allocator.ts +++ /dev/null @@ -1,672 +0,0 @@ -namespace pxsim { - const GROUND_COLOR = "blue"; - const POWER_COLOR = "red"; - - export interface AllocatorOpts { - boardDef: BoardDefinition, - partDefs: Map, - 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, - } - 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, - 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" && (location).type === "breadboard") { - let 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: A, b: B): A & B { - let res: any = {}; - for (let aKey in a) - res[aKey] = (a)[aKey]; - for (let bKey in b) - res[bKey] = (b)[bKey]; - return res; - } - function merge3(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 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 => {type: "breadboard", row: "+", col: `${n}`}), - ground: mkRange(26, 51).map(n => {type: "breadboard", row: "-", col: `${n}`}), - }, - bottom: { - threeVolt: mkRange(1, 26).map(n => {type: "breadboard", row: "+", col: `${n}`}), - ground: mkRange(1, 26).map(n => {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): 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 = pinDef.target; - } else { - let instIdx = (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 = 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 = {}; - 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 ((location).type === "breadboard") { - return location; - } else if (location === "MOSI" || location === "MISO" || location === "SCK") { - if (!this.opts.boardDef.spiPins) - console.debug("No SPI pin mappings found!"); - let pin = (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 = (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 = 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 = 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 { - 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(); - } -} \ No newline at end of file diff --git a/sim/dalboard.ts b/sim/dalboard.ts index 8ab23f27..adeb064b 100644 --- a/sim/dalboard.ts +++ b/sim/dalboard.ts @@ -5,7 +5,7 @@ namespace pxsim { id: string; // the bus - bus: EventBus; + bus: pxsim.EventBus; // state & update logic for component services ledMatrixState: LedMatrixState; @@ -25,7 +25,7 @@ namespace pxsim { constructor() { super() this.id = "b" + Math_.random(2147483647); - this.bus = new EventBus(runtime); + this.bus = new pxsim.EventBus(runtime); // components this.ledMatrixState = new LedMatrixState(runtime); @@ -97,4 +97,37 @@ namespace pxsim { 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; + } } \ No newline at end of file diff --git a/sim/instructions.ts b/sim/instructions.ts index b4973331..f0fa1220 100644 --- a/sim/instructions.ts +++ b/sim/instructions.ts @@ -1,8 +1,6 @@ /// /// /// -/// -/// //HACK: allows instructions.html to access pxtblocks without requiring simulator.html to import blocks as well if (!(window).pxt) (window).pxt = {}; diff --git a/sim/microbit.ts b/sim/microbit.ts index 792907a5..3d366c2c 100644 --- a/sim/microbit.ts +++ b/sim/microbit.ts @@ -226,7 +226,14 @@ namespace pxsim.visuals { 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", down: "pointerdown", move: "pointermove", diff --git a/sim/simlib.ts b/sim/simlib.ts deleted file mode 100644 index 6c7cc734..00000000 --- a/sim/simlib.ts +++ /dev/null @@ -1,271 +0,0 @@ -/// -/// - -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, - scaleUnit1: number, - el2: SVGAndSize, - 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 = 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 = svg.child(host, "g"); - host.appendChild(a.el); - host.appendChild(b.el); - let over = 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 { - let scaleFn = mkScaleFn(opts.imageUnitDist, opts.targetUnitDist); - let w = scaleFn(opts.width); - let h = scaleFn(opts.height); - let img = 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 { - style: string, - element: SVGElement, - overElement?: SVGElement, - defs: SVGElement[], - init(bus: EventBus, state: T, svgEl: SVGSVGElement, otherParams: Map): 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 = 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 = { - 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 { - el: T, - y: number, - x: number, - w: number, - h: number - }; - export type SVGElAndSize = SVGAndSize; - - export const PIN_DIST = 15; - - export interface BoardView { - getView(): SVGAndSize; - getCoord(pinNm: string): Coord; - getPinDist(): number; - highlightPin(pinNm: string): void; - } -} \ No newline at end of file diff --git a/sim/state/misc.ts b/sim/state/misc.ts index c4194496..db0f622c 100644 --- a/sim/state/misc.ts +++ b/sim/state/misc.ts @@ -77,25 +77,6 @@ namespace pxsim { export interface RuntimeOptions { theme: string; } - - export class EventBus { - private queues: Map> = {}; - - 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(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 { diff --git a/sim/visuals/breadboard.ts b/sim/visuals/breadboard.ts deleted file mode 100644 index f9f88978..00000000 --- a/sim/visuals/breadboard.ts +++ /dev/null @@ -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 = svg.elt("g"); - let colIdxOffset = opts.colStartIdx || 0; - let rowIdxOffset = opts.rowStartIdx || 0; - let copyArr = (arr: T[]): T[] => arr ? arr.slice(0, arr.length) : []; - let removeAll = (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> = {}; - private rowColToLbls: Map> = {}; - - 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 = svg.elt("svg", { - "version": "1.0", - "viewBox": `0 0 ${WIDTH} ${HEIGHT}`, - "class": `sim-bb`, - "width": WIDTH + "px", - "height": HEIGHT + "px", - }); - this.styleEl = svg.child(this.bb, "style", {}); - this.styleEl.textContent += BREADBOARD_CSS; - this.defs = 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 = 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 = {}; - 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 = 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 = 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 = {}; - allGrpNms.forEach((g, i) => grpNmToGroup[g] = groups[i]); - //group pins and add connecting wire - let grpNmToPins: Map = {}; - 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 = 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 { - 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); - } - } -} \ No newline at end of file diff --git a/sim/visuals/genericboard.ts b/sim/visuals/genericboard.ts deleted file mode 100644 index 60601364..00000000 --- a/sim/visuals/genericboard.ts +++ /dev/null @@ -1,305 +0,0 @@ -/// -/// - -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 = {}; - private pinNmToPin: Map = {}; - - 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 = 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 = svg.child(this.element, "style", {}); - this.style.textContent += BOARD_SYTLE; - this.defs = svg.child(this.element, "defs", {}); - this.g = 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 = svg.elt("rect"); - svg.hydrate(rect, {x: x, y: y, width: w, height: h, class: "gray-cover"}); - return rect; - } - - - public getView(): SVGAndSize { - 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"); - } - } - } -} \ No newline at end of file diff --git a/sim/visuals/genericpart.ts b/sim/visuals/genericpart.ts deleted file mode 100644 index e14599b5..00000000 --- a/sim/visuals/genericpart.ts +++ /dev/null @@ -1,35 +0,0 @@ - -namespace pxsim.visuals { - export function mkGenericPartSVG(partVisual: PartVisualDefinition): SVGAndSize { - 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 { - 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 { } - } -} \ No newline at end of file diff --git a/sim/visuals/neopixel.ts b/sim/visuals/neopixel.ts index 86777300..f357ad14 100644 --- a/sim/visuals/neopixel.ts +++ b/sim/visuals/neopixel.ts @@ -2,8 +2,6 @@ /// /// /// -/// -/// //TODO move to utils namespace pxsim.visuals { diff --git a/sim/visuals/wiring.ts b/sim/visuals/wiring.ts deleted file mode 100644 index b527b805..00000000 --- a/sim/visuals/wiring.ts +++ /dev/null @@ -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 { - let g = 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 = 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 { - //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 = svg.mkPath("sim-bb-wire", `M${coordStr(p1)} C${coordStr(c1)} ${coordStr(c2)} ${coordStr(p2)}`); - (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 = 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}`); - (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"}); - (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"}); - (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"}); - (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"}); - (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"}); - (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 = 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 = 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 = (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; - } - } -}