userdata.py
1 from abc import ABC, abstractmethod 2 from DreamTalk.constants import * 3 import c4d 4 5 6 class UData(ABC): 7 """creates userdata for xpresso setups""" 8 9 def __init__(self, name=None, default_value=None): 10 # create default container for the specified data type 11 self.specify_data_type() 12 # set constraints 13 self.specify_constraints() 14 # set the display name of the element 15 self.specify_name(name) 16 # specify the default value 17 self.default_value = default_value 18 # add attribute for descId 19 self.desc_id = None 20 21 @abstractmethod 22 def specify_data_type(): 23 pass 24 25 def specify_name(self, name): 26 # sets the display name of the element 27 self.name = name # write as attribute 28 self.bc[c4d.DESC_NAME] = self.name 29 30 def __rshift__(self, default_value): 31 """ 32 Inline binding operator for part constructors. 33 34 Usage: 35 Circle(radius=self.size_parameter >> 100) 36 37 This creates a BoundValue that: 38 1. Uses default_value (100) for initial construction 39 2. Creates a binding to this parameter for generator code 40 41 Returns: 42 BoundValue instance 43 """ 44 from DreamTalk.xpresso.bindings import BoundValue, BindingExpression 45 # Create expression that reads this parameter by name 46 expr = BindingExpression( 47 f'get_userdata_by_name(op, "{self.name}")', 48 [self.name] 49 ) 50 return BoundValue(expression=expr, default=default_value, param_name=self.name) 51 52 53 ### data type classes ### 54 55 class UGroup(UData): 56 """creates a user data group element""" 57 58 def __init__(self, *children, target=None, **kwargs): 59 super().__init__(**kwargs) 60 self.target = target 61 self.children = children 62 # insert group 63 self.desc_id = self.target.AddUserData(self.bc) 64 self.insert_children() 65 self.set_default_values() 66 67 def insert_children(self): 68 for child in self.children: 69 # add as child 70 child.bc[c4d.DESC_PARENTGROUP] = self.desc_id 71 # Add the user data element, retrieving its DescId. 72 child.desc_id = self.target.AddUserData(child.bc) 73 74 def specify_data_type(self): 75 # create base container 76 self.bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_GROUP) 77 78 def set_default_values(self): 79 for child in self.children: 80 if child.default_value: 81 self.target[child.desc_id] = child.default_value 82 83 def specify_constraints(self): 84 pass 85 86 87 class UReal(UData): 88 """creates a field of type real""" 89 90 def specify_data_type(self): 91 # create base container 92 self.bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_REAL) 93 self.port_desc_id_in = REAL_DESCID_IN 94 self.port_desc_id_out = REAL_DESCID_OUT 95 self.value_type = float 96 self.bc[c4d.DESC_DEFAULT] = 0 97 98 @abstractmethod 99 def specify_constraints(): 100 pass 101 102 103 class UInt(UData): 104 """creates an integer field""" 105 106 def specify_data_type(self): 107 # create base container 108 self.bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_LONG) 109 self.port_desc_id_in = INTEGER_DESCID_IN 110 self.port_desc_id_out = INTEGER_DESCID_OUT 111 self.value_type = int 112 113 @abstractmethod 114 def specify_constraints(): 115 pass 116 117 118 class UBool(UData): 119 """creates a bool field""" 120 121 def specify_data_type(self): 122 # create base container 123 self.bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_BOOL) 124 self.port_desc_id_in = BOOL_DESCID_IN 125 self.port_desc_id_out = BOOL_DESCID_OUT 126 self.value_type = bool 127 128 def specify_constraints(self): 129 # no constraints needed for bool field 130 pass 131 132 133 class UString(UData): 134 """creates a string field""" 135 136 def specify_data_type(self): 137 # create base container 138 self.bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_STRING) 139 self.port_desc_id_in = STRING_DESCID_IN 140 self.port_desc_id_out = STRING_DESCID_OUT 141 self.value_type = str 142 143 def specify_constraints(self): 144 # no constraints needed for string field 145 pass 146 147 ### concrete classes ### 148 149 150 class UVector(UData): 151 """creates a vector field""" 152 153 def specify_data_type(self): 154 # create base container 155 self.bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_VECTOR) 156 self.port_desc_id_in = None 157 self.port_desc_id_out = None 158 self.value_type = c4d.Vector 159 160 def specify_name(self, name): 161 # sets the display name of the element 162 if name is None: 163 name = "Vector" 164 super().specify_name(name) 165 166 def specify_constraints(self): 167 # no constraints needed for vector field 168 pass 169 170 171 class UColor(UData): 172 """creates a color field""" 173 174 def specify_data_type(self): 175 # create base container 176 self.bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_COLOR) 177 self.port_desc_id_in = COLOR_DESCID_IN 178 self.port_desc_id_out = COLOR_DESCID_OUT 179 self.value_type = c4d.Vector 180 181 def specify_name(self, name): 182 # sets the display name of the element 183 if name is None: 184 name = "Color" 185 super().specify_name(name) 186 187 def specify_constraints(self): 188 # no constraints needed for color field 189 pass 190 191 192 class UCompletion(UReal): 193 """creates a completion field: t -> [0,1]""" 194 195 def specify_constraints(self): 196 # set range 197 self.bc[c4d.DESC_MIN] = 0 198 self.bc[c4d.DESC_MAX] = 1 199 # set unit to percent 200 self.bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_PERCENT 201 # set step size to one percent 202 self.bc[c4d.DESC_STEP] = 0.01 203 # set interface to slider 204 self.bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_REALSLIDER 205 206 def specify_name(self, name): 207 # sets the display name of the element 208 if name is None: 209 name = "Completion" 210 super().specify_name(name) 211 212 213 class UBipolar(UReal): 214 """creates a bipolar field: t -> [-1,1] 215 216 Useful for parameters that can go in both directions, 217 like folding that can flip forward or backward. 218 """ 219 220 def specify_constraints(self): 221 # set range 222 self.bc[c4d.DESC_MIN] = -1 223 self.bc[c4d.DESC_MAX] = 1 224 # set unit to percent 225 self.bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_PERCENT 226 # set step size to one percent 227 self.bc[c4d.DESC_STEP] = 0.01 228 # set interface to slider 229 self.bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_REALSLIDER 230 231 def specify_name(self, name): 232 # sets the display name of the element 233 if name is None: 234 name = "Bipolar" 235 super().specify_name(name) 236 237 238 class UAngle(UReal): 239 """creates an angle field: phi -> [0,2π]""" 240 241 def specify_constraints(self): 242 # set range 243 self.bc[c4d.DESC_MIN] = -2 * PI 244 self.bc[c4d.DESC_MAX] = 2 * PI 245 # set unit to degree 246 self.bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_DEGREE 247 # set step size to one percent 248 self.bc[c4d.DESC_STEP] = 0.01 249 # set interface to slider 250 self.bc[c4d.DESC_CUSTOMGUI] = c4d.CUSTOMGUI_REALSLIDER 251 252 def specify_name(self, name): 253 # sets the display name of the element 254 if name is None: 255 name = "Angle" 256 super().specify_name(name) 257 258 259 class ULength(UReal): 260 """creates a length field: l -> [0,∞)""" 261 262 def specify_constraints(self): 263 # set unit to length 264 self.bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_METER 265 # set step size 266 self.bc[c4d.DESC_STEP] = 0.01 267 268 def specify_name(self, name): 269 # sets the display name of the element 270 if name is None: 271 name = "Length" 272 super().specify_name(name) 273 274 275 class UStrength(UReal): 276 """creates a strength field: s -> [0,∞)""" 277 278 def specify_constraints(self): 279 # set lower bound 280 self.bc[c4d.DESC_MIN] = 0 281 self.bc[c4d.DESC_STEP] = 0.01 282 283 def specify_name(self, name): 284 # sets the display name of the element 285 if name is None: 286 name = "Strength" 287 super().specify_name(name) 288 289 290 class UCount(UInt): 291 """creates a positive number field: n -> N""" 292 293 def specify_constraints(self): 294 # set lower bound 295 self.bc[c4d.DESC_MIN] = 0 296 297 def specify_name(self, name): 298 # sets the display name of the element 299 if name is None: 300 name = "Count" 301 super().specify_name(name) 302 303 304 class UCheckBox(UBool): 305 """creates a check box field: b -> {0,1}""" 306 307 def specify_name(self, name): 308 # sets the display name of the element 309 if name is None: 310 name = "CheckBox" 311 super().specify_name(name) 312 313 314 class UText(UString): 315 """creates a text field: str""" 316 317 def specify_name(self, name): 318 # sets the display name of the element 319 if name is None: 320 name = "text" 321 super().specify_name(name) 322 323 324 class UOptions(UInt): 325 """creates a menu: i -> {options}""" 326 327 def __init__(self, options=[], default_value=None, **kwargs): 328 super().__init__(**kwargs) 329 self.options = options 330 self.specify_options() 331 self.default_value = options.index(default_value) 332 333 def specify_constraints(self): 334 # set interface to quicktab radio 335 self.bc[c4d.DESC_CUSTOMGUI] = 200000281 336 337 def specify_options(self): 338 dropdown_values = c4d.BaseContainer() 339 for i, option in enumerate(self.options): 340 dropdown_values[i] = option 341 self.bc[c4d.DESC_CYCLE] = dropdown_values 342 343 def specify_name(self, name): 344 # sets the display name of the element 345 if name is None: 346 name = "Options" 347 super().specify_name(name) 348 349 350 class UParameter(): 351 """represents an existing parameter to be targeted by an xpression""" 352 353 def __init__(self, target, desc_id, name="Parameter", link_target=None, dtype=None): 354 self.target = target 355 self.desc_id = desc_id 356 self.access_control = None 357 self.name = name 358 self.link_target = link_target 359 self.dtype = dtype