Skip to content

Commit

Permalink
Implement multi-selection
Browse files Browse the repository at this point in the history
  • Loading branch information
martinRenou committed May 25, 2023
1 parent 011d841 commit 3a2474b
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 115 deletions.
20 changes: 15 additions & 5 deletions packages/jupytercad-extension/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,10 +130,11 @@ const OPERATORS = {
shape: 'Part::Cut',
default: (model: IJupyterCadModel) => {
const objects = model.getAllObject();
const selected = model.localState?.selected.value || [];
return {
Name: newName('Cut', model),
Base: objects[0].name ?? '',
Tool: objects[1].name ?? '',
Base: selected.length > 0 ? selected[0] : objects[0].name ?? '',
Tool: selected.length > 1 ? selected[1] : objects[1].name ?? '',
Refine: false,
Placement: { Position: [0, 0, 0], Axis: [0, 0, 1], Angle: 0 }
};
Expand Down Expand Up @@ -178,9 +179,10 @@ const OPERATORS = {
shape: 'Part::Extrusion',
default: (model: IJupyterCadModel) => {
const objects = model.getAllObject();
const selected = model.localState?.selected.value || [];
return {
Name: newName('Extrusion', model),
Base: [objects[0].name ?? ''],
Base: [selected.length > 0 ? selected[0] : objects[0].name ?? ''],
Dir: [0, 0, 1],
LengthFwd: 10,
LengthRev: 0,
Expand Down Expand Up @@ -218,9 +220,13 @@ const OPERATORS = {
shape: 'Part::MultiFuse',
default: (model: IJupyterCadModel) => {
const objects = model.getAllObject();
const selected = model.localState?.selected.value || [];
return {
Name: newName('Union', model),
Shapes: [objects[0].name ?? '', objects[1].name ?? ''],
Shapes: [
selected.length > 0 ? selected[0] : objects[0].name ?? '',
selected.length > 1 ? selected[1] : objects[1].name ?? ''
],
Refine: false,
Placement: { Position: [0, 0, 0], Axis: [0, 0, 1], Angle: 0 }
};
Expand Down Expand Up @@ -255,9 +261,13 @@ const OPERATORS = {
shape: 'Part::MultiCommon',
default: (model: IJupyterCadModel) => {
const objects = model.getAllObject();
const selected = model.localState?.selected.value || [];
return {
Name: newName('Intersection', model),
Shapes: [objects[0].name ?? '', objects[1].name ?? ''],
Shapes: [
selected.length > 0 ? selected[0] : objects[0].name ?? '',
selected.length > 1 ? selected[1] : objects[1].name ?? ''
],
Refine: false,
Placement: { Position: [0, 0, 0], Axis: [0, 0, 1], Angle: 0 }
};
Expand Down
128 changes: 58 additions & 70 deletions packages/jupytercad-extension/src/mainview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -488,43 +488,37 @@ export class MainView extends React.Component<IProps, IStates> {

private _onClick(e: MouseEvent) {
const selection = this._pick();

const guidata = this._model.sharedModel.getOption('guidata');
const selectedMeshesNames = new Set(
this._selectedMeshes.map(sel => sel.name)
);

if (selection) {
// Deselect old selection
if (this._selectedMesh) {
let originalColor = DEFAULT_MESH_COLOR;
if (
guidata &&
guidata[this._selectedMesh.name] &&
guidata[this._selectedMesh.name]['color']
) {
const rgba = guidata[this._selectedMesh.name]['color'] as number[];
originalColor = new THREE.Color(rgba[0], rgba[1], rgba[2]);
}

this._selectedMesh.material.color = originalColor;
// TODO Support selecting edges?
let selectionName = '';
if (selection.mesh.name.startsWith('edge')) {
selectionName = (selection.mesh.parent as BasicMesh).name;
} else {
selectionName = selection.mesh.name;
}

// Set new selection
if (selection.mesh === this._selectedMesh) {
this._selectedMesh = null;
} else {
// TODO Support selecting edges?
if (selection.mesh.name.startsWith('edge')) {
this._selectedMesh = selection.mesh.parent as BasicMesh;
if (e.ctrlKey) {
if (selectedMeshesNames.has(selectionName)) {
selectedMeshesNames.delete(selectionName);
} else {
this._selectedMesh = selection.mesh;
selectedMeshesNames.add(selectionName);
}
}

if (this._selectedMesh) {
this._selectedMesh.material.color = SELECTED_MESH_COLOR;
this._model.syncSelectedObject(this._selectedMesh.name, this.state.id);
} else {
this._model.syncSelectedObject(undefined, this.state.id);
const alreadySelected = selectedMeshesNames.has(selectionName);
selectedMeshesNames.clear();

if (!alreadySelected) {
selectedMeshesNames.add(selectionName);
}
}

const names = Array.from(selectedMeshesNames);
this._updateSelected(names);
this._model.syncSelectedObject(names, this.state.id);
}
}

Expand All @@ -538,6 +532,9 @@ export class MainView extends React.Component<IProps, IStates> {

const guidata = this._model.sharedModel.getOption('guidata');

const selectedNames = this._selectedMeshes.map(sel => sel.name);
this._selectedMeshes = [];

this._boundingGroup = new THREE.Box3();

this._meshGroup = new THREE.Group();
Expand Down Expand Up @@ -619,8 +616,8 @@ export class MainView extends React.Component<IProps, IStates> {
this._boundingGroup.expandByObject(mesh);
}

if (this._selectedMesh?.name === objName) {
this._selectedMesh = mesh;
if (selectedNames.includes(objName)) {
this._selectedMeshes.push(mesh);
mesh.material.color = SELECTED_MESH_COLOR;
}

Expand Down Expand Up @@ -726,37 +723,36 @@ export class MainView extends React.Component<IProps, IStates> {
return new THREE.Mesh(this._pointerGeometry, material);
}

private _selectObject(name: string): void {
if (name === this._selectedMesh?.name) {
return;
}

const selected = this._meshGroup?.getObjectByName(name);
private _updateSelected(names: string[]) {
// Reset original color for old selection
for (const selectedMesh of this._selectedMeshes) {
let originalColor = DEFAULT_MESH_COLOR;
const guidata = this._model.sharedModel.getOption('guidata');
if (
guidata &&
guidata[selectedMesh.name] &&
guidata[selectedMesh.name]['color']
) {
const rgba = guidata[selectedMesh.name]['color'] as number[];
originalColor = new THREE.Color(rgba[0], rgba[1], rgba[2]);
}

if (selected) {
this._selectedMesh = selected as BasicMesh;
this._selectedMesh.material.color = SELECTED_MESH_COLOR;
selectedMesh.material.color = originalColor;
}
}

private _deselectObject(): void {
if (!this._selectedMesh) {
return;
}
// Set new selection
this._selectedMeshes = [];
for (const name of names) {
const selected = this._meshGroup?.getObjectByName(name) as
| BasicMesh
| undefined;
if (!selected) {
continue;
}

let originalColor = DEFAULT_MESH_COLOR;
const guidata = this._model.sharedModel.getOption('guidata');
if (
guidata &&
guidata[this._selectedMesh.name] &&
guidata[this._selectedMesh.name]['color']
) {
const rgba = guidata[this._selectedMesh.name]['color'] as number[];
originalColor = new THREE.Color(rgba[0], rgba[1], rgba[2]);
this._selectedMeshes.push(selected);
selected.material.color = SELECTED_MESH_COLOR;
}

this._selectedMesh.material.color = originalColor;
this._selectedMesh = null;
}

private _onSharedMetadataChanged = (
Expand Down Expand Up @@ -804,12 +800,8 @@ export class MainView extends React.Component<IProps, IStates> {
}

// Sync selected
if (remoteState.selected.value !== this._selectedMesh?.name) {
this._deselectObject();

if (remoteState.selected.value) {
this._selectObject(remoteState.selected.value);
}
if (Array.isArray(remoteState.selected.value)) {
this._updateSelected(remoteState.selected.value);
}

// Sync camera
Expand Down Expand Up @@ -840,12 +832,8 @@ export class MainView extends React.Component<IProps, IStates> {
// Sync local selection if needed
const localState = this._model.localState;

if (localState?.selected?.value !== this._selectedMesh?.name) {
this._deselectObject();

if (localState?.selected?.value) {
this._selectObject(localState.selected.value);
}
if (localState?.selected && Array.isArray(localState.selected.value)) {
this._updateSelected(localState.selected.value);
}
}

Expand Down Expand Up @@ -1151,7 +1139,7 @@ export class MainView extends React.Component<IProps, IStates> {
position: THREE.Vector3 | undefined,
parent: string | undefined
) => void;
private _selectedMesh: BasicMesh | null = null;
private _selectedMeshes: BasicMesh[] = [];

private _meshGroup: THREE.Group | null = null; // The list of ThreeJS meshes

Expand Down
2 changes: 1 addition & 1 deletion packages/jupytercad-extension/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ export class JupyterCadModel implements IJupyterCadModel {
});
}

syncSelectedObject(name?: string, emitter?: string): void {
syncSelectedObject(name: string[], emitter?: string): void {
this.sharedModel.awareness.setLocalStateField('selected', {
value: name,
emitter: emitter
Expand Down
Loading

0 comments on commit 3a2474b

Please sign in to comment.