Skip to content

Commit

Permalink
Merge pull request jupytercad#24 from trungleduc/mesh-selector
Browse files Browse the repository at this point in the history
Mesh selection
  • Loading branch information
martinRenou authored Oct 25, 2022
2 parents 9c5d605 + 9e485e0 commit 4d5c9f5
Show file tree
Hide file tree
Showing 7 changed files with 325 additions and 210 deletions.
179 changes: 96 additions & 83 deletions src/mainview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import { JupyterCadModel } from './model';
import {
IDict,
IDisplayShape,
IJupyterCadClientState,
IMainMessage,
IWorkerMessage,
MainAction,
Position,
WorkerAction
} from './types';

Expand All @@ -32,7 +32,8 @@ interface IProps {
}

interface IStates {
id: string;
id: string; // ID of the component, it is used to identify which component
//is the source of awareness updates.
loading: boolean;
lightTheme: boolean;
}
Expand All @@ -45,10 +46,7 @@ export class MainView extends React.Component<IProps, IStates> {
this._geometry.setDrawRange(0, 3 * 10000);
this._refLength = 0;
this._sceneAxe = [];
// this.shapeGroup = new THREE.Group();
// this.sceneScaled = false;
// this.computedScene = {};
// this.progressData = { time_step: -1, data: {} };

this._resizeTimeout = null;

const lightTheme =
Expand Down Expand Up @@ -76,15 +74,13 @@ export class MainView extends React.Component<IProps, IStates> {
{ action: WorkerAction.REGISTER, payload: { id: this.state.id } },
this._messageChannel.port2
);
this._model.themeChanged.connect((_, arg) => {
this.handleThemeChange();
});
this._model.cameraChanged.connect(this._onCameraChanged);
this._model.themeChanged.connect(this._handleThemeChange);
this._model.clientStateChanged.connect(this._onClientSharedStateChanged);
});
}

componentDidMount(): void {
window.addEventListener('resize', this.handleWindowResize);
window.addEventListener('resize', this._handleWindowResize);
this.generateScene();
}

Expand All @@ -94,29 +90,18 @@ export class MainView extends React.Component<IProps, IStates> {

componentWillUnmount(): void {
window.cancelAnimationFrame(this._requestID);
window.removeEventListener('resize', this.handleWindowResize);
window.removeEventListener('resize', this._handleWindowResize);
this._controls.dispose();
this.postMessage({
action: WorkerAction.CLOSE_FILE,
payload: {
fileName: this._context.path
}
});
this._model.themeChanged.disconnect(this._handleThemeChange);
this._model.clientStateChanged.disconnect(this._onClientSharedStateChanged);
}

handleThemeChange = (): void => {
const lightTheme =
document.body.getAttribute('data-jp-theme-light') === 'true';
this.setState(old => ({ ...old, lightTheme }));
};

handleWindowResize = () => {
clearTimeout(this._resizeTimeout);
this._resizeTimeout = setTimeout(() => {
this.forceUpdate();
}, 500);
};

addSceneAxe = (dir: THREE.Vector3, color: number): void => {
const origin = new THREE.Vector3(0, 0, 0);
const length = 20;
Expand Down Expand Up @@ -255,13 +240,16 @@ export class MainView extends React.Component<IProps, IStates> {
canvas.addEventListener(
evtName as any,
(event: MouseEvent | WheelEvent) => {
this._model.syncCamera({
offsetX: event.offsetX,
offsetY: event.offsetY,
x: this._camera.position.x,
y: this._camera.position.y,
z: this._camera.position.z
});
this._model.syncCamera(
{
offsetX: event.offsetX,
offsetY: event.offsetY,
x: this._camera.position.x,
y: this._camera.position.y,
z: this._camera.position.z
},
this.state.id
);
}
);
});
Expand Down Expand Up @@ -395,7 +383,17 @@ export class MainView extends React.Component<IProps, IStates> {
}

private _onClick(e: MouseEvent) {
this._selectedMesh = this._pick();
const selectedMesh = this._pick();
if (selectedMesh) {
if (selectedMesh === this._selectedMesh) {
this._selectedMesh = null;
} else {
this._selectedMesh = selectedMesh;
}
if (this._selectedMesh) {
this._model.syncSelectedObject(this._selectedMesh.name, this.state.id);
}
}
}

private shapeToMesh = (payload: IDisplayShape['payload']) => {
Expand Down Expand Up @@ -531,61 +529,76 @@ export class MainView extends React.Component<IProps, IStates> {
}
};

private _onCameraChanged = (
private _onClientSharedStateChanged = (
sender: JupyterCadModel,
clients: Map<number, any>
clients: Map<number, IJupyterCadClientState>
): void => {
clients.forEach((client, key) => {
if (this._context.model.getClientId() !== key) {
const id = key.toString();
const mouse = client.mouse as Position;
if (mouse && this._cameraClients[id]) {
if (mouse.offsetX > 0) {
this._cameraClients[id]!.style.left = mouse.offsetX + 'px';
}
if (mouse.offsetY > 0) {
this._cameraClients[id]!.style.top = mouse.offsetY + 'px';
}
if (!this._mouseDown) {
this._camera.position.set(mouse.x, mouse.y, mouse.z);
const clientId = this._context.model.getClientId();
// TODO Handle state changes from another user in follow mode.
const targetId: number | null = null;
if (targetId) {
const remoteState = clients.get(targetId)!;
const mouse = remoteState?.mouse.value;
if (mouse && this._cameraClients[targetId]) {
if (mouse.offsetX > 0) {
this._cameraClients[targetId]!.style.left = mouse.offsetX + 'px';
}
if (mouse.offsetY > 0) {
this._cameraClients[targetId]!.style.top = mouse.offsetY + 'px';
}
if (!this._mouseDown) {
this._camera.position.set(mouse.x, mouse.y, mouse.z);
}
} else if (mouse && !this._cameraClients[targetId]) {
const el = document.createElement('div');
el.className = 'jpcad-camera-client';
el.style.left = mouse.offsetX + 'px';
el.style.top = mouse.offsetY + 'px';
el.style.backgroundColor = remoteState.user.color;
el.innerText = remoteState.user.name;
this._cameraClients[targetId] = el;
this._cameraRef.current?.appendChild(el);
} else if (!mouse && this._cameraClients[targetId]) {
this._cameraRef.current?.removeChild(this._cameraClients[targetId]!);
this._cameraClients[targetId] = undefined;
}
} else {
// Sync local state updated by other components

const localState = clients.get(clientId);
if (localState) {
if (
localState.selected?.emitter &&
localState.selected?.emitter !== this.state.id
) {
if (this._selectedMesh?.name !== localState.selected.value) {
this._meshGroup?.children.forEach(obj => {
if (obj.name === localState.selected.value) {
this._selectedMesh = obj as THREE.Mesh<
THREE.BufferGeometry,
THREE.MeshBasicMaterial
>;
}
});
}
} else if (mouse && !this._cameraClients[id]) {
const el = document.createElement('div');
el.className = 'jpcad-camera-client';
el.style.left = mouse.offsetX + 'px';
el.style.top = mouse.offsetY + 'px';
el.style.backgroundColor = client.user.color;
el.innerText = client.user.name;
this._cameraClients[id] = el;
this._cameraRef.current?.appendChild(el);
} else if (!mouse && this._cameraClients[id]) {
this._cameraRef.current?.removeChild(this._cameraClients[id]!);
this._cameraClients[id] = undefined;
} else {
this._selectedMesh = null;
}

// if (client.mouse) {
// const cameraPos = client.mouse as Position;
// if (el) {
// el.style.left = cameraPos.offsetX + 'px';
// el.style.top = cameraPos.offsetY + 'px';
// } else {
// const newEl = document.createElement('div');
// newEl.className = 'jpcad-camera-client';
// newEl.style.left = cameraPos.offsetX + 'px';
// newEl.style.top = cameraPos.offsetY + 'px';
// newEl.style.backgroundColor = client.user.color;
// newEl.innerText = client.user.name;
// this._cameraClients[id] = el;
// this._cameraRef.current?.appendChild(newEl);
// }
// } else {
// if (el) {
// this._cameraRef.current?.removeChild(el);
// this._cameraClients[id] = undefined;
// }
// }
}
});
}
};

private _handleThemeChange = (): void => {
const lightTheme =
document.body.getAttribute('data-jp-theme-light') === 'true';
this.setState(old => ({ ...old, lightTheme }));
};

private _handleWindowResize = (): void => {
clearTimeout(this._resizeTimeout);
this._resizeTimeout = setTimeout(() => {
this.forceUpdate();
}, 500);
};

render(): JSX.Element {
Expand Down
36 changes: 27 additions & 9 deletions src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { IJCadContent, IJCadModel } from './_interface/jcad';
import jcadSchema from './schema/jcad.json';
import {
IJCadObjectDoc,
IJupyterCadClientState,
IJupyterCadDoc,
IJupyterCadDocChange,
IJupyterCadModel,
Expand All @@ -21,7 +22,7 @@ export class JupyterCadModel implements IJupyterCadModel {
constructor(languagePreference?: string, modelDB?: IModelDB) {
this.modelDB = modelDB || new ModelDB();
this.sharedModel.changed.connect(this._onSharedModelChanged);
this.sharedModel.awareness.on('change', this._onCameraChanged);
this.sharedModel.awareness.on('change', this._onClientStateChanged);
}

get isDisposed(): boolean {
Expand Down Expand Up @@ -141,21 +142,35 @@ export class JupyterCadModel implements IJupyterCadModel {
return all;
}

syncCamera(pos: Position | undefined): void {
this.sharedModel.awareness.setLocalStateField('mouse', pos);
syncCamera(pos: Position | undefined, emitter?: string): void {
this.sharedModel.awareness.setLocalStateField('mouse', {
value: pos,
emitter: emitter
});
}

syncSelectedObject(name: string | null, emitter?: string): void {
this.sharedModel.awareness.setLocalStateField('selected', {
value: name,
emitter: emitter
});
}

getClientId(): number {
return this.sharedModel.awareness.clientID;
}

get cameraChanged(): ISignal<this, Map<number, any>> {
return this._cameraChanged;
get clientStateChanged(): ISignal<this, Map<number, IJupyterCadClientState>> {
return this._clientStateChanged;
}

private _onCameraChanged = () => {
const clients = this.sharedModel.awareness.getStates();
this._cameraChanged.emit(clients);
private _onClientStateChanged = changed => {
const clients = this.sharedModel.awareness.getStates() as Map<
number,
IJupyterCadClientState
>;

this._clientStateChanged.emit(clients);
};

private _onSharedModelChanged = (
Expand All @@ -176,7 +191,10 @@ export class JupyterCadModel implements IJupyterCadModel {
private _contentChanged = new Signal<this, void>(this);
private _stateChanged = new Signal<this, IChangedArgs<any>>(this);
private _themeChanged = new Signal<this, IChangedArgs<any>>(this);
private _cameraChanged = new Signal<this, Map<number, any>>(this);
private _clientStateChanged = new Signal<
this,
Map<number, IJupyterCadClientState>
>(this);
private _sharedModelChanged = new Signal<this, IJupyterCadDocChange>(this);
static worker: Worker;
}
Expand Down
35 changes: 10 additions & 25 deletions src/panelview/model.ts
Original file line number Diff line number Diff line change
@@ -1,60 +1,45 @@
import { ObservableMap } from '@jupyterlab/observables';
import { ISignal } from '@lumino/signaling';

import { IJupyterCadTracker } from '../token';
import {
IControlPanelModel,
IControlPanelState,
IJupyterCadDoc,
IJupyterCadModel,
IJupyterCadWidget,
ISateChangedSignal,
IStateValue
IJupyterCadWidget
} from '../types';

export class ControlPanelModel implements IControlPanelModel {
constructor(options: ControlPanelModel.IOptions) {
const state = { activatedObject: '', visibleObjects: [] };
this._state = new ObservableMap({ values: state });
this._stateChanged = this._state.changed;
this._tracker = options.tracker;
this._documentChanged = this._tracker.currentChanged;
}
get state(): ObservableMap<IStateValue> {
return this._state;
}
get stateChanged(): ISateChangedSignal {
return this._stateChanged;
}

get documentChanged(): ISignal<IJupyterCadTracker, IJupyterCadWidget | null> {
return this._documentChanged;
}

get filePath(): string | undefined {
return this._tracker.currentWidget?.context.localPath;
}

get jcadModel(): IJupyterCadModel | undefined {
return this._tracker.currentWidget?.context.model;
}

get sharedModel(): IJupyterCadDoc | undefined {
return this._tracker.currentWidget?.context.model.sharedModel;
}
set(key: keyof IControlPanelState, value: IStateValue): void {
this._state.set(key, value);
}
get(key: keyof IControlPanelState): IStateValue | undefined {
return this._state.get(key);
}
has(key: keyof IControlPanelState): boolean {
return this._state.has(key);
}

disconnect(f: any): void {
this._tracker.forEach(w =>
w.context.model.sharedModelChanged.disconnect(f)
);
this._tracker.forEach(w => w.context.model.themeChanged.disconnect(f));
this._tracker.forEach(w =>
w.context.model.clientStateChanged.disconnect(f)
);
}

private readonly _stateChanged: ISateChangedSignal;
private readonly _state: ObservableMap<IStateValue>;
private readonly _tracker: IJupyterCadTracker;
private _documentChanged: ISignal<
IJupyterCadTracker,
Expand Down
Loading

0 comments on commit 4d5c9f5

Please sign in to comment.