4
4
IJCadObject ,
5
5
IJupyterCadDoc ,
6
6
IJupyterCadModel ,
7
+ ISelection ,
7
8
Parts
8
9
} from '@jupytercad/schema' ;
9
10
import { JupyterFrontEnd } from '@jupyterlab/application' ;
@@ -26,7 +27,9 @@ import {
26
27
sphereIcon ,
27
28
torusIcon ,
28
29
unionIcon ,
29
- clippingIcon
30
+ clippingIcon ,
31
+ chamferIcon ,
32
+ filletIcon
30
33
} from './tools' ;
31
34
import { JupyterCadPanel , JupyterCadWidget } from './widget' ;
32
35
import { DocumentRegistry } from '@jupyterlab/docregistry' ;
@@ -132,17 +135,60 @@ const PARTS = {
132
135
}
133
136
} ;
134
137
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
+
135
179
const OPERATORS = {
136
180
cut : {
137
181
title : 'Cut parameters' ,
138
182
shape : 'Part::Cut' ,
139
183
default : ( model : IJupyterCadModel ) => {
140
184
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 ) ;
142
188
return {
143
189
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 || '' ,
146
192
Refine : false ,
147
193
Placement : { Position : [ 0 , 0 , 0 ] , Axis : [ 0 , 0 , 1 ] , Angle : 0 }
148
194
} ;
@@ -180,10 +226,11 @@ const OPERATORS = {
180
226
shape : 'Part::Extrusion' ,
181
227
default : ( model : IJupyterCadModel ) => {
182
228
const objects = model . getAllObject ( ) ;
183
- const selected = model . localState ?. selected . value || [ ] ;
229
+ const selected = model . localState ?. selected . value || { } ;
230
+ const sel0 = getSelectedMeshName ( selected , 0 ) ;
184
231
return {
185
232
Name : newName ( 'Extrusion' , model ) ,
186
- Base : [ selected . length > 0 ? selected [ 0 ] : objects [ 0 ] . name ?? '' ] ,
233
+ Base : [ sel0 || objects [ 0 ] . name || '' ] ,
187
234
Dir : [ 0 , 0 , 1 ] ,
188
235
LengthFwd : 10 ,
189
236
LengthRev : 0 ,
@@ -223,13 +270,12 @@ const OPERATORS = {
223
270
shape : 'Part::MultiFuse' ,
224
271
default : ( model : IJupyterCadModel ) => {
225
272
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 ) ;
227
276
return {
228
277
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 || '' ] ,
233
279
Refine : false ,
234
280
Placement : { Position : [ 0 , 0 , 0 ] , Axis : [ 0 , 0 , 1 ] , Angle : 0 }
235
281
} ;
@@ -268,13 +314,12 @@ const OPERATORS = {
268
314
shape : 'Part::MultiCommon' ,
269
315
default : ( model : IJupyterCadModel ) => {
270
316
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 ) ;
272
320
return {
273
321
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 || '' ] ,
278
323
Refine : false ,
279
324
Placement : { Position : [ 0 , 0 , 0 ] , Axis : [ 0 , 0 , 1 ] , Angle : 0 }
280
325
} ;
@@ -295,6 +340,88 @@ const OPERATORS = {
295
340
setVisible ( sharedModel , shape , false ) ;
296
341
} ) ;
297
342
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
+
298
425
if ( ! sharedModel . objectExists ( objectModel . name ) ) {
299
426
sharedModel . addObject ( objectModel ) ;
300
427
} else {
@@ -652,6 +779,28 @@ export function addCommands(
652
779
execute : Private . executeOperator ( 'intersection' , tracker )
653
780
} ) ;
654
781
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
+
655
804
commands . addCommand ( CommandIDs . updateAxes , {
656
805
label : trans . __ ( 'Axes Helper' ) ,
657
806
isEnabled : ( ) => Boolean ( tracker . currentWidget ) ,
@@ -792,6 +941,9 @@ export namespace CommandIDs {
792
941
export const union = 'jupytercad:union' ;
793
942
export const intersection = 'jupytercad:intersection' ;
794
943
944
+ export const chamfer = 'jupytercad:chamfer' ;
945
+ export const fillet = 'jupytercad:fillet' ;
946
+
795
947
export const updateAxes = 'jupytercad:updateAxes' ;
796
948
export const updateExplodedView = 'jupytercad:updateExplodedView' ;
797
949
export const updateCameraSettings = 'jupytercad:updateCameraSettings' ;
@@ -822,6 +974,7 @@ namespace Private {
822
974
} ;
823
975
} ) ;
824
976
}
977
+
825
978
export function createPart (
826
979
part : keyof typeof PARTS ,
827
980
tracker : WidgetTracker < JupyterCadWidget >
0 commit comments