Merge branch 'master' into motorslider
This commit is contained in:
@ -3,24 +3,6 @@
|
||||
/// <reference path="../built/common-sim.d.ts"/>
|
||||
|
||||
namespace pxsim {
|
||||
export enum CPlayPinName {
|
||||
A0,
|
||||
A1,
|
||||
A2,
|
||||
A3,
|
||||
A4,
|
||||
A5,
|
||||
A6,
|
||||
A7,
|
||||
A8,
|
||||
A9,
|
||||
D4,
|
||||
D5,
|
||||
D6,
|
||||
D7,
|
||||
D8,
|
||||
D13
|
||||
}
|
||||
|
||||
export class EV3Board extends CoreBoard {
|
||||
view: SVGSVGElement;
|
||||
@ -36,7 +18,7 @@ namespace pxsim {
|
||||
brickNode: BrickNode;
|
||||
outputNodes: MotorNode[] = [];
|
||||
|
||||
private motorMap: pxt.Map<number> = {
|
||||
public motorMap: pxt.Map<number> = {
|
||||
0x01: 0,
|
||||
0x02: 1,
|
||||
0x04: 2,
|
||||
@ -115,27 +97,40 @@ namespace pxsim {
|
||||
return this.brickNode;
|
||||
}
|
||||
|
||||
motorUsed(port:number, large: boolean) {
|
||||
for(let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
|
||||
motorUsed(port: number, large: boolean) {
|
||||
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
|
||||
const p = 1 << i;
|
||||
if (port & p) {
|
||||
const motorPort = this.motorMap[p];
|
||||
if (!this.outputNodes[motorPort])
|
||||
this.outputNodes[motorPort] = new MotorNode(motorPort, large);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hasMotor(port: number) {
|
||||
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
|
||||
const p = 1 << i;
|
||||
if (port & p) {
|
||||
const motorPort = this.motorMap[p];
|
||||
const outputNode = this.outputNodes[motorPort];
|
||||
if (outputNode)
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
getMotor(port: number, large?: boolean): MotorNode[] {
|
||||
const r = [];
|
||||
for(let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
|
||||
for (let i = 0; i < DAL.NUM_OUTPUTS; ++i) {
|
||||
const p = 1 << i;
|
||||
if (port & p) {
|
||||
const motorPort = this.motorMap[p];
|
||||
const outputNode = this.outputNodes[motorPort];
|
||||
if (outputNode)
|
||||
r.push(outputNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
@ -144,6 +139,10 @@ namespace pxsim {
|
||||
return this.outputNodes;
|
||||
}
|
||||
|
||||
hasSensor(port: number) {
|
||||
return !!this.inputNodes[port];
|
||||
}
|
||||
|
||||
getSensor(port: number, type: number): SensorNode {
|
||||
if (!this.inputNodes[port]) {
|
||||
switch (type) {
|
||||
@ -168,6 +167,7 @@ namespace pxsim {
|
||||
runtime.postError = (e) => {
|
||||
// TODO
|
||||
runtime.updateDisplay();
|
||||
console.log('runtime error: ' + e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,13 +24,14 @@ namespace pxsim {
|
||||
}
|
||||
|
||||
export class EV3AnalogState {
|
||||
|
||||
constructor() {
|
||||
let data = new Uint8Array(5172)
|
||||
MMapMethods.register("/dev/lms_analog", {
|
||||
data,
|
||||
beforeMemRead: () => {
|
||||
//console.log("analog before read");
|
||||
data[AnalogOff.BatteryTemp] = 21; // TODO simulate this
|
||||
data[AnalogOff.BatteryCurrent] = 100; // TODO simulate this
|
||||
const inputNodes = ev3board().getInputNodes();
|
||||
for (let port = 0; port < DAL.NUM_INPUTS; port++) {
|
||||
const node = inputNodes[port];
|
||||
|
@ -3,12 +3,13 @@
|
||||
namespace pxsim {
|
||||
|
||||
export enum ColorSensorMode {
|
||||
None = -1,
|
||||
Reflected = 0,
|
||||
Ambient = 1,
|
||||
Colors = 2,
|
||||
RefRaw = 3,
|
||||
RgbRaw = 4,
|
||||
ColorCal = 5
|
||||
ColorCal = 5,
|
||||
}
|
||||
|
||||
export enum ThresholdState {
|
||||
@ -24,6 +25,7 @@ namespace pxsim {
|
||||
|
||||
constructor(port: number) {
|
||||
super(port);
|
||||
this.mode = -1;
|
||||
}
|
||||
|
||||
getDeviceType() {
|
||||
|
@ -81,7 +81,6 @@ namespace pxsim.control {
|
||||
export function dmesg(s: string) {
|
||||
console.log("DMESG: " + s)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace pxsim.output {
|
||||
|
@ -1,10 +1,17 @@
|
||||
|
||||
|
||||
import lf = pxsim.localization.lf;
|
||||
|
||||
namespace pxsim.motors {
|
||||
|
||||
export function __motorUsed(port: number, large: boolean) {
|
||||
//console.log("MOTOR INIT " + port);
|
||||
ev3board().motorUsed(port, large);
|
||||
runtime.queueDisplayUpdate();
|
||||
if (!ev3board().hasMotor(port)) {
|
||||
ev3board().motorUsed(port, large);
|
||||
runtime.queueDisplayUpdate();
|
||||
} else {
|
||||
U.userError(`${lf("Multiple motors are connected to Port")} ${String.fromCharCode('A'.charCodeAt(0) + ev3board().motorMap[port])}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +19,11 @@ namespace pxsim.sensors {
|
||||
|
||||
export function __sensorUsed(port: number, type: number) {
|
||||
//console.log("SENSOR INIT " + port + ", type: " + type);
|
||||
const sensor = ev3board().getSensor(port, type);
|
||||
runtime.queueDisplayUpdate();
|
||||
if (!ev3board().hasSensor(port)) {
|
||||
const sensor = ev3board().getSensor(port, type);
|
||||
runtime.queueDisplayUpdate();
|
||||
} else {
|
||||
U.userError(`${lf("Multiple sensors are connected to Port")} ${port + 1}`);
|
||||
}
|
||||
}
|
||||
}
|
@ -15,29 +15,51 @@ namespace pxsim {
|
||||
private speedCmdValues: number[];
|
||||
private speedCmdTacho: number;
|
||||
private speedCmdTime: number;
|
||||
private _synchedMotor: MotorNode; // non-null if synchronized
|
||||
|
||||
constructor(port: number, large: boolean) {
|
||||
super(port);
|
||||
this.setLarge(large);
|
||||
}
|
||||
|
||||
isReady() {
|
||||
return !this.speedCmd;
|
||||
}
|
||||
|
||||
getSpeed() {
|
||||
return this.speed * (this.polarity == 0 ? -1 : 1);
|
||||
return this.speed * (this.polarity == 0 ? -1 : 1);
|
||||
}
|
||||
|
||||
getAngle() {
|
||||
return this.angle;
|
||||
}
|
||||
|
||||
// returns the slave motor if any
|
||||
getSynchedMotor() {
|
||||
return this._synchedMotor;
|
||||
}
|
||||
|
||||
setSpeedCmd(cmd: DAL, values: number[]) {
|
||||
if (this.speedCmd != cmd ||
|
||||
JSON.stringify(this.speedCmdValues) != JSON.stringify(values))
|
||||
this.setChangedState();
|
||||
// new command TODO: values
|
||||
this.speedCmd = cmd;
|
||||
this.speedCmdValues = values;
|
||||
this.speedCmdTacho = this.angle;
|
||||
this.speedCmdTime = pxsim.U.now();
|
||||
delete this._synchedMotor;
|
||||
}
|
||||
|
||||
setSyncCmd(motor: MotorNode, cmd: DAL, values: number[]) {
|
||||
this.setSpeedCmd(cmd, values);
|
||||
this._synchedMotor = motor;
|
||||
}
|
||||
|
||||
clearSpeedCmd() {
|
||||
delete this.speedCmd;
|
||||
delete this.speedCmdValues;
|
||||
delete this._synchedMotor;
|
||||
}
|
||||
|
||||
setLarge(large: boolean) {
|
||||
@ -67,6 +89,7 @@ namespace pxsim {
|
||||
|
||||
stop() {
|
||||
this.started = false;
|
||||
this.clearSpeedCmd();
|
||||
}
|
||||
|
||||
start() {
|
||||
@ -86,7 +109,7 @@ namespace pxsim {
|
||||
//console.log(`motor: ${elapsed}ms - ${this.speed}% - ${this.angle}> - ${this.tacho}|`)
|
||||
const interval = Math.min(20, elapsed);
|
||||
let t = 0;
|
||||
while(t < elapsed) {
|
||||
while (t < elapsed) {
|
||||
let dt = interval;
|
||||
if (t + dt > elapsed) dt = elapsed - t;
|
||||
this.updateStateStep(dt);
|
||||
@ -106,14 +129,14 @@ namespace pxsim {
|
||||
case DAL.opOutputTimeSpeed:
|
||||
case DAL.opOutputTimePower:
|
||||
case DAL.opOutputStepPower:
|
||||
case DAL.opOutputStepSpeed:
|
||||
case DAL.opOutputStepSpeed: {
|
||||
// ramp up, run, ramp down, <brake> using time
|
||||
const speed = this.speedCmdValues[0];
|
||||
const step1 = this.speedCmdValues[1];
|
||||
const step2 = this.speedCmdValues[2];
|
||||
const step3 = this.speedCmdValues[3];
|
||||
const brake = this.speedCmdValues[4];
|
||||
const dstep = (this.speedCmd == DAL.opOutputTimePower || this.speedCmd == DAL.opOutputTimeSpeed)
|
||||
const dstep = (this.speedCmd == DAL.opOutputTimePower || this.speedCmd == DAL.opOutputTimeSpeed)
|
||||
? pxsim.U.now() - this.speedCmdTime
|
||||
: this.tacho - this.speedCmdTacho;
|
||||
if (dstep < step1) // rampup
|
||||
@ -126,9 +149,49 @@ namespace pxsim {
|
||||
if (brake) this.speed = 0;
|
||||
this.clearSpeedCmd();
|
||||
}
|
||||
this.speed = Math.round(this.speed); // integer only
|
||||
break;
|
||||
}
|
||||
case DAL.opOutputStepSync:
|
||||
case DAL.opOutputTimeSync: {
|
||||
const otherMotor = this._synchedMotor;
|
||||
if (otherMotor.port < this.port) // handled in other motor code
|
||||
break;
|
||||
|
||||
const speed = this.speedCmdValues[0];
|
||||
const turnRatio = this.speedCmdValues[1];
|
||||
const stepsOrTime = this.speedCmdValues[2];
|
||||
const brake = this.speedCmdValues[3];
|
||||
const dstep = this.speedCmd == DAL.opOutputTimeSync
|
||||
? pxsim.U.now() - this.speedCmdTime
|
||||
: this.tacho - this.speedCmdTacho;
|
||||
// 0 is special case, run infinite
|
||||
if (!stepsOrTime || dstep < stepsOrTime)
|
||||
this.speed = speed;
|
||||
else {
|
||||
if (brake) this.speed = 0;
|
||||
this.clearSpeedCmd();
|
||||
}
|
||||
|
||||
// turn ratio is a bit weird to interpret
|
||||
// see https://communities.theiet.org/blogs/698/1706
|
||||
if (turnRatio < 0) {
|
||||
otherMotor.speed = speed;
|
||||
this.speed *= (100 + turnRatio) / 100;
|
||||
} else {
|
||||
otherMotor.speed = this.speed * (100 - turnRatio) / 100;
|
||||
}
|
||||
|
||||
// clamp
|
||||
this.speed = Math.max(-100, Math.min(100, this.speed >> 0));
|
||||
otherMotor.speed = Math.max(-100, Math.min(100, otherMotor.speed >> 0));;
|
||||
|
||||
// stop other motor if needed
|
||||
if (!this._synchedMotor)
|
||||
otherMotor.clearSpeedCmd();
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.speed = Math.round(this.speed); // integer only
|
||||
|
||||
// compute delta angle
|
||||
const rotations = this.getSpeed() / 100 * this.rotationsPerMilliSecond * elapsed;
|
||||
@ -143,8 +206,15 @@ namespace pxsim {
|
||||
// let it coast to speed 0
|
||||
if (this.speed && !(this.started || this.speedCmd)) {
|
||||
// decay speed 5% per tick
|
||||
this.speed = Math.round(Math.max(0, Math.abs(this.speed) - 10) * Math.sign(this.speed));
|
||||
this.speed = Math.round(Math.max(0, Math.abs(this.speed) - 10) * sign(this.speed));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace pxsim {
|
||||
// A re-implementation of Math.sign (since IE11 doesn't support it)
|
||||
export function sign(num: number) {
|
||||
return num ? num < 0 ? -1 : 1 : 0;
|
||||
}
|
||||
}
|
@ -12,10 +12,24 @@ namespace pxsim {
|
||||
data[i] = 0
|
||||
},
|
||||
read: buf => {
|
||||
let v = "vSIM"
|
||||
for (let i = 0; i < buf.data.length; ++i)
|
||||
buf.data[i] = v.charCodeAt(i) || 0
|
||||
console.log("pwm read");
|
||||
// console.log("pwm read");
|
||||
if (buf.data.length == 0) return 2;
|
||||
const cmd = buf.data[0];
|
||||
switch (cmd) {
|
||||
case DAL.opOutputTest:
|
||||
const port = buf.data[1];
|
||||
let r = 0;
|
||||
ev3board().getMotor(port)
|
||||
.filter(motor => !motor.isReady())
|
||||
.forEach(motor => r |= (1 << motor.port));
|
||||
pxsim.BufferMethods.setNumber(buf, BufferMethods.NumberFormat.UInt8LE, 2, r);
|
||||
break;
|
||||
default:
|
||||
let v = "vSIM"
|
||||
for (let i = 0; i < buf.data.length; ++i)
|
||||
buf.data[i] = v.charCodeAt(i) || 0
|
||||
break;
|
||||
}
|
||||
return buf.data.length
|
||||
},
|
||||
write: buf => {
|
||||
@ -56,6 +70,25 @@ namespace pxsim {
|
||||
motors.forEach(motor => motor.setSpeedCmd(cmd, [speed, step1, step2, step3, brake]));
|
||||
return 2;
|
||||
}
|
||||
case DAL.opOutputStepSync:
|
||||
case DAL.opOutputTimeSync: {
|
||||
const port = buf.data[1];
|
||||
const speed = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 2); // signed byte
|
||||
// note that b[3] is padding
|
||||
const turnRatio = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int16LE, 4);
|
||||
// b[6], b[7] is padding
|
||||
const stepsOrTime = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int32LE, 8);
|
||||
const brake = pxsim.BufferMethods.getNumber(buf, BufferMethods.NumberFormat.Int8LE, 12);
|
||||
|
||||
const motors = ev3board().getMotor(port);
|
||||
for (const motor of motors) {
|
||||
const otherMotor = motors.filter(m => m.port != motor.port)[0];
|
||||
motor.setSyncCmd(
|
||||
otherMotor,
|
||||
cmd, [speed, turnRatio, stepsOrTime, brake]);
|
||||
}
|
||||
return 2;
|
||||
}
|
||||
case DAL.opOutputStop: {
|
||||
// stop
|
||||
const port = buf.data[1];
|
||||
|
@ -5,6 +5,7 @@ namespace pxsim {
|
||||
|
||||
protected mode: number;
|
||||
protected valueChanged: boolean;
|
||||
protected modeChanged: boolean;
|
||||
|
||||
constructor(port: number) {
|
||||
super(port);
|
||||
@ -24,6 +25,8 @@ namespace pxsim {
|
||||
|
||||
setMode(mode: number) {
|
||||
this.mode = mode;
|
||||
this.changed = true;
|
||||
this.modeChanged = true;
|
||||
}
|
||||
|
||||
getMode() {
|
||||
@ -44,6 +47,12 @@ namespace pxsim {
|
||||
return res;
|
||||
}
|
||||
|
||||
modeChange() {
|
||||
const res = this.modeChanged;
|
||||
this.modeChanged = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
setChangedState() {
|
||||
this.changed = true;
|
||||
this.valueChanged = false;
|
||||
|
@ -10,8 +10,6 @@ namespace pxsim.music {
|
||||
}
|
||||
|
||||
namespace pxsim.SoundMethods {
|
||||
let numSoundsPlaying = 0;
|
||||
const soundsLimit = 1;
|
||||
let audio: HTMLAudioElement;
|
||||
|
||||
export function buffer(buf: RefBuffer) {
|
||||
@ -27,18 +25,15 @@ namespace pxsim.SoundMethods {
|
||||
}
|
||||
|
||||
export function play(buf: RefBuffer, volume: number) {
|
||||
if (!buf || numSoundsPlaying >= soundsLimit) {
|
||||
if (!buf) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return new Promise<void>(resolve => {
|
||||
let url = "data:audio/wav;base64," + btoa(uint8ArrayToString(buf.data))
|
||||
audio = new Audio(url)
|
||||
audio.onended = () => {
|
||||
resolve();
|
||||
numSoundsPlaying--;
|
||||
}
|
||||
numSoundsPlaying++;
|
||||
audio.play()
|
||||
audio.onended = () => resolve();
|
||||
audio.onpause = () => resolve();
|
||||
audio.play();
|
||||
})
|
||||
}
|
||||
|
||||
@ -46,8 +41,8 @@ namespace pxsim.SoundMethods {
|
||||
return new Promise<void>(resolve => {
|
||||
if (audio) {
|
||||
audio.pause();
|
||||
numSoundsPlaying--;
|
||||
}
|
||||
resolve();
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,15 @@ namespace pxsim.visuals {
|
||||
fill:#5A5A5A;
|
||||
}
|
||||
|
||||
.no-drag, .sim-text, .sim-text-pin {
|
||||
user-drag: none;
|
||||
user-select: none;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
|
||||
/* Color Grid */
|
||||
.sim-color-grid-circle:hover {
|
||||
stroke-width: 0.4;
|
||||
@ -192,8 +201,8 @@ namespace pxsim.visuals {
|
||||
this.layoutView.updateTheme(theme);
|
||||
}
|
||||
|
||||
private getControlForNode(id: NodeType, port: number) {
|
||||
if (this.cachedControlNodes[id] && this.cachedControlNodes[id][port]) {
|
||||
private getControlForNode(id: NodeType, port: number, useCache = true) {
|
||||
if (useCache && this.cachedControlNodes[id] && this.cachedControlNodes[id][port]) {
|
||||
return this.cachedControlNodes[id][port];
|
||||
}
|
||||
|
||||
@ -352,6 +361,28 @@ namespace pxsim.visuals {
|
||||
cancelAnimationFrame(animationId);
|
||||
})
|
||||
}
|
||||
// Save previous inputs for the next cycle
|
||||
EV3View.previousSelectedInputs = ev3board().getInputNodes().map((node, index) => (this.getDisplayViewForNode(node.id, index).getSelected()) ? node.id : -1)
|
||||
EV3View.previousSeletedOutputs = ev3board().getMotors().map((node, index) => (this.getDisplayViewForNode(node.id, index).getSelected()) ? node.id : -1);
|
||||
}
|
||||
|
||||
private static previousSelectedInputs: number[];
|
||||
private static previousSeletedOutputs: number[];
|
||||
|
||||
private static isPreviousInputSelected(index: number, id: number) {
|
||||
if (EV3View.previousSelectedInputs && EV3View.previousSelectedInputs[index] == id) {
|
||||
EV3View.previousSelectedInputs[index] = undefined;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static isPreviousOutputSelected(index: number, id: number) {
|
||||
if (EV3View.previousSeletedOutputs && EV3View.previousSeletedOutputs[index] == id) {
|
||||
EV3View.previousSeletedOutputs[index] = undefined;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private begin() {
|
||||
@ -394,7 +425,9 @@ namespace pxsim.visuals {
|
||||
const view = this.getDisplayViewForNode(node.id, index);
|
||||
if (!node.didChange() && !view.didChange()) return;
|
||||
if (view) {
|
||||
const control = view.getSelected() ? this.getControlForNode(node.id, index) : undefined;
|
||||
const isSelected = EV3View.isPreviousInputSelected(index, node.id) || view.getSelected();
|
||||
if (isSelected && !view.getSelected()) view.setSelected(true);
|
||||
const control = isSelected ? this.getControlForNode(node.id, index, !node.modeChange()) : undefined;
|
||||
const closeIcon = control ? this.getCloseIconView() : undefined;
|
||||
this.layoutView.setInput(index, view, control, closeIcon);
|
||||
view.updateState();
|
||||
@ -413,7 +446,9 @@ namespace pxsim.visuals {
|
||||
const view = this.getDisplayViewForNode(node.id, index);
|
||||
if (!node.didChange() && !view.didChange()) return;
|
||||
if (view) {
|
||||
const control = view.getSelected() ? this.getControlForNode(node.id, index) : undefined;
|
||||
const isSelected = EV3View.isPreviousOutputSelected(index, node.id) || view.getSelected();
|
||||
if (isSelected && !view.getSelected()) view.setSelected(true);
|
||||
const control = isSelected ? this.getControlForNode(node.id, index) : undefined;
|
||||
const closeIcon = control ? this.getCloseIconView() : undefined;
|
||||
this.layoutView.setOutput(index, view, control, closeIcon);
|
||||
view.updateState();
|
||||
|
@ -7,7 +7,7 @@ namespace pxsim.visuals {
|
||||
|
||||
getInnerView() {
|
||||
this.group = svg.elt("g") as SVGGElement;
|
||||
this.group.setAttribute("transform", `translate(1.02, 1.5) scale(0.8)`)
|
||||
this.group.setAttribute("transform", `translate(2, 2.5) scale(0.6)`)
|
||||
|
||||
const colorIds = ['red', 'yellow', 'blue', 'green', undefined, 'grey'];
|
||||
const colors = ['#f12a21', '#ffd01b', '#006db3', '#00934b', undefined, '#6c2d00'];
|
||||
|
@ -173,15 +173,15 @@ namespace pxsim.visuals {
|
||||
}
|
||||
|
||||
// Inject all ports
|
||||
this.setInput(0, new PortView(0, 'A'));
|
||||
this.setInput(1, new PortView(1, 'B'));
|
||||
this.setInput(2, new PortView(2, 'C'));
|
||||
this.setInput(3, new PortView(3, 'D'));
|
||||
this.setInput(0, new PortView(0, '1'));
|
||||
this.setInput(1, new PortView(1, '2'));
|
||||
this.setInput(2, new PortView(2, '3'));
|
||||
this.setInput(3, new PortView(3, '4'));
|
||||
|
||||
this.setOutput(0, new PortView(0, '1'));
|
||||
this.setOutput(1, new PortView(1, '2'));
|
||||
this.setOutput(2, new PortView(2, '3'));
|
||||
this.setOutput(3, new PortView(3, '4'));
|
||||
this.setOutput(0, new PortView(0, 'A'));
|
||||
this.setOutput(1, new PortView(1, 'B'));
|
||||
this.setOutput(2, new PortView(2, 'C'));
|
||||
this.setOutput(3, new PortView(3, 'D'));
|
||||
|
||||
return this.contentGroup;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ namespace pxsim.visuals {
|
||||
}
|
||||
|
||||
public updateState() {
|
||||
super.updateState();
|
||||
// TODO: show different color modes
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,51 @@
|
||||
/// <reference path="./moduleView.ts" />
|
||||
/// <reference path="./motorView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export class LargeMotorView extends ModuleView implements LayoutElement {
|
||||
|
||||
private static ROTATING_ECLIPSE_ID = "hole";
|
||||
export class LargeMotorView extends MotorView implements LayoutElement {
|
||||
|
||||
constructor(port: number) {
|
||||
super(LARGE_MOTOR_SVG, "large-motor", NodeType.LargeMotor, port);
|
||||
super(LARGE_MOTOR_SVG, "large-motor", NodeType.LargeMotor, port, "hole");
|
||||
}
|
||||
|
||||
private syncedMotor: MotorNode;
|
||||
private syncedLabelG: SVGGElement;
|
||||
|
||||
updateState() {
|
||||
super.updateState();
|
||||
const motorState = ev3board().getMotors()[this.port];
|
||||
if (!motorState) return;
|
||||
const speed = motorState.getSpeed();
|
||||
|
||||
if (!speed) return;
|
||||
this.setMotorAngle(motorState.getAngle());
|
||||
const syncedMotor = motorState.getSynchedMotor();
|
||||
if ((syncedMotor || this.syncedMotor) && syncedMotor != this.syncedMotor) {
|
||||
this.syncedMotor = syncedMotor;
|
||||
if (this.syncedMotor) {
|
||||
this.showSyncedLabel(motorState, syncedMotor);
|
||||
} else if (this.syncedLabelG) {
|
||||
this.syncedLabelG.parentNode.removeChild(this.syncedLabelG);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setMotorAngle(angle: number) {
|
||||
const holeEl = this.content.getElementById(this.normalizeId(LargeMotorView.ROTATING_ECLIPSE_ID))
|
||||
private showSyncedLabel(motorNode: MotorNode, syncedMotor: MotorNode) {
|
||||
const a = String.fromCharCode('A'.charCodeAt(0) + motorNode.port);
|
||||
const b = String.fromCharCode('A'.charCodeAt(0) + syncedMotor.port);
|
||||
|
||||
this.syncedLabelG = pxsim.svg.child(this.element, 'g', {'transform': 'scale(0.5)'}) as SVGGElement;
|
||||
pxsim.svg.child(this.syncedLabelG, 'rect', {'rx': 15, 'ry': 15, 'x': 0, 'y': 0, 'width': 84, 'height': 34, 'fill': '#A8A9A8'});
|
||||
pxsim.svg.child(this.syncedLabelG, 'circle', {'cx': 17, 'cy': 17, 'r': 15, 'fill': 'white'});
|
||||
const leftLabel = pxsim.svg.child(this.syncedLabelG, 'text', {'transform': 'translate(11, 22)', 'class': 'no-drag', 'style': 'isolation: isolate;font-size: 16px;fill: #A8A9A8;font-family: ArialMT, Arial'});
|
||||
leftLabel.textContent = a;
|
||||
|
||||
pxsim.svg.child(this.syncedLabelG, 'rect', {'rx': 0, 'ry': 0, 'x': 37, 'y': 12, 'width': 10, 'height': 3, 'fill': '#ffffff'});
|
||||
pxsim.svg.child(this.syncedLabelG, 'rect', {'rx': 0, 'ry': 0, 'x': 37, 'y': 18, 'width': 10, 'height': 3, 'fill': '#ffffff'});
|
||||
|
||||
pxsim.svg.child(this.syncedLabelG, 'circle', {'cx': 67, 'cy': 17, 'r': 15, 'fill': 'white'});
|
||||
const rightLabel = pxsim.svg.child(this.syncedLabelG, 'text', {'transform': 'translate(61, 22)', 'class': 'no-drag', 'style': 'isolation: isolate;font-size: 16px;fill: #A8A9A8;font-family: ArialMT, Arial'});
|
||||
rightLabel.textContent = b;
|
||||
}
|
||||
|
||||
protected renderMotorAngle(holeEl: Element, angle: number) {
|
||||
const width = 125.92;
|
||||
const height = 37.9;
|
||||
const transform = `rotate(${angle} ${width / 2} ${height / 2})`;
|
||||
|
@ -2,34 +2,17 @@
|
||||
|
||||
namespace pxsim.visuals {
|
||||
|
||||
export const MOTOR_ROTATION_FPS = 32;
|
||||
|
||||
export class MediumMotorView extends ModuleView implements LayoutElement {
|
||||
|
||||
private static ROTATING_ECLIPSE_ID = "medmotor_Hole";
|
||||
|
||||
private hasPreviousAngle: boolean;
|
||||
private previousAngle: number;
|
||||
export class MediumMotorView extends MotorView implements LayoutElement {
|
||||
|
||||
constructor(port: number) {
|
||||
super(MEDIUM_MOTOR_SVG, "medium-motor", NodeType.MediumMotor, port);
|
||||
super(MEDIUM_MOTOR_SVG, "medium-motor", NodeType.MediumMotor, port, "medmotor_Hole");
|
||||
}
|
||||
|
||||
public getPaddingRatio() {
|
||||
return 1 / 5;
|
||||
}
|
||||
|
||||
updateState() {
|
||||
const motorState = ev3board().getMotors()[this.port];
|
||||
if (!motorState) return;
|
||||
const speed = motorState.getSpeed();
|
||||
|
||||
if (!speed) return;
|
||||
this.setMotorAngle(motorState.getAngle());
|
||||
}
|
||||
|
||||
private setMotorAngle(angle: number) {
|
||||
const holeEl = this.content.getElementById(this.normalizeId(MediumMotorView.ROTATING_ECLIPSE_ID))
|
||||
protected renderMotorAngle(holeEl: Element, angle: number) {
|
||||
const width = 44.45;
|
||||
const height = 44.45;
|
||||
const transform = `translate(2 1.84) rotate(${angle} ${width / 2} ${height / 2})`;
|
||||
|
@ -4,7 +4,8 @@ namespace pxsim.visuals {
|
||||
protected content: SVGSVGElement;
|
||||
|
||||
protected controlShown: boolean;
|
||||
protected selected: boolean;
|
||||
|
||||
protected opacity: number;
|
||||
|
||||
constructor(protected xml: string, protected prefix: string, protected id: NodeType, protected port: NodeType) {
|
||||
super();
|
||||
@ -106,19 +107,24 @@ namespace pxsim.visuals {
|
||||
this.updateOpacity();
|
||||
}
|
||||
|
||||
public updateState() {
|
||||
this.updateOpacity();
|
||||
}
|
||||
|
||||
protected updateOpacity() {
|
||||
if (this.rendered) {
|
||||
const opacity = this.selected ? "0.2" : "1";
|
||||
if (this.hasClick()) {
|
||||
this.setOpacity(opacity);
|
||||
const opacity = this.selected ? 0.2 : 1;
|
||||
if (this.hasClick() && this.opacity != opacity) {
|
||||
this.opacity = opacity;
|
||||
this.setOpacity(this.opacity);
|
||||
if (this.selected) this.content.style.cursor = "";
|
||||
else this.content.style.cursor = "pointer";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected setOpacity(opacity: string) {
|
||||
this.element.setAttribute("opacity", opacity);
|
||||
protected setOpacity(opacity: number) {
|
||||
this.element.setAttribute("opacity", `${opacity}`);
|
||||
}
|
||||
}
|
||||
}
|
33
sim/visuals/nodes/motorView.ts
Normal file
33
sim/visuals/nodes/motorView.ts
Normal file
@ -0,0 +1,33 @@
|
||||
/// <reference path="./moduleView.ts" />
|
||||
|
||||
namespace pxsim.visuals {
|
||||
export abstract class MotorView extends ModuleView implements LayoutElement {
|
||||
|
||||
constructor(xml: string, prefix: string, id: NodeType, port: NodeType,
|
||||
protected rotating_hole_id: string) {
|
||||
super(xml, prefix, id, port);
|
||||
}
|
||||
|
||||
updateState() {
|
||||
super.updateState();
|
||||
const motorState = ev3board().getMotors()[this.port];
|
||||
if (!motorState) return;
|
||||
const speed = motorState.getSpeed();
|
||||
|
||||
if (!speed) return;
|
||||
this.setMotorAngle(motorState.getAngle());
|
||||
}
|
||||
|
||||
private setMotorAngle(angle: number) {
|
||||
const holeEl = this.content.getElementById(this.normalizeId(this.rotating_hole_id))
|
||||
this.renderMotorAngle(holeEl, angle);
|
||||
}
|
||||
|
||||
protected abstract renderMotorAngle(holeEl: Element, angle: number): void;
|
||||
|
||||
|
||||
getWiringRatio() {
|
||||
return 0.37;
|
||||
}
|
||||
}
|
||||
}
|
@ -219,7 +219,6 @@ namespace pxsim.visuals {
|
||||
this.changed = false;
|
||||
return res;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export abstract class SimView<T extends BaseNode> extends View implements LayoutElement {
|
||||
|
Reference in New Issue
Block a user