@@ -1003,15 +1003,12 @@ def matchText(self, text: str) -> bool:
10031003 isDefault = Property (bool , lambda self : all (v .isDefault for v in self .value ), notify = Attribute .valueChanged )
10041004
10051005
1006- class ShapeAttribute (GroupAttribute ):
1006+ class GeometryAttribute (GroupAttribute ):
10071007 """
1008- GroupAttribute subtype tailored for shape -specific handling.
1008+ GroupAttribute subtype tailored for geometry -specific handling.
10091009 """
10101010
1011- def __init__ (self , node , attributeDesc : desc .Shape , isOutput : bool ,
1012- root = None , parent = None ):
1013- self ._visible = True
1014- self ._color = "#2A82DA" # default shape color
1011+ def __init__ (self , node , attributeDesc : desc .Geometry , isOutput : bool , root = None , parent = None ):
10151012 super ().__init__ (node , attributeDesc , isOutput , root , parent )
10161013
10171014 # Override
@@ -1038,20 +1035,20 @@ def upgradeValue(self, exportedValue):
10381035 def getSerializedValue (self ):
10391036 if self .isLink :
10401037 return self ._getInputLink ().asLinkExpr ()
1041- return { key : attr . getSerializedValue () for key , attr in self . _value . objects . items ()}
1038+ return super (). getSerializedValue ()
10421039
10431040 def getValueAsDict (self ) -> dict :
10441041 """
1045- Return the shape attribute value as dict.
1046- For not keyable shape , this is the same as getSerializedValue().
1047- For keyable shape , the dict is indexed by key.
1042+ Return the geometry attribute value as dict.
1043+ For not keyable geometry , this is the same as getSerializedValue().
1044+ For keyable geometry , the dict is indexed by key.
10481045 """
10491046 from collections import defaultdict
10501047 outValue = defaultdict (dict )
1051- if not self .shapeKeyable :
1048+ if not self .observationKeyable :
10521049 return super ().getSerializedValue ()
10531050 for attribute in self .value :
1054- if isinstance (attribute , ShapeAttribute ):
1051+ if isinstance (attribute , GeometryAttribute ):
10551052 attributeDict = attribute .getValueAsDict ()
10561053 if attributeDict :
10571054 for key , value in attributeDict .items ():
@@ -1061,115 +1058,57 @@ def getValueAsDict(self) -> dict:
10611058 outValue [str (pair .key )][attribute .name ] = pair .value
10621059 return dict (outValue )
10631060
1064- def getShapeAsDict (self ) -> dict :
1065- """
1066- Return the shape attribute as dict with the shape file structure.
1067- """
1068- outDict = {
1069- "name" : self .rootName ,
1070- "type" : self .type ,
1071- "properties" : { "color" : self ._color }
1072- }
1073-
1074- if not self .shapeKeyable :
1075- # Not keyable shape, use properties.
1076- outDict .get ("properties" ).update (super ().getSerializedValue ())
1077- else :
1078- # Keyable shape, use observations.
1079- from collections import defaultdict
1080- outObservations = defaultdict (dict )
1081- for attribute in self .value :
1082- if isinstance (attribute , ShapeAttribute ):
1083- attributeDict = attribute .getValueAsDict ()
1084- if attributeDict :
1085- for key , value in attributeDict .items ():
1086- outObservations [key ][attribute .name ] = value
1087- else :
1088- for pair in attribute .keyValues .pairs :
1089- outObservations [str (pair .key )][attribute .name ] = pair .value
1090- outDict .update ({ "observations" : dict (outObservations )})
1091- return outDict
1092-
1093- def _getVisible (self ) -> bool :
1094- """
1095- Return whether the shape attribute is visible for display.
1096- """
1097- return self ._visible
1098-
1099- def _setVisible (self , visible :bool ):
1100- """
1101- Set the shape attribute visibility for display.
1102- """
1103- self ._visible = visible
1104- self .shapeChanged .emit ()
1105-
1106- def _getColor (self ) -> str :
1107- """
1108- Return the shape attribute color for display.
1109- """
1110- if self .isLink :
1111- return self .inputLink .shapeColor
1112- return self ._color
1113-
1114- @raiseIfLink
1115- def _setColor (self , color : str ):
1116- """
1117- Set the shape attribute color for display.
1118- """
1119- self ._color = color
1120- self .shapeChanged .emit ()
1121-
11221061 def _hasKeyableChilds (self ) -> bool :
11231062 """
11241063 Whether all child attributes are keyable.
11251064 """
1126- return all ((isinstance (attribute , ShapeAttribute ) and attribute .shapeKeyable ) or
1065+ return all ((isinstance (attribute , GeometryAttribute ) and attribute .observationKeyable ) or
11271066 attribute .keyable for attribute in self .value )
11281067
11291068 def _getNbObservations (self ) -> int :
11301069 """
1131- Return the shape attribute number of observations.
1070+ Return the geometry attribute number of observations.
11321071 Note: Observation is a value defined across all child attributes for a specific key.
11331072 """
1134- if self .shapeKeyable :
1073+ if self .observationKeyable :
11351074 firstAttribute = next (iter (self .value .values ()))
1136- if isinstance (firstAttribute , ShapeAttribute ):
1075+ if isinstance (firstAttribute , GeometryAttribute ):
11371076 return firstAttribute .nbObservations
11381077 return len (firstAttribute .keyValues .pairs )
11391078 return 1
11401079
11411080 def _getObservationKeys (self ) -> list :
11421081 """
1143- Return the shape attribute list of observation keys.
1082+ Return the geometry attribute list of observation keys.
11441083 Note: Observation is a value defined across all child attributes for a specific key.
11451084 """
1146- if not self .shapeKeyable :
1085+ if not self .observationKeyable :
11471086 return []
11481087 firstAttribute = next (iter (self .value .values ()))
1149- if isinstance (firstAttribute , ShapeAttribute ):
1088+ if isinstance (firstAttribute , GeometryAttribute ):
11501089 return firstAttribute .observationKeys
11511090 return firstAttribute .keyValues .getKeys ()
11521091
11531092 @Slot (str , result = bool )
11541093 def hasObservation (self , key : str ) -> bool :
11551094 """
1156- Whether the shape attribute has an observation for the given key.
1095+ Whether the geometry attribute has an observation for the given key.
11571096 Note: Observation is a value defined across all child attributes for a specific key.
11581097 """
1159- if not self .shapeKeyable :
1098+ if not self .observationKeyable :
11601099 return True
1161- return all ((isinstance (attribute , ShapeAttribute ) and attribute .hasObservation (key )) or
1162- (not isinstance (attribute , ShapeAttribute ) and attribute .keyValues .hasKey (key ))
1100+ return all ((isinstance (attribute , GeometryAttribute ) and attribute .hasObservation (key )) or
1101+ (not isinstance (attribute , GeometryAttribute ) and attribute .keyValues .hasKey (key ))
11631102 for attribute in self .value )
11641103
11651104 @raiseIfLink
11661105 def removeObservation (self , key : str ):
11671106 """
1168- Remove the shape attribute observation for the given key.
1107+ Remove the geometry attribute observation for the given key.
11691108 Note: Observation is a value defined across all child attributes for a specific key.
11701109 """
11711110 for attribute in self .value :
1172- if isinstance (attribute , ShapeAttribute ):
1111+ if isinstance (attribute , GeometryAttribute ):
11731112 attribute .removeObservation (key )
11741113 else :
11751114 if attribute .keyable :
@@ -1181,15 +1120,15 @@ def removeObservation(self, key: str):
11811120 @raiseIfLink
11821121 def setObservation (self , key : str , observation : Variant ):
11831122 """
1184- Set the shape attribute observation for the given key with the given observation.
1123+ Set the geometry attribute observation for the given key with the given observation.
11851124 Note: Observation is a value defined across all child attributes for a specific key.
11861125 """
11871126 for attributeStr , value in observation .items ():
11881127 attribute = self .childAttribute (attributeStr )
11891128 if attribute is None :
1190- raise RuntimeError (f"Cannot set shape observation for attribute { self ._getFullName ()} \
1129+ raise RuntimeError (f"Cannot set geometry observation for attribute { self ._getFullName ()} \
11911130 observation is incorrect." )
1192- if isinstance (attribute , ShapeAttribute ):
1131+ if isinstance (attribute , GeometryAttribute ):
11931132 attribute .setObservation (key , value )
11941133 else :
11951134 if attribute .keyable :
@@ -1201,17 +1140,17 @@ def setObservation(self, key: str, observation: Variant):
12011140 @Slot (str , result = Variant )
12021141 def getObservation (self , key : str ) -> Variant :
12031142 """
1204- Return the shape attribute observation for the given key.
1143+ Return the geometry attribute observation for the given key.
12051144 Note: Observation is a value defined across all child attributes for a specific key.
12061145 """
12071146 observation = {}
12081147 for attribute in self .value :
1209- if isinstance (attribute , ShapeAttribute ):
1210- shapeObservation = attribute .getObservation (key )
1211- if shapeObservation is None :
1148+ if isinstance (attribute , GeometryAttribute ):
1149+ geoObservation = attribute .getObservation (key )
1150+ if geoObservation is None :
12121151 return None
12131152 else :
1214- observation [attribute .name ] = shapeObservation
1153+ observation [attribute .name ] = geoObservation
12151154 else :
12161155 if attribute .keyable :
12171156 if attribute .keyValues .hasKey (key ):
@@ -1222,43 +1161,135 @@ def getObservation(self, key: str) -> Variant:
12221161 observation [attribute .name ] = attribute .value
12231162 return observation
12241163
1164+ # Properties and signals
1165+ # Emitted when a geometry observation changed.
1166+ observationsChanged = Signal ()
1167+ # Whether the geometry attribute childs are keyable.
1168+ observationKeyable = Property (bool ,_hasKeyableChilds , constant = True )
1169+ # The list of geometry observation keys.
1170+ observationKeys = Property (Variant , _getObservationKeys , notify = observationsChanged )
1171+ # The number of geometry observation defined.
1172+ nbObservations = Property (int , _getNbObservations , notify = observationsChanged )
1173+
1174+
1175+
1176+ class ShapeAttribute (GroupAttribute ):
1177+ """
1178+ GroupAttribute subtype tailored for shape-specific handling.
1179+ """
1180+
1181+ def __init__ (self , node , attributeDesc : desc .Shape , isOutput : bool , root = None , parent = None ):
1182+ super ().__init__ (node , attributeDesc , isOutput , root , parent )
1183+ self ._visible = True
1184+
1185+ # Override
1186+ # Connect geometry attribute valueChanged to emit geometryChanged signal.
1187+ def _initValue (self ):
1188+ super ()._initValue ()
1189+ # Using Attribute.valueChanged for the userName, userColor, geometry properties results in a segmentation fault.
1190+ # As a workaround, we manually connect valueChanged to shapeChanged or geometryChanged.
1191+ self .value .get ("userName" ).valueChanged .connect (self ._onShapeChanged )
1192+ self .value .get ("userColor" ).valueChanged .connect (self ._onShapeChanged )
1193+ self .geometry .valueChanged .connect (self ._onGeometryChanged )
1194+
1195+ # Override
1196+ # Fix missing link expression serialization.
1197+ # Should be remove if link expression serialization is added in GroupAttribute.
1198+ def getSerializedValue (self ):
1199+ if self .isLink :
1200+ return self ._getInputLink ().asLinkExpr ()
1201+ return super ().getSerializedValue ()
1202+
1203+ def getShapeAsDict (self ) -> dict :
1204+ """
1205+ Return the shape attribute as dict with the shape file structure.
1206+ """
1207+ outDict = {
1208+ "name" : self .userName if self .userName else self .rootName ,
1209+ "type" : self .type ,
1210+ "properties" : { "color" : self .userColor }
1211+ }
1212+ if not self .geometry .observationKeyable :
1213+ # Not keyable geometry, use properties.
1214+ outDict .get ("properties" ).update (self .geometry .getSerializedValue ())
1215+ else :
1216+ # Keyable geometry, use observations.
1217+ outDict .update ({ "observations" : self .geometry .getValueAsDict ()})
1218+ return outDict
1219+
1220+ def _getVisible (self ) -> bool :
1221+ """
1222+ Return whether the shape attribute is visible for display.
1223+ """
1224+ return self ._visible
1225+
1226+ def _setVisible (self , visible :bool ):
1227+ """
1228+ Set the shape attribute visibility for display.
1229+ """
1230+ self ._visible = visible
1231+ self .shapeChanged .emit ()
1232+
1233+ def _getUserName (self ) -> str :
1234+ """
1235+ Return the shape attribute user name for display.
1236+ """
1237+ return self .value .get ("userName" ).value
1238+
1239+ def _getUserColor (self ) -> str :
1240+ """
1241+ Return the shape attribute user color for display.
1242+ """
1243+ return self .value .get ("userColor" ).value
1244+
1245+ @Slot ()
1246+ def _onShapeChanged (self ):
1247+ """
1248+ Emit shapeChanged signal.
1249+ Used when shape userName or userColor value changed.
1250+ """
1251+ self .shapeChanged .emit ()
1252+
1253+ @Slot ()
1254+ def _onGeometryChanged (self ):
1255+ """
1256+ Emit geometryChanged signal.
1257+ Used when geometry attribute value changed.
1258+ """
1259+ self .geometryChanged .emit ()
1260+
12251261 # Properties and signals
12261262 # Emitted when a shape related property changed (color, visibility).
12271263 shapeChanged = Signal ()
12281264 # Emitted when a shape observation changed.
1229- observationsChanged = Signal ()
1265+ geometryChanged = Signal ()
12301266 # Whether the shape is displayable.
12311267 isVisible = Property (bool , _getVisible , _setVisible , notify = shapeChanged )
1232- # The shape color for display.
1233- shapeColor = Property (str , _getColor , _setColor , notify = shapeChanged )
1234- # The shape list of observation keys.
1235- observationKeys = Property (Variant , _getObservationKeys , notify = observationsChanged )
1236- # The number of observation defined.
1237- nbObservations = Property (int , _getNbObservations , notify = observationsChanged )
1238- # Whether the shape attribute childs are keyable.
1239- shapeKeyable = Property (bool ,_hasKeyableChilds , constant = True )
1268+ # The shape user name for display.
1269+ userName = Property (str , _getUserName , notify = shapeChanged )
1270+ # The shape user color for display.
1271+ userColor = Property (str , _getUserColor , notify = shapeChanged )
1272+ # The shape geometry group attribute.
1273+ geometry = Property (Variant , lambda self : self .value .get ("geometry" ), notify = geometryChanged )
12401274 # Override hasDisplayableShape property.
12411275 hasDisplayableShape = Property (bool , lambda self : True , constant = True )
1242- # Override value property.
1243- value = Property (Variant , Attribute ._getValue , _setValue , notify = Attribute .valueChanged )
12441276
12451277class ShapeListAttribute (ListAttribute ):
12461278 """
12471279 ListAttribute subtype tailored for shape-specific handling.
12481280 """
12491281
1250- def __init__ (self , node , attributeDesc : desc .ShapeList , isOutput : bool ,
1251- root = None , parent = None ):
1252- self ._visible = True
1282+ def __init__ (self , node , attributeDesc : desc .ShapeList , isOutput : bool , root = None , parent = None ):
12531283 super ().__init__ (node , attributeDesc , isOutput , root , parent )
1284+ self ._visible = True
12541285
1255- def getValuesAsDicts (self ):
1286+ def getGeometriesAsDict (self ):
12561287 """
1257- Return the values of the children of the shape list attribute.
1288+ Return the geometries values of the children of the shape list attribute.
12581289 """
1259- return [shapeAttribute .getValueAsDict () for shapeAttribute in self .value ]
1290+ return [shapeAttribute .geometry . getValueAsDict () for shapeAttribute in self .value ]
12601291
1261- def getShapesAsDicts (self ):
1292+ def getShapesAsDict (self ):
12621293 """
12631294 Return the children of the shape list attribute.
12641295 """
0 commit comments