1
1
import numpy as np
2
2
3
3
import pygfx
4
+ from pylinalg import quat_from_vecs , vec_transform_quat
4
5
5
6
6
7
GRID_PLANES = ["xy" , "xz" , "yz" ]
7
8
9
+ CANONICAL_BAIS = np .array ([[1.0 , 0.0 , 0.0 ], [0.0 , 1.0 , 0.0 ], [0.0 , 0.0 , 1.0 ]])
10
+
8
11
9
12
# very thin subclass that just adds GridMaterial properties to this world object for easier user control
10
13
class Grid (pygfx .Grid ):
@@ -252,6 +255,9 @@ def __init__(
252
255
grid_kwargs : dict = None ,
253
256
auto_grid : bool = True ,
254
257
offset : np .ndarray = np .array ([0.0 , 0.0 , 0.0 ]),
258
+ basis : np .ndarray = np .array (
259
+ [[1.0 , 0.0 , 0.0 ], [0.0 , 1.0 , 0.0 ], [0.0 , 0.0 , 1.0 ]]
260
+ ),
255
261
):
256
262
self ._plot_area = plot_area
257
263
@@ -362,10 +368,28 @@ def __init__(
362
368
self ._intersection = intersection
363
369
self ._auto_grid = auto_grid
364
370
371
+ self ._basis = None
372
+ self .basis = basis
373
+
365
374
@property
366
375
def world_object (self ) -> pygfx .WorldObject :
367
376
return self ._world_object
368
377
378
+ @property
379
+ def basis (self ) -> np .ndarray :
380
+ """get or set the basis, shape is [3, 3]"""
381
+ return self ._basis
382
+
383
+ @basis .setter
384
+ def basis (self , basis : np .ndarray ):
385
+ if basis .shape != (3 , 3 ):
386
+ raise ValueError
387
+
388
+ # apply quaternion to each of x, y, z rulers
389
+ for dim , cbasis , new_basis in zip (["x" , "y" , "z" ], CANONICAL_BAIS , basis ):
390
+ ruler : pygfx .Ruler = getattr (self , dim )
391
+ ruler .local .rotation = quat_from_vecs (cbasis , new_basis )
392
+
369
393
@property
370
394
def offset (self ) -> np .ndarray :
371
395
"""offset of the axes"""
@@ -395,6 +419,19 @@ def grids(self) -> Grids | bool:
395
419
"""grids for each plane: xy, xz, yz"""
396
420
return self ._grids
397
421
422
+ @property
423
+ def colors (self ) -> tuple [pygfx .Color ]:
424
+ return tuple (getattr (self , dim ).line .material .color for dim in ["x" , "y" , "z" ])
425
+
426
+ @colors .setter
427
+ def colors (self , colors : tuple [pygfx .Color | str ]):
428
+ """get or set the colors for the x, y, and z rulers"""
429
+ if len (colors ) != 3 :
430
+ raise ValueError
431
+
432
+ for dim , color in zip (["x" , "y" , "z" ], colors ):
433
+ getattr (self , dim ).line .material .color = color
434
+
398
435
@property
399
436
def auto_grid (self ) -> bool :
400
437
"""auto adjust the grid on each render cycle"""
@@ -482,10 +519,31 @@ def update_using_camera(self):
482
519
xpos , ypos , width , height = self ._plot_area .get_rect ()
483
520
# orthographic projection, get ranges using inverse
484
521
485
- # get range of screen space
522
+ # get range of screen space by getting the corners
486
523
xmin , xmax = xpos , xpos + width
487
524
ymin , ymax = ypos + height , ypos
488
525
526
+ # apply quaternion to account for rotation of axes
527
+ # xmin, _, _ = vec_transform_quat(
528
+ # [xmin, ypos + height / 2, 0],
529
+ # self.x.local.rotation
530
+ # )
531
+ #
532
+ # xmax, _, _ = vec_transform_quat(
533
+ # [xmax, ypos + height / 2, 0],
534
+ # self.x.local.rotation,
535
+ # )
536
+ #
537
+ # _, ymin, _ = vec_transform_quat(
538
+ # [xpos + width / 2, ymin, 0],
539
+ # self.y.local.rotation
540
+ # )
541
+ #
542
+ # _, ymax, _ = vec_transform_quat(
543
+ # [xpos + width / 2, ymax, 0],
544
+ # self.y.local.rotation
545
+ # )
546
+
489
547
min_vals = self ._plot_area .map_screen_to_world ((xmin , ymin ))
490
548
max_vals = self ._plot_area .map_screen_to_world ((xmax , ymax ))
491
549
@@ -578,7 +636,7 @@ def update(self, bbox, intersection):
578
636
self .z .start_pos = world_x_10 , world_y_10 , world_zmin
579
637
self .z .end_pos = world_x_10 , world_y_10 , world_zmax
580
638
581
- self .z .start_value = self .z .start_pos [1 ] - self .offset [2 ]
639
+ self .z .start_value = self .z .start_pos [2 ] - self .offset [2 ]
582
640
statsz = self .z .update (
583
641
self ._plot_area .camera , self ._plot_area .viewport .logical_size
584
642
)
0 commit comments