/ xpresso / xpresso.py
xpresso.py
  1  from DreamTalk.constants import *
  2  from c4d.modules.mograph import FieldLayer
  3  import c4d
  4  
  5  
  6  class XNode:
  7      """creates a node inside the xpresso tag of a given target"""
  8  
  9      def __init__(self, target, node_type, parent=None, name=None, custom_tag=False, freeze_tag=False, composition_level=None):
 10          node_types = {
 11              "group": c4d.ID_GV_OPERATOR_GROUP,
 12              "bool": c4d.ID_OPERATOR_BOOL,
 13              "not": c4d.ID_OPERATOR_NOT,
 14              "compare": c4d.ID_OPERATOR_CMP,
 15              "condition": c4d.ID_OPERATOR_CONDITION,
 16              "constant": c4d.ID_OPERATOR_CONST,
 17              "formula": c4d.ID_OPERATOR_FORMULA,
 18              "freeze": c4d.ID_OPERATOR_FREEZE,
 19              "math": c4d.ID_OPERATOR_MATH,
 20              "matrix2vect": c4d.ID_OPERATOR_MATRIX2VECT,
 21              "memory": c4d.ID_OPERATOR_MEMORY,
 22              "object": c4d.ID_OPERATOR_OBJECT,
 23              "python": 1022471,
 24              "rangemapper": c4d.ID_OPERATOR_RANGEMAPPER,
 25              "reals2vect": c4d.ID_OPERATOR_REAL2VECT,
 26              "vect2reals": c4d.ID_OPERATOR_VECT2REAL,
 27              "nearest_point_on_spline": c4d.ID_OPERATOR_NEARESTPOINTONSPLINE,
 28              "mix": c4d.ID_OPERATOR_MIX,
 29              "distance": c4d.ID_OPERATOR_DISTANCE,
 30              "matrix_mul_vector": c4d.ID_OPERATOR_MATRIXMULVECTOR,
 31              "invert": c4d.ID_OPERATOR_INV,
 32              "spline": c4d.ID_OPERATOR_SPLINE,
 33              "matrix2hpb": c4d.ID_OPERATOR_MATRIXCALCHPB,
 34              "vect2matrix": c4d.ID_OPERATOR_VECTCALCMATRIX,
 35              "falloff": 1019302,
 36              "bounding_box": c4d.ID_OPERATOR_BOX
 37          }
 38          # define data types
 39          self.data_types = {
 40              "integer": 0,
 41              "real": 1,
 42              "normal": 2,
 43              "vector": 3,
 44              "color": 3,
 45              "matrix": 4
 46          }
 47          # set attributes
 48          self.target = target
 49          """
 50          # set xtag to animator tag as default
 51          self.xtag = target.animator_tag.obj
 52          if freeze_tag:
 53              self.xtag = target.freeze_tag.obj
 54          if composition_level:
 55              if len(target.composition_tags) < composition_level:
 56                  self.xtag = target.add_composition_tag()
 57              else:
 58                  self.xtag = target.composition_tags[composition_level - 1].obj
 59          """
 60          if True:  # custom_tag:
 61              self.xtag = target.custom_tag.obj
 62          self.master = self.xtag.GetNodeMaster()
 63          # get parent xgroup/root
 64          if parent is None:
 65              parent = self.master.GetRoot()
 66          # create node as child of parent
 67          self.obj = self.master.CreateNode(
 68              parent, id=node_types[node_type])
 69          self.name = name
 70          # set name
 71          if name is not None:
 72              self.obj.SetName(name)
 73          # set optional additional parameters
 74          self.set_params()
 75  
 76      def __repr__(self):
 77          return self.__class__.__name__
 78  
 79      def set_params(self):
 80          """used for setting optional additional parameters"""
 81          pass
 82  
 83  
 84  class XGroup(XNode):
 85      """creates an xgroup containing specified nodes"""
 86  
 87      def __init__(self, *nodes, name=None, inputs_first=False, **kwargs):
 88          target = nodes[0].target  # rip target from first group member
 89          self.inputs_first = inputs_first
 90          super().__init__(target, "group", name=name, **kwargs)  # create group node
 91          self.add(*nodes)
 92  
 93      def add(self, *nodes):
 94          """add node to xgroup"""
 95          for node in nodes:
 96              # insert node under group
 97              self.master.InsertFirst(self.obj, node.obj)
 98  
 99      def set_params(self):
100          # makes sure inputs are updated before being fed into xgroup
101          self.obj[c4d.GV_GROUP_INPUTS_FIRST] = self.inputs_first
102          self.obj[c4d.GV_GROUP_ACTIVE] = True
103  
104  
105  class XObject(XNode):
106      """creates an object node"""
107  
108      def __init__(self, target, link_target=None, **kwargs):
109          self.link_target = link_target
110          super().__init__(target, "object", **kwargs)
111  
112      def set_params(self):
113          if self.link_target is not None:
114              self.obj[c4d.GV_OBJECT_OBJECT_ID] = self.link_target.obj
115  
116  
117  class XCondition(XNode):
118      """creates a condition node"""
119  
120      def __init__(self, target, **kwargs):
121          super().__init__(target, "condition", **kwargs)
122  
123  
124  class XConstant(XNode):
125      """creates a constant node"""
126  
127      def __init__(self, target, value=0, data_type="real", **kwargs):
128          self.value = value
129          self.data_type = data_type
130          super().__init__(target, "constant", **kwargs)
131  
132      def set_params(self):
133          data_types = {
134              "integer": 15,
135              "real": 19,
136              "vector": 23
137          }
138          self.obj[c4d.GV_DYNAMIC_DATATYPE] = data_types[self.data_type]
139          if self.data_type == "vector":
140              self.value = c4d.Vector(*self.value)
141          self.obj[c4d.GV_CONST_VALUE] = self.value  # set value
142  
143  
144  class XCompare(XNode):
145      """creates a compare node"""
146  
147      def __init__(self, target, mode="==", comparison_value=0, **kwargs):
148          self.mode = mode
149          self.comparison_value = comparison_value
150          super().__init__(target, "compare", **kwargs)
151  
152      def set_params(self):
153          modes = {
154              "==": 0,
155              "<": 1,
156              "<=": 2,
157              ">": 3,
158              ">=": 4,
159              "!=": 5,
160          }
161          self.obj[c4d.GV_CMP_FUNCTION] = modes[self.mode]  # set mode
162          # set comparison value
163          self.obj[c4d.GV_CMP_INPUT2] = self.comparison_value
164  
165  
166  class XBool(XNode):
167      """creates a bool node"""
168  
169      def __init__(self, target, mode="AND", **kwargs):
170          self.mode = mode
171          super().__init__(target, "bool", **kwargs)
172  
173      def set_params(self):
174          modes = {
175              "AND": 0,
176              "OR": 1,
177              "XOR": 2,
178              "NAND": 3,
179              "NOR": 4,
180              "NXOR": 5,
181          }
182          self.obj[c4d.GV_BOOL_FUNCTION_ID] = modes[self.mode]  # set mode
183  
184  
185  class XNot(XNode):
186      """creates a NOT node"""
187  
188      def __init__(self, target, **kwargs):
189          super().__init__(target, "not", **kwargs)
190  
191  
192  class XMemory(XNode):
193      """creates a memory node"""
194  
195      def __init__(self, target, history_level=1, history_depth=3, **kwargs):
196          self.history_level = history_level
197          self.history_depth = history_depth
198          super().__init__(target, "memory", **kwargs)
199  
200      def set_params(self):
201          # set history level
202          self.obj[c4d.GV_MEMORY_HISTORY_SWITCH] = self.history_level
203          # set history depth
204          self.obj[c4d.GV_MEMORY_HISTORY_DEPTH] = self.history_depth
205  
206  
207  class XPython(XNode):
208      """creates a python node"""
209  
210      def __init__(self, target, name=None, **kwargs):
211          super().__init__(target, "python", name=name, **kwargs)
212  
213  
214  class XConditionSwitch(XPython):
215  
216      def __init__(self, target, name="ConditionSwitch", **kwargs):
217          super().__init__(target, name=name, **kwargs)
218  
219      def set_params(self):
220          self.obj[c4d.GV_PYTHON_CODE] = 'import c4d\n\ndef main():\n    global Output1\n    Output1 = 0\n    for i in range(op.GetInPortCount()):\n        port = op.GetInPort(i)\n        value = globals()[port.GetName(op)]\n        if value == 1:\n            Output1 = i+1\n            return'
221  
222  
223  class XDelta(XPython):
224      """outputs delta of input values if delta != 0 else outputs 1"""
225  
226      def __init__(self, target, name="Delta", **kwargs):
227          super().__init__(target, name=name, **kwargs)
228  
229      def set_params(self):
230          self.obj[c4d.GV_PYTHON_CODE] = 'import c4d\n\ndef main():\n    global Output1\n    Output1 = 1\n    delta_t = Input1 - Input2\n    if delta_t:\n        Output1 = delta_t'
231  
232  
233  class XProximityConnector(XPython):
234      """connects clones to their nearest clone"""
235  
236      def __init__(self, target, matrix_count=None, name="ProximityConnector", **kwargs):
237          self.matrix_count = matrix_count
238          super().__init__(target, name=name, **kwargs)
239          self.add_parameter_ports()
240          self.add_matrix_ports()
241  
242      def create_matrix_string(self):
243          matrix_string = ""
244          for i in range(self.matrix_count):
245              matrix_string += f"Matrix{i}"
246              if i < self.matrix_count - 1:
247                  matrix_string += ", "
248          return matrix_string
249      
250      def set_params(self):
251          matrix_string = self.create_matrix_string()
252          self.obj[c4d.GV_PYTHON_CODE] = f"from DreamTalk.utils import connect_nearest_clones\n\ndef main() -> None:\n    connect_nearest_clones({matrix_string}, n=NeighbourCount, max_distance=MaxDistance)\n"
253  
254      def add_matrix_ports(self):
255          for i in range(self.matrix_count):
256              new_matrix_port = self.obj.AddPort(
257                  c4d.GV_PORT_INPUT, PYTHON_OBJECT_DESCID_IN)
258              new_matrix_port.SetName(f"Matrix{i}")
259          return new_matrix_port
260  
261      def add_parameter_ports(self):
262          self.obj.RemoveUnusedPorts()
263          self.neighbour_count_port = self.obj.AddPort(
264              c4d.GV_PORT_INPUT, PYTHON_INTEGER_DESCID_IN)
265          self.neighbour_count_port.SetName("NeighbourCount")
266          self.max_distance_port = self.obj.AddPort(
267              c4d.GV_PORT_INPUT, PYTHON_REAL_DESCID_IN)
268          self.max_distance_port.SetName("MaxDistance")
269  
270  class XBBox(XPython):
271      """a more robust python version of the bounding box node"""
272  
273      def __init__(self, target, name="BoundingBox", **kwargs):
274          self.object_port_count = 0
275          super().__init__(target, name=name, **kwargs)
276          self.set_ports()
277  
278      def set_params(self):
279          self.obj[c4d.GV_PYTHON_CODE] = 'from typing import Optional\nimport c4d\n\nop: c4d.modules.graphview.GvNode # The Xpresso node\n\n\nObject: c4d.BaseList2D # In: The object to measure the bounding box for.\nCenter: c4d.Vector # Out: The bounding box center.\nDiameter: c4d.Vector # Out: The bounding box radius.\n\n\ndef get_bounding_box(obj):\n\n    center = obj.GetMp()*obj.GetMg()\n    radius = obj.GetRad()\n    radius = c4d.Vector(abs(radius.x), abs(radius.y), abs(radius.z))\n\n    return center, radius\n\n\ndef recurse_hierarchy(op, callback, local_bboxes):\n# Recurses a hierarchy, starting from op\n    while op:\n        center, radius = callback(op)\n        if radius != c4d.Vector(0,0,0) and not op.GetName() == "MoSpline":\n            local_bbox = (center, radius)\n            local_bboxes.append(local_bbox)\n        local_bboxes = recurse_hierarchy(op.GetDown(), callback, local_bboxes)\n        op = op.GetNext()\n    return local_bboxes\n\n\ndef initial_step(op, callback):\n    # manually does the initial step for the parent of the hierarchy\n    # everything underneath it will be recursively crawled\n    center, radius = callback(op)\n    local_bbox = (center, radius)\n    return local_bbox\n\n\ndef get_global_bounding_box(local_bboxes):\n    """derives the global bounding box from a list of local ones"""\n    xs = []\n    ys = []\n    zs = []\n    for local_bbox in local_bboxes:\n        center, radius = local_bbox\n        max_x = center.x + radius.x\n        min_x = center.x - radius.x\n        max_y = center.y + radius.y\n        min_y = center.y - radius.y\n        max_z = center.z + radius.z\n        min_z = center.z - radius.z\n        xs += [min_x, max_x]\n        ys += [min_y, max_y]\n        zs += [min_z, max_z]\n\n    global_max_x = max(xs)\n    global_min_x = min(xs)\n    global_max_y = max(ys)\n    global_min_y = min(ys)\n    global_max_z = max(zs)\n    global_min_z = min(zs)\n\n    global_width = global_max_x - global_min_x\n    global_height = global_max_y - global_min_y\n    global_depth = global_max_z - global_min_z\n\n    global_center_x = global_min_x + global_width/2\n    global_center_y = global_min_y + global_height/2\n    global_center_z = global_min_z + global_depth/2\n\n    global_diameter = c4d.Vector(global_width, global_height, global_depth)\n    global_center = c4d.Vector(global_center_x, global_center_y, global_center_z)\n\n    return global_center, global_diameter\n\n\ndef main() -> None:\n    global Center, Diameter\n\n    local_bboxes = []\n\n    for port in op.GetInPorts():\n        obj = globals()[port.GetName(op)]\n\n        initial_bbox = initial_step(obj, get_bounding_box)\n        local_bboxes.append(initial_bbox)\n        local_bboxes = recurse_hierarchy(obj.GetDown(), get_bounding_box, local_bboxes)\n\n    global_bbox = get_global_bounding_box(local_bboxes)\n\n    Center, Diameter = global_bbox'
280  
281      def set_ports(self):
282          self.obj.RemoveUnusedPorts()
283          # add diameter port out
284          self.diameter_port_out = self.obj.AddPort(
285              c4d.GV_PORT_OUTPUT, PYTHON_VECTOR_DESCID_OUT)
286          self.diameter_port_out.SetName("Diameter")
287          # add center port out
288          self.center_port_out = self.obj.AddPort(
289              c4d.GV_PORT_OUTPUT, PYTHON_VECTOR_DESCID_OUT)
290          self.center_port_out.SetName("Center")
291  
292      def add_object_port(self):
293          new_object_port = self.obj.AddPort(
294              c4d.GV_PORT_INPUT, PYTHON_OBJECT_DESCID_IN)
295          new_object_port.SetName(f"Object{self.object_port_count}")
296          self.object_port_count += 1
297          return new_object_port
298  
299  
300  class XMax(XPython):
301      """outputs the maximum value of all input values"""
302  
303      def __init__(self, target, name="Max", **kwargs):
304          super().__init__(target, name=name, **kwargs)
305  
306      def set_params(self):
307          self.obj[c4d.GV_PYTHON_CODE] = 'import c4d\n\ndef main():\n    global Output1\n    Output1 = 0\n    values = []\n    for port in op.GetInPorts():\n        value = globals()[port.GetName(op)]\n        values.append(value)\n    Output1 = max(values)'
308  
309  
310  class XMin(XPython):
311      """outputs the minimum value of all input values"""
312  
313      def __init__(self, target, name="Min", **kwargs):
314          super().__init__(target, name=name, **kwargs)
315  
316      def set_params(self):
317          self.obj[c4d.GV_PYTHON_CODE] = 'import c4d\n\ndef main():\n    global Output1\n    Output1 = 0\n    values = []\n    for port in op.GetInPorts():\n        value = globals()[port.GetName(op)]\n        values.append(value)\n    Output1 = min(values)'
318  
319  
320  class XFormula(XNode):
321      """creates a formula node"""
322  
323      def __init__(self, target, variables=["t"], formula="t", **kwargs):
324          self.variables = variables
325          self.formula = "t"
326          if formula:
327              self.formula = formula
328          super().__init__(target, "formula", **kwargs)
329  
330      def set_params(self):
331          # add variables
332          self.variable_ports = {}
333          for variable_name in self.variables:
334              variable_port = self.obj.AddPort(
335                  c4d.GV_PORT_INPUT, VALUE_DESCID_IN)
336              variable_port.SetName(variable_name)
337              self.variable_ports[variable_name] = variable_port
338          # set formula
339          self.obj[c4d.GV_FORMULA_STRING] = self.formula
340          # set options
341          self.obj[c4d.GV_FORMULA_USE_PORTNAMES] = True  # use portnames
342          self.obj[c4d.GV_FORMULA_ANGLE] = 1  # use radians
343  
344  
345  class XRangeMapper(XNode):
346      """creates a range mapper node"""
347  
348      def __init__(self, target, input_range=(0, 1), output_range=(0, 1), easing=False, reverse=False, **kwargs):
349          self.input_range = input_range
350          self.output_range = output_range
351          self.easing = easing
352          self.reverse = reverse
353          super().__init__(target, "rangemapper", **kwargs)
354  
355      def set_params(self):
356          # create spline
357          spline = c4d.SplineData()
358          spline.MakeLinearSplineBezier()
359          # set easing
360          knot_ini, knot_fin = spline.GetKnots()
361          if self.easing is True:
362              spline.SetKnot(0, knot_ini["vPos"], knot_ini["lFlagsSettings"], vTangentLeft=c4d.Vector(
363                  0, 0, 0), vTangentRight=c4d.Vector(0.25, 0, 0))
364              spline.SetKnot(1, knot_fin["vPos"], knot_fin["lFlagsSettings"],
365                             vTangentLeft=c4d.Vector(-0.25, 0, 0), vTangentRight=c4d.Vector(0, 0, 0))
366          elif self.easing == "strong":
367              spline.SetKnot(0, knot_ini["vPos"], knot_ini["lFlagsSettings"], vTangentLeft=c4d.Vector(
368                  0, 0, 0), vTangentRight=c4d.Vector(0.5, 0, 0))
369              spline.SetKnot(1, knot_fin["vPos"], knot_fin["lFlagsSettings"],
370                             vTangentLeft=c4d.Vector(-0.5, 0, 0), vTangentRight=c4d.Vector(0, 0, 0))
371          elif self.easing == "soft":
372              spline.SetKnot(0, knot_ini["vPos"], knot_ini["lFlagsSettings"], vTangentLeft=c4d.Vector(
373                  0, 0, 0), vTangentRight=c4d.Vector(0.125, 0, 0))
374              spline.SetKnot(1, knot_fin["vPos"], knot_fin["lFlagsSettings"],
375                             vTangentLeft=c4d.Vector(-0.125, 0, 0), vTangentRight=c4d.Vector(0, 0, 0))
376          elif self.easing == "in":
377              spline.SetKnot(0, knot_ini["vPos"], knot_ini["lFlagsSettings"], vTangentLeft=c4d.Vector(
378                  0, 0, 0), vTangentRight=c4d.Vector(0.25, 0, 0))
379          elif self.easing == "strong_in":
380              spline.SetKnot(0, knot_ini["vPos"], knot_ini["lFlagsSettings"], vTangentLeft=c4d.Vector(
381                  0, 0, 0), vTangentRight=c4d.Vector(0.5, 0, 0))
382          elif self.easing == "out":
383              spline.SetKnot(1, knot_fin["vPos"], knot_fin["lFlagsSettings"],
384                             vTangentLeft=c4d.Vector(-0.25, 0, 0), vTangentRight=c4d.Vector(0, 0, 0))
385          elif self.easing == "strong_out":
386              spline.SetKnot(1, knot_fin["vPos"], knot_fin["lFlagsSettings"],
387                             vTangentLeft=c4d.Vector(-0.5, 0, 0), vTangentRight=c4d.Vector(0, 0, 0))
388  
389          self.obj[c4d.GV_RANGEMAPPER_SPLINE] = spline
390  
391          # set output range
392          self.obj[c4d.GV_RANGEMAPPER_RANGE21] = self.output_range[0]
393          self.obj[c4d.GV_RANGEMAPPER_RANGE22] = self.output_range[1]
394          # set input range
395          self.obj[c4d.GV_RANGEMAPPER_RANGE11] = self.input_range[0]
396          self.obj[c4d.GV_RANGEMAPPER_RANGE12] = self.input_range[1]
397          # set options
398          self.obj[c4d.GV_RANGEMAPPER_CLAMP_LOWER] = True
399          self.obj[c4d.GV_RANGEMAPPER_CLAMP_UPPER] = True
400          self.obj[c4d.GV_RANGEMAPPER_REVERSE] = self.reverse
401  
402  
403  class XFreeze(XNode):
404      """creates a freeze node"""
405  
406      def __init__(self, target, **kwargs):
407          super().__init__(target, "freeze", **kwargs)
408  
409  
410  class XVec2Reals(XNode):
411      """creates a vect2reals node"""
412  
413      def __init__(self, target, **kwargs):
414          super().__init__(target, "vect2reals", **kwargs)
415  
416  
417  class XReals2Vec(XNode):
418      """creates a reals2vect node"""
419  
420      def __init__(self, target, **kwargs):
421          super().__init__(target, "reals2vect", **kwargs)
422  
423  
424  class XMath(XNode):
425      """creates a math node"""
426  
427      def __init__(self, target, mode="+", data_type="real", **kwargs):
428          self.mode = mode
429          self.data_type = data_type
430          super().__init__(target, "math", **kwargs)
431  
432      def set_params(self):
433          # define modes
434          modes = {
435              "+": 0,
436              "-": 1,
437              "*": 2,
438              "/": 3,
439              "%": 4
440          }
441          # specify mode
442          self.obj[c4d.GV_MATH_FUNCTION_ID] = modes[self.mode]
443          # specify data type
444          data_types = {
445              "integer": 15,
446              "real": 19,
447              "vector": 23
448          }
449          self.obj[c4d.GV_DYNAMIC_DATATYPE] = data_types[self.data_type]
450  
451  
452  class XNearestPointOnSpline(XNode):
453      """creates a nearest point on spline node"""
454  
455      def __init__(self, target, **kwargs):
456          super().__init__(target, "nearest_point_on_spline", **kwargs)
457  
458  
459  class XMix(XNode):
460      """creates a mix node"""
461  
462      def __init__(self, target, mixing_factor=1 / 2, data_type="real", **kwargs):
463          self.mixing_factor = mixing_factor
464          self.data_type = data_type
465          super().__init__(target, "mix", **kwargs)
466  
467      def set_params(self):
468          data_types = {
469              "real": 19,
470              "vector": 23,
471              "matrix": 25,
472              "color": 3
473          }
474          self.obj[c4d.GV_MIX_INPUT_MIXINGFACTOR] = self.mixing_factor
475          self.obj[c4d.GV_DYNAMIC_DATATYPE] = data_types[self.data_type]
476  
477  
478  class XDistance(XNode):
479      """creates a distance node"""
480  
481      def __init__(self, target, **kwargs):
482          super().__init__(target, "distance", **kwargs)
483  
484  
485  class XMatrixMulVector(XNode):
486      """creates a matrix mul vector node"""
487  
488      def __init__(self, target, **kwargs):
489          super().__init__(target, "matrix_mul_vector", **kwargs)
490  
491  
492  class XInvert(XNode):
493      """creates an invert node"""
494  
495      def __init__(self, target, data_type="matrix", **kwargs):
496          self.data_type = data_type
497          super().__init__(target, "invert", **kwargs)
498  
499      def set_params(self):
500          data_types = {
501              "real": 19,
502              "matrix": 25
503          }
504          self.obj[c4d.GV_DYNAMIC_DATATYPE] = data_types[self.data_type]
505  
506  
507  class XSpline(XNode):
508      """creates a spline node"""
509  
510      def __init__(self, target, **kwargs):
511          super().__init__(target, "spline", **kwargs)
512  
513  
514  class XMatrix2HPB(XNode):
515      """creates a matrix to hpb node"""
516  
517      def __init__(self, target, **kwargs):
518          super().__init__(target, "matrix2hpb", **kwargs)
519  
520  
521  class XVect2Matrix(XNode):
522      """creates a vect2matrix node"""
523  
524      def __init__(self, target, **kwargs):
525          super().__init__(target, "vect2matrix", **kwargs)
526  
527  
528  class XFalloff(XNode):
529      """creates a falloff node"""
530  
531      def __init__(self, target, fields=[], **kwargs):
532          self.fields = fields
533          super().__init__(target, "falloff", **kwargs)
534  
535      def set_params(self):
536          # insert fields
537          field_list = c4d.FieldList()
538          for field in self.fields:
539              field_layer = FieldLayer(c4d.FLfield)
540              field_layer.SetLinkedObject(field.obj)
541              field_list.InsertLayer(field_layer)
542          self.obj[c4d.FIELDS] = field_list