Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add back (most of) expression to circuit popup #1292

Open
wants to merge 1 commit into
base: model_refactor_api
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions src/app/core/public/api/Component.ts
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
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
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
@@ -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
@@ -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
@@ -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());
}