Skip to content

Commit c0219d7

Browse files
martinRenoupre-commit-ci[bot]github-actions[bot]
authored
Edge operators (#340)
* Chamfer backbone * Draft line rendering with width != 1 for all browsers * Refactor selection logic * Working version * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Allow ts-ignore * Add fillet symbols * Fillet operator * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix object properties panel * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Fix selection from tree view * Fix visibility toggle * Move prop * Ease linter * Revert example change * Update Playwright Snapshots * Slightly smaller lines * Update Playwright Snapshots * Fix fillet icon for JupyterLab display * Fix chamfer icon for jlab * Use chamfer and fillet icons * Rename Edge::Chamfer to Part::Chamfer * Ts-ignore warning * Remove dotted lines in icons --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent f286c89 commit c0219d7

35 files changed

+676
-168
lines changed

.eslintrc.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ module.exports = {
2525
'@typescript-eslint/no-unused-vars': ['warn', { args: 'none' }],
2626
'@typescript-eslint/no-explicit-any': 'off',
2727
'@typescript-eslint/no-namespace': 'off',
28+
'@typescript-eslint/ban-ts-comment': 'warn',
2829
'@typescript-eslint/no-use-before-define': 'off',
2930
'@typescript-eslint/quotes': [
3031
'error',

packages/base/src/commands.ts

Lines changed: 169 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
IJCadObject,
55
IJupyterCadDoc,
66
IJupyterCadModel,
7+
ISelection,
78
Parts
89
} from '@jupytercad/schema';
910
import { JupyterFrontEnd } from '@jupyterlab/application';
@@ -26,7 +27,9 @@ import {
2627
sphereIcon,
2728
torusIcon,
2829
unionIcon,
29-
clippingIcon
30+
clippingIcon,
31+
chamferIcon,
32+
filletIcon
3033
} from './tools';
3134
import { JupyterCadPanel, JupyterCadWidget } from './widget';
3235
import { DocumentRegistry } from '@jupyterlab/docregistry';
@@ -132,17 +135,60 @@ const PARTS = {
132135
}
133136
};
134137

138+
function getSelectedMeshName(
139+
selection: { [key: string]: ISelection } | undefined,
140+
index: number
141+
): string {
142+
if (selection === undefined) {
143+
return '';
144+
}
145+
146+
const selectedNames = Object.keys(selection);
147+
148+
if (selectedNames[index]) {
149+
const selected = selection[selectedNames[index]];
150+
151+
if (selected.type === 'shape') {
152+
return selectedNames[index];
153+
} else {
154+
return selected.parent as string;
155+
}
156+
}
157+
158+
return '';
159+
}
160+
161+
function getSelectedEdge(
162+
selection: { [key: string]: ISelection } | undefined
163+
): { shape: string; edgeIndex: number } | undefined {
164+
if (selection === undefined) {
165+
return;
166+
}
167+
168+
const selectedNames = Object.keys(selection);
169+
for (const name of selectedNames) {
170+
if (selection[name].type === 'edge') {
171+
return {
172+
shape: selection[name].parent!,
173+
edgeIndex: selection[name].edgeIndex!
174+
};
175+
}
176+
}
177+
}
178+
135179
const OPERATORS = {
136180
cut: {
137181
title: 'Cut parameters',
138182
shape: 'Part::Cut',
139183
default: (model: IJupyterCadModel) => {
140184
const objects = model.getAllObject();
141-
const selected = model.localState?.selected.value || [];
185+
const selected = model.localState?.selected.value || {};
186+
const sel0 = getSelectedMeshName(selected, 0);
187+
const sel1 = getSelectedMeshName(selected, 1);
142188
return {
143189
Name: newName('Cut', model),
144-
Base: selected.length > 0 ? selected[0] : objects[0].name ?? '',
145-
Tool: selected.length > 1 ? selected[1] : objects[1].name ?? '',
190+
Base: sel0 || objects[0].name || '',
191+
Tool: sel1 || objects[1].name || '',
146192
Refine: false,
147193
Placement: { Position: [0, 0, 0], Axis: [0, 0, 1], Angle: 0 }
148194
};
@@ -180,10 +226,11 @@ const OPERATORS = {
180226
shape: 'Part::Extrusion',
181227
default: (model: IJupyterCadModel) => {
182228
const objects = model.getAllObject();
183-
const selected = model.localState?.selected.value || [];
229+
const selected = model.localState?.selected.value || {};
230+
const sel0 = getSelectedMeshName(selected, 0);
184231
return {
185232
Name: newName('Extrusion', model),
186-
Base: [selected.length > 0 ? selected[0] : objects[0].name ?? ''],
233+
Base: [sel0 || objects[0].name || ''],
187234
Dir: [0, 0, 1],
188235
LengthFwd: 10,
189236
LengthRev: 0,
@@ -223,13 +270,12 @@ const OPERATORS = {
223270
shape: 'Part::MultiFuse',
224271
default: (model: IJupyterCadModel) => {
225272
const objects = model.getAllObject();
226-
const selected = model.localState?.selected.value || [];
273+
const selected = model.localState?.selected.value || {};
274+
const sel0 = getSelectedMeshName(selected, 0);
275+
const sel1 = getSelectedMeshName(selected, 1);
227276
return {
228277
Name: newName('Union', model),
229-
Shapes: [
230-
selected.length > 0 ? selected[0] : objects[0].name ?? '',
231-
selected.length > 1 ? selected[1] : objects[1].name ?? ''
232-
],
278+
Shapes: [sel0 || objects[0].name || '', sel1 || objects[1].name || ''],
233279
Refine: false,
234280
Placement: { Position: [0, 0, 0], Axis: [0, 0, 1], Angle: 0 }
235281
};
@@ -268,13 +314,12 @@ const OPERATORS = {
268314
shape: 'Part::MultiCommon',
269315
default: (model: IJupyterCadModel) => {
270316
const objects = model.getAllObject();
271-
const selected = model.localState?.selected.value || [];
317+
const selected = model.localState?.selected.value || {};
318+
const sel0 = getSelectedMeshName(selected, 0);
319+
const sel1 = getSelectedMeshName(selected, 1);
272320
return {
273321
Name: newName('Intersection', model),
274-
Shapes: [
275-
selected.length > 0 ? selected[0] : objects[0].name ?? '',
276-
selected.length > 1 ? selected[1] : objects[1].name ?? ''
277-
],
322+
Shapes: [sel0 || objects[0].name || '', sel1 || objects[1].name || ''],
278323
Refine: false,
279324
Placement: { Position: [0, 0, 0], Axis: [0, 0, 1], Angle: 0 }
280325
};
@@ -295,6 +340,88 @@ const OPERATORS = {
295340
setVisible(sharedModel, shape, false);
296341
});
297342

343+
if (!sharedModel.objectExists(objectModel.name)) {
344+
sharedModel.addObject(objectModel);
345+
} else {
346+
showErrorMessage(
347+
'The object already exists',
348+
'There is an existing object with the same name.'
349+
);
350+
}
351+
});
352+
}
353+
};
354+
}
355+
},
356+
chamfer: {
357+
title: 'Chamfer parameters',
358+
shape: 'Part::Chamfer',
359+
default: (model: IJupyterCadModel) => {
360+
const objects = model.getAllObject();
361+
const selectedEdge = getSelectedEdge(model.localState?.selected.value);
362+
return {
363+
Name: newName('Chamfer', model),
364+
Base: selectedEdge?.shape || objects[0].name || '',
365+
Edge: selectedEdge?.edgeIndex || 0,
366+
Dist: 0.2,
367+
Placement: { Position: [0, 0, 0], Axis: [0, 0, 1], Angle: 0 }
368+
};
369+
},
370+
syncData: (model: IJupyterCadModel) => {
371+
return (props: IDict) => {
372+
const { Name, ...parameters } = props;
373+
const objectModel: IJCadObject = {
374+
shape: 'Part::Chamfer',
375+
parameters,
376+
visible: true,
377+
name: Name
378+
};
379+
const sharedModel = model.sharedModel;
380+
if (sharedModel) {
381+
sharedModel.transact(() => {
382+
setVisible(sharedModel, parameters['Base'], false);
383+
384+
if (!sharedModel.objectExists(objectModel.name)) {
385+
sharedModel.addObject(objectModel);
386+
} else {
387+
showErrorMessage(
388+
'The object already exists',
389+
'There is an existing object with the same name.'
390+
);
391+
}
392+
});
393+
}
394+
};
395+
}
396+
},
397+
fillet: {
398+
title: 'Fillet parameters',
399+
shape: 'Part::Fillet',
400+
default: (model: IJupyterCadModel) => {
401+
const objects = model.getAllObject();
402+
const selectedEdge = getSelectedEdge(model.localState?.selected.value);
403+
return {
404+
Name: newName('Fillet', model),
405+
Base: selectedEdge?.shape || objects[0].name || '',
406+
Edge: selectedEdge?.edgeIndex || 0,
407+
Radius: 0.2,
408+
Placement: { Position: [0, 0, 0], Axis: [0, 0, 1], Angle: 0 }
409+
};
410+
},
411+
syncData: (model: IJupyterCadModel) => {
412+
return (props: IDict) => {
413+
const { Name, ...parameters } = props;
414+
const objectModel: IJCadObject = {
415+
shape: 'Part::Fillet',
416+
parameters,
417+
visible: true,
418+
name: Name
419+
};
420+
const sharedModel = model.sharedModel;
421+
if (sharedModel) {
422+
sharedModel.transact(() => {
423+
setVisible(sharedModel, parameters['Base'], false);
424+
298425
if (!sharedModel.objectExists(objectModel.name)) {
299426
sharedModel.addObject(objectModel);
300427
} else {
@@ -652,6 +779,28 @@ export function addCommands(
652779
execute: Private.executeOperator('intersection', tracker)
653780
});
654781

782+
commands.addCommand(CommandIDs.chamfer, {
783+
label: trans.__('Make chamfer'),
784+
isEnabled: () => {
785+
return tracker.currentWidget
786+
? tracker.currentWidget.context.model.sharedModel.editable
787+
: false;
788+
},
789+
icon: chamferIcon,
790+
execute: Private.executeOperator('chamfer', tracker)
791+
});
792+
793+
commands.addCommand(CommandIDs.fillet, {
794+
label: trans.__('Make fillet'),
795+
isEnabled: () => {
796+
return tracker.currentWidget
797+
? tracker.currentWidget.context.model.sharedModel.editable
798+
: false;
799+
},
800+
icon: filletIcon,
801+
execute: Private.executeOperator('fillet', tracker)
802+
});
803+
655804
commands.addCommand(CommandIDs.updateAxes, {
656805
label: trans.__('Axes Helper'),
657806
isEnabled: () => Boolean(tracker.currentWidget),
@@ -792,6 +941,9 @@ export namespace CommandIDs {
792941
export const union = 'jupytercad:union';
793942
export const intersection = 'jupytercad:intersection';
794943

944+
export const chamfer = 'jupytercad:chamfer';
945+
export const fillet = 'jupytercad:fillet';
946+
795947
export const updateAxes = 'jupytercad:updateAxes';
796948
export const updateExplodedView = 'jupytercad:updateExplodedView';
797949
export const updateCameraSettings = 'jupytercad:updateCameraSettings';
@@ -822,6 +974,7 @@ namespace Private {
822974
};
823975
});
824976
}
977+
825978
export function createPart(
826979
part: keyof typeof PARTS,
827980
tracker: WidgetTracker<JupyterCadWidget>

0 commit comments

Comments
 (0)