115115_ANNOTATION_TYPES = ["text" , "line" , "ellipse" , "rectangle" ]
116116
117117
118+ class _CoordinateXY2Mixin :
119+ """Mixin providing x2/y2 coordinate properties for annotation subclasses."""
120+
121+ @property
122+ def x2 (self ) -> int :
123+ """X2 coordinate.
124+
125+ :returns: The x2 coordinate value.
126+ """
127+ self ._lab .sync_topology_if_outdated ()
128+ return self ._x2
129+
130+ @x2 .setter
131+ @locked
132+ def x2 (self , value : int ) -> None :
133+ """Set x2 coordinate.
134+
135+ :param value: The x2 coordinate value to set.
136+ """
137+ self ._set_annotation_property ("x2" , value )
138+ self ._x2 = value
139+
140+ @property
141+ def y2 (self ) -> int :
142+ """Y2 coordinate.
143+
144+ :returns: The y2 coordinate value.
145+ """
146+ self ._lab .sync_topology_if_outdated ()
147+ return self ._y2
148+
149+ @y2 .setter
150+ @locked
151+ def y2 (self , value : int ) -> None :
152+ """Set y2 coordinate.
153+
154+ :param value: The y2 coordinate value to set.
155+ """
156+ self ._set_annotation_property ("y2" , value )
157+ self ._y2 = value
158+
159+
160+ class _RotationMixin :
161+ """Mixin providing rotation property for annotation subclasses."""
162+
163+ @property
164+ def rotation (self ) -> int :
165+ """Rotation of an object, in degrees.
166+
167+ :returns: The rotation value in degrees.
168+ """
169+ self ._lab .sync_topology_if_outdated ()
170+ return self ._rotation
171+
172+ @rotation .setter
173+ @locked
174+ def rotation (self , value : int ) -> None :
175+ """Set rotation of an object, in degrees.
176+
177+ :param value: The rotation value in degrees to set.
178+ """
179+ self ._set_annotation_property ("rotation" , value )
180+ self ._rotation = value
181+
182+
118183class Annotation :
119184 """Base class for VIRL2 lab annotations (text, line, ellipse, rectangle)."""
120185
@@ -380,7 +445,7 @@ def get_default_property_values(cls, annotation_type: str) -> dict[str, Any]:
380445 for ppty in ANNOTATION_PROPERTY_MAP :
381446 if ppty == "type" :
382447 continue
383- if not ANNOTATION_MAP [ annotation_type ] & ANNOTATION_PROPERTY_MAP [ ppty ] :
448+ if not cls . _is_property_valid_for_type ( annotation_type , ppty ) :
384449 continue
385450 ppty_default = ANNOTATION_PROPERTIES_DEFAULTS [ppty ]
386451 if isinstance (ppty_default , dict ):
@@ -401,14 +466,19 @@ def is_valid_property(
401466 :param _property: The property name to validate.
402467 :returns: True if the property is valid for the given type, False otherwise.
403468 """
404- if (
405- annotation_type not in _ANNOTATION_TYPES
406- or _property not in ANNOTATION_PROPERTY_MAP
407- ):
408- return False
409469 return (
470+ annotation_type in _ANNOTATION_TYPES
471+ and _property in ANNOTATION_PROPERTY_MAP
472+ and cls ._is_property_valid_for_type (annotation_type , _property )
473+ )
474+
475+ @classmethod
476+ def _is_property_valid_for_type (
477+ cls , annotation_type : AnnotationTypeString , _property : str
478+ ) -> bool :
479+ return bool (
410480 ANNOTATION_MAP [annotation_type ] & ANNOTATION_PROPERTY_MAP [_property ]
411- ) > 0
481+ )
412482
413483 @locked
414484 def as_dict (self ) -> dict [str , Any ]:
@@ -503,7 +573,7 @@ def _set_annotation_properties(self, annotation_data: dict[str, Any]) -> None:
503573# ~~~~~< Annotation subclasses >~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
504574
505575
506- class AnnotationRectangle (Annotation ):
576+ class AnnotationRectangle (_CoordinateXY2Mixin , _RotationMixin , Annotation ):
507577 """
508578 Annotation class representing rectangle annotation.
509579 """
@@ -556,65 +626,8 @@ def border_radius(self, value: int) -> None:
556626 self ._set_annotation_property ("border_radius" , value )
557627 self ._border_radius = value
558628
559- @property
560- def x2 (self ) -> int :
561- """X2 coordinate.
562-
563- :returns: The x2 coordinate value.
564- """
565- self ._lab .sync_topology_if_outdated ()
566- return self ._x2
567-
568- @x2 .setter
569- @locked
570- def x2 (self , value : int ) -> None :
571- """Set x2 coordinate.
572-
573- :param value: The x2 coordinate value to set.
574- """
575- self ._set_annotation_property ("x2" , value )
576- self ._x2 = value
577-
578- @property
579- def y2 (self ) -> int :
580- """Y2 coordinate.
581-
582- :returns: The y2 coordinate value.
583- """
584- self ._lab .sync_topology_if_outdated ()
585- return self ._y2
586-
587- @y2 .setter
588- @locked
589- def y2 (self , value : int ) -> None :
590- """Set y2 coordinate.
591-
592- :param value: The y2 coordinate value to set.
593- """
594- self ._set_annotation_property ("y2" , value )
595- self ._y2 = value
596-
597- @property
598- def rotation (self ) -> int :
599- """Rotation of an object, in degrees.
600-
601- :returns: The rotation value in degrees.
602- """
603- self ._lab .sync_topology_if_outdated ()
604- return self ._rotation
605-
606- @rotation .setter
607- @locked
608- def rotation (self , value : int ) -> None :
609- """Set rotation of an object, in degrees.
610-
611- :param value: The rotation value in degrees to set.
612- """
613- self ._set_annotation_property ("rotation" , value )
614- self ._rotation = value
615629
616-
617- class AnnotationEllipse (Annotation ):
630+ class AnnotationEllipse (_CoordinateXY2Mixin , _RotationMixin , Annotation ):
618631 """
619632 Annotation class representing ellipse annotation.
620633 """
@@ -645,65 +658,8 @@ def __init__(
645658 if annotation_data :
646659 self ._update (annotation_data , push_to_server = False )
647660
648- @property
649- def x2 (self ) -> int :
650- """X2 coordinate.
651-
652- :returns: The x2 coordinate value.
653- """
654- self ._lab .sync_topology_if_outdated ()
655- return self ._x2
656-
657- @x2 .setter
658- @locked
659- def x2 (self , value : int ) -> None :
660- """Set x2 coordinate.
661-
662- :param value: The x2 coordinate value to set.
663- """
664- self ._set_annotation_property ("x2" , value )
665- self ._x2 = value
666-
667- @property
668- def y2 (self ) -> int :
669- """Y2 coordinate.
670-
671- :returns: The y2 coordinate value.
672- """
673- self ._lab .sync_topology_if_outdated ()
674- return self ._y2
675-
676- @y2 .setter
677- @locked
678- def y2 (self , value : int ) -> None :
679- """Set y2 coordinate.
680-
681- :param value: The y2 coordinate value to set.
682- """
683- self ._set_annotation_property ("y2" , value )
684- self ._y2 = value
685-
686- @property
687- def rotation (self ) -> int :
688- """Rotation of an object, in degrees.
689-
690- :returns: The rotation value in degrees.
691- """
692- self ._lab .sync_topology_if_outdated ()
693- return self ._rotation
694661
695- @rotation .setter
696- @locked
697- def rotation (self , value : int ) -> None :
698- """Set rotation of an object, in degrees.
699-
700- :param value: The rotation value in degrees to set.
701- """
702- self ._set_annotation_property ("rotation" , value )
703- self ._rotation = value
704-
705-
706- class AnnotationLine (Annotation ):
662+ class AnnotationLine (_CoordinateXY2Mixin , Annotation ):
707663 """
708664 Annotation class representing line annotation.
709665 """
@@ -737,44 +693,6 @@ def __init__(
737693 if annotation_data :
738694 self ._update (annotation_data , push_to_server = False )
739695
740- @property
741- def x2 (self ) -> int :
742- """X2 coordinate.
743-
744- :returns: The x2 coordinate value.
745- """
746- self ._lab .sync_topology_if_outdated ()
747- return self ._x2
748-
749- @x2 .setter
750- @locked
751- def x2 (self , value : int ) -> None :
752- """Set x2 coordinate.
753-
754- :param value: The x2 coordinate value to set.
755- """
756- self ._set_annotation_property ("x2" , value )
757- self ._x2 = value
758-
759- @property
760- def y2 (self ) -> int :
761- """Y2 coordinate.
762-
763- :returns: The y2 coordinate value.
764- """
765- self ._lab .sync_topology_if_outdated ()
766- return self ._y2
767-
768- @y2 .setter
769- @locked
770- def y2 (self , value : int ) -> None :
771- """Set y2 coordinate.
772-
773- :param value: The y2 coordinate value to set.
774- """
775- self ._set_annotation_property ("y2" , value )
776- self ._y2 = value
777-
778696 @property
779697 def line_start (self ) -> str | None :
780698 """Line arrow start style.
@@ -814,7 +732,7 @@ def line_end(self, value: str | None) -> None:
814732 self ._line_end = value
815733
816734
817- class AnnotationText (Annotation ):
735+ class AnnotationText (_RotationMixin , Annotation ):
818736 """
819737 Annotation class representing text annotation.
820738 """
@@ -863,25 +781,6 @@ def __init__(
863781 if annotation_data :
864782 self ._update (annotation_data , push_to_server = False )
865783
866- @property
867- def rotation (self ) -> int :
868- """Rotation of an object, in degrees.
869-
870- :returns: The rotation value in degrees.
871- """
872- self ._lab .sync_topology_if_outdated ()
873- return self ._rotation
874-
875- @rotation .setter
876- @locked
877- def rotation (self , value : int ) -> None :
878- """Set rotation of an object, in degrees.
879-
880- :param value: The rotation value in degrees to set.
881- """
882- self ._set_annotation_property ("rotation" , value )
883- self ._rotation = value
884-
885784 @property
886785 def text_bold (self ) -> bool :
887786 """Text boldness.
0 commit comments