Skip to content

Commit

Permalink
Add back (most of) expression to circuit popup
Browse files Browse the repository at this point in the history
  • Loading branch information
TGCrystal committed Feb 20, 2024
1 parent 969c57e commit 566c295
Show file tree
Hide file tree
Showing 6 changed files with 153 additions and 109 deletions.
1 change: 1 addition & 0 deletions src/app/core/public/api/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface Component extends BaseObject {

x: number;
y: number;
size: Vector;
pos: Vector;
angle: number;

Expand Down
6 changes: 6 additions & 0 deletions src/app/core/public/api/impl/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export function ComponentImpl<T extends CircuitTypes>(
get y(): number {
return (getComponent().props.y ?? 0);
},
set size(val: Vector) {
throw new Error("Unimplemented!");
},
get size(): Vector {
throw new Error("Unimplemented!");
},
set pos(val: Vector) {
internal.beginTransaction();
internal.setPropFor<Schema.Component, "x">(id, "x", val.x).unwrap();
Expand Down
3 changes: 2 additions & 1 deletion src/site/pages/digital/src/containers/App/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {useMainDigitalDesigner} from "site/digital/utils/hooks/useDigitalDesigne
import {DigitalHeader} from "site/digital/containers/DigitalHeader";
import {DigitalItemNav} from "site/digital/containers/DigitalItemNav";
import {DigitalMainDesigner} from "site/digital/containers/DigitalMainDesigner";
import {ExprToCircuitPopup} from "site/digital/containers/ExprToCircuitPopup";

import {KeyboardShortcutsPopup} from "site/digital/containers/KeyboardShortcutsPopup";
import {QuickStartPopup} from "site/digital/containers/QuickStartPopup";
Expand Down Expand Up @@ -83,7 +84,7 @@ export const App = () => {
<KeyboardShortcutsPopup />
<ImageExporterPopup designer={designer} />

{/* <ExprToCircuitPopup /> */}
<ExprToCircuitPopup circuit={designer.circuit} />

<LoginPopup />
</div>
Expand Down
181 changes: 88 additions & 93 deletions src/site/pages/digital/src/containers/ExprToCircuitPopup/generate.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import {OperatorFormat,
OperatorFormatLabel} from "digital/utils/ExpressionParser/Constants/DataStructures";
import {FORMATS} from "digital/utils/ExpressionParser/Constants/Formats";

import {DigitalCircuitInfo} from "digital/utils/DigitalCircuitInfo";
import {V} from "Vector";
import {Circuit} from "core/public";
import {Err, Ok, Result} from "core/utils/Result";
import {DigitalCircuit} from "digital/public";
import {ExpressionToCircuit} from "site/digital/utils/ExpressionParser";
import {OrganizeMinDepth} from "site/digital/utils/ExpressionParser/ComponentOrganizers";
import {OperatorFormat, OperatorFormatLabel} from "site/digital/utils/ExpressionParser/Constants/DataStructures";
import {FORMATS} from "site/digital/utils/ExpressionParser/Constants/Formats";
import {GenerateTokens} from "site/digital/utils/ExpressionParser/GenerateTokens";


export type ExprToCirGeneratorOptions = {
Expand All @@ -28,42 +32,41 @@ const defaultOptions: ExprToCirGeneratorOptions = {
ops: FORMATS[0],
}

// @TODO

// function addLabels(inputMap: Map<string, DigitalComponent>, action: GroupAction,
// circuitComponents: DigitalComponent[], designer: DigitalCircuitDesigner) {
// // Add labels next to inputs
// // TODO: This will have to be redone when there is a better organization algorithm
// for (const [name, component] of inputMap) {
// const newLabel = Create<Label>("Label");
// const pos = component.getPos().sub(newLabel.getSize().x + component.getSize().x, 0);
// action.add(Place(designer, newLabel));
// action.add(SetName(newLabel, name));
// action.add(Translate([newLabel], [pos]));
// circuitComponents.push(newLabel);
// }
// }
function addLabels(inputMap: Map<string, string>, circuit: Circuit) {
// Add labels next to inputs
// TODO: This will have to be redone when there is a better organization algorithm
for (const name of inputMap.keys()) {
const newLabel = circuit.placeComponentAt("Label", V(0, 0));
const component = circuit.getComponents().find((comp) => comp.name === name);
// All handled internally, so newLabel and component shouldn't be undefined
newLabel!.pos = component!.pos.sub(newLabel!.size.x + component!.size.x, 0);
newLabel.name = name;
}
}

// function setClocks(inputMap: Map<string, Clock>, action: GroupAction, options: ExprToCirGeneratorOptions,
// o: DigitalComponent, designer: DigitalCircuitDesigner) {
// let inIndex = 0;
// // Set clock frequencies
// for (const clock of inputMap.values()) {
// action.add(SetProperty(clock, "delay", 500 * (2 ** inIndex)));
// inIndex = Math.min(inIndex + 1, 4);
// }
// // Connect clocks to oscilloscope
// if (options.connectClocksToOscope) {
// inIndex = 0;
// action.add(SetInputPortCount(o, Math.min(inputMap.size + 1, 6)));
// for (const clock of inputMap.values()) {
// action.add(Connect(designer, clock.getOutputPort(0), o.getInputPort(inIndex + 1)));
// inIndex++;
// if (inIndex === 5)
// break;
// }
// }
// }
function setClocks(inputMap: Map<string, string>, options: ExprToCirGeneratorOptions,
circuit: Circuit) {
let inIndex = 0;
// Set clock frequencies
for (const name of inputMap.keys()) {
const clock = circuit.getComponents().find((comp) => comp.name === name);
clock?.setProp("delay", 500 * (2 ** inIndex));
inIndex = Math.min(inIndex + 1, 4);
}
// TODO[model_refactor](trevor): Revisit when oscilloscopes implemented
// New version will query to output oscilloscope
// Connect clocks to oscilloscope
// if (options.connectClocksToOscope) {
// inIndex = 0;
// action.add(SetInputPortCount(o, Math.min(inputMap.size + 1, 6)));
// for (const clock of inputMap.values()) {
// action.add(Connect(designer, clock.getOutputPort(0), o.getInputPort(inIndex + 1)));
// inIndex++;
// if (inIndex === 5)
// break;
// }
// }
}

// function handleIC(action: GroupAction, circuitComponents: DigitalComponent[], expression: string,
// info: DigitalCircuitInfo) {
Expand All @@ -80,60 +83,52 @@ const defaultOptions: ExprToCirGeneratorOptions = {
// action.add(Select(info.selections, ic));
// }

// TODO: Refactor this to a GroupAction factory once there is a better (and Action) algorithm to arrange the circuit
export function Generate(info: DigitalCircuitInfo, expression: string,
userOptions: Partial<ExprToCirGeneratorOptions>) {
// const options = { ...defaultOptions, ...userOptions };
// options.isIC = (options.output !== "Oscilloscope") ? options.isIC : false;
// const ops = (options.format === "custom")
// ? (options.ops)
// : (FORMATS.find((form) => form.icon === options.format) ?? FORMATS[0]);
// const tokenList = GenerateTokens(expression, ops);
// const action = new GroupAction([DeselectAll(info.selections)], "Expression Parser Action");
// const inputMap = new Map<string, DigitalComponent>();
// for (const token of tokenList) {
// if (token.type !== "input" || inputMap.has(token.name))
// continue;
// inputMap.set(token.name, Create<DigitalComponent>(options.input));
// action.add(SetName(inputMap.get(token.name)!, token.name));
export function Generate(circuit: DigitalCircuit, expression: string,
userOptions: Partial<ExprToCirGeneratorOptions>): Result {
const options = { ...defaultOptions, ...userOptions };
options.isIC = (options.output !== "Oscilloscope") ? options.isIC : false;
const ops = (options.format === "custom")
? (options.ops)
: (FORMATS.find((form) => form.icon === options.format) ?? FORMATS[0]);

const tokenList = GenerateTokens(expression, ops);
if (!tokenList.ok) {
return Err(tokenList.error);
}
// Maps input name as key, input component type as value
const inputMap = new Map<string, InputTypes>();
for (const token of tokenList.value) {
if (token.type !== "input" || inputMap.has(token.name))
continue;
inputMap.set(token.name, options.input);
}

const generatedCircuitRes = ExpressionToCircuit(inputMap, expression, options.output, ops);
if (!generatedCircuitRes.ok) {
return Err(generatedCircuitRes.error);
}

const generatedCircuit = generatedCircuitRes.value;
// Get the location of the top left corner of the screen, the 1.5 acts as a modifier
// so that the components are not literally in the uppermost leftmost corner
// const startPos = info.camera.getPos().sub(info.camera.getCenter().scale(info.camera.getZoom()/1.5));
// TODO: Replace with a better (action based) way of organizing a circuit
OrganizeMinDepth(generatedCircuit, circuit.camera.pos);

if (options.label)
addLabels(inputMap, generatedCircuit);

if (options.input === "Clock")
setClocks(inputMap, options, generatedCircuit);

// TODO[model_refactor](trevor): Actually add when there is a way to add
// if (options.isIC) {
// // TODO: Add as IC
// } else {
// // TODO: Add directly
// }

// // Create output LED
// const o = Create<DigitalComponent>(options.output);
// action.add(SetName(o, "Output"));

// // Get the generated circuit
// let circuit = new DigitalObjectSet();
// try {
// circuit = ExpressionToCircuit(inputMap, expression, o, ops);
// } catch (e) {
// action.undo(); // Undo any actions that have been done so far
// throw e;
// }

// action.add(AddGroup(info.designer, circuit));

// // Get the location of the top left corner of the screen, the 1.5 acts as a modifier
// // so that the components are not literally in the uppermost leftmost corner
// // const startPos = info.camera.getPos().sub(info.camera.getCenter().scale(info.camera.getZoom()/1.5));
// // TODO: Replace with a better (action based) way of organizing a circuit
// OrganizeMinDepth(circuit, info.camera.getPos());

// const circuitComponents = circuit.getComponents();

// // Add labels if necessary
// if (options.label)
// addLabels(inputMap, action, circuitComponents, info.designer);

// // Set clock frequencies, also connect to oscilloscope if that option is set
// if (options.input === "Clock")
// setClocks(inputMap as Map<string, Clock>, action, options, o, info.designer);

// if (options.isIC) // If creating as IC
// handleIC(action, circuitComponents, expression, info);
// else // If placing directly
// action.add(SelectGroup(info.selections, circuitComponents));
// TODO[model_refactor](trevor): Iterate over objects and select

// info.history.add(action);
// info.renderer.render();
return Ok(undefined);
}
28 changes: 13 additions & 15 deletions src/site/pages/digital/src/containers/ExprToCircuitPopup/index.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import {useState} from "react";

import {DigitalCircuit} from "digital/public";

import {OperatorFormat, OperatorFormatLabel} from "site/digital/utils/ExpressionParser/Constants/DataStructures";
import {FORMATS} from "site/digital/utils/ExpressionParser/Constants/Formats";

import {DigitalCircuitInfo} from "digital/utils/DigitalCircuitInfo";

import {useSharedDispatch,
useSharedSelector} from "shared/utils/hooks/useShared";

Expand All @@ -25,9 +25,9 @@ import "./index.scss";


type Props = {
mainInfo: DigitalCircuitInfo;
circuit: DigitalCircuit;
}
export const ExprToCircuitPopup = (({ mainInfo }: Props) => {
export const ExprToCircuitPopup = (({ circuit }: Props) => {
const { curPopup } = useSharedSelector(
(state) => ({ curPopup: state.header.curPopup })
);
Expand Down Expand Up @@ -111,17 +111,15 @@ export const ExprToCircuitPopup = (({ mainInfo }: Props) => {
className="exprtocircuit__popup__generate"
disabled={expression===""}
onClick={() => {
try {
Generate(mainInfo, expression, {
input, output, isIC,
connectClocksToOscope: clocksToOscope,
label, format,
ops: customOps,
});
reset();
} catch (e) {
setErrorMessage(e.message);
console.error(e);
const result = Generate(circuit, expression, {
input, output, isIC,
connectClocksToOscope: clocksToOscope,
label, format,
ops: customOps,
});
if (!result.ok) {
setErrorMessage(result.error.errors.map((err) => err.message).join("\n"));
console.error(result.error);
}
}}>
Generate
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// TODO[model_refactor_api](trevor): Get this working for expression to circuit frontend
import {Vector} from "Vector";
import {Circuit} from "core/public";

import {CreateGraph} from "core/utils/CircuitUtils";


const ORGANIZE_SEP_X = 4;
const ORGANIZE_SEP_Y = 3;

function OrganizeCore(circuit: Circuit, start: Vector, depths: string[][]): void {
// Depths is a 2d array where the index of the inner array indicates the depth of all of the nodes inside that array
depths.forEach((nodes, depth) =>
nodes.forEach((id, index) =>
circuit.getComponent(id)!.pos = ( // VVV extra space for labels
start.add(ORGANIZE_SEP_X*(depth - (depths.length - 1)/2) + ORGANIZE_SEP_X/2,
-ORGANIZE_SEP_Y*(index - (nodes.length - 1)/2))
)
)
);
}

/**
* Organizes the components so that components at greater depth are further to the right,
* using the getMaxNodeDepths function of Graph to accomplish this.
*
* @param circuit The circuit to organize.
* @param start The top left coordinate where the organization should start.
*/
export function OrganizeMaxDepth(circuit: Circuit, start: Vector): void {
OrganizeCore(circuit, start, CreateGraph(circuit).getMaxNodeDepths());
}

/**
* Organizes the components so that components at greater depth are further to the right,
* using the getMinNodeDepths function of Graph to accomplish this.
*
* @param circuit The circuit to organize.
* @param start The top left coordinate where the organization should start.
*/
export function OrganizeMinDepth(circuit: Circuit, start: Vector): void {
OrganizeCore(circuit, start, CreateGraph(circuit).getMinNodeDepths());
}

0 comments on commit 566c295

Please sign in to comment.