/ xpresso / userdata.py
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