moving wiring to pxt

This commit is contained in:
Peli de Halleux 2016-09-09 15:01:29 -07:00
parent da30afb121
commit 3858b0a0a0
11 changed files with 43 additions and 2432 deletions

View File

@ -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();
}
}

View File

@ -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;
}
} }

View File

@ -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 = {};

View File

@ -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",

View File

@ -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;
}
}

View File

@ -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 {

View File

@ -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);
}
}
}

View File

@ -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");
}
}
}
}

View File

@ -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 { }
}
}

View File

@ -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 {

View File

@ -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;
}
}
}