/ xpresso / xpressions.py
xpressions.py
   1  from DreamTalk.xpresso.xpresso import *
   2  from DreamTalk.xpresso.userdata import *
   3  from DreamTalk.constants import *
   4  from abc import ABC, abstractmethod
   5  import c4d
   6  
   7  
   8  class XPression(ABC):
   9      """creates xpressions for a given xpresso tag"""
  10  
  11      def __init__(self, target, freeze_tag=False, composition_level=None, name=None):
  12          self.target = target
  13          self.freeze_tag = freeze_tag
  14          self.composition_level = composition_level
  15          self.name = name
  16          self.nodes = []
  17          self.set_name()
  18          self.construct()
  19  
  20      @abstractmethod
  21      def construct():
  22          """analogous to scene class this function constructs the xpression"""
  23          pass
  24  
  25      def set_name(self):
  26          if self.name is None:
  27              self.name = self.__class__.__name__[1:]
  28  
  29  
  30  class XActiveRange(XPression):
  31      """xpression for checking if completion is in active range"""
  32  
  33      def construct(self):
  34          self.create_nodes()
  35          self.group_nodes()
  36          self.create_ports()
  37          self.connect_ports()
  38  
  39      def create_nodes(self):
  40          self.compare_node_0 = XCompare(
  41              self.target, mode="!=", comparison_value=0)
  42          self.compare_node_1 = XCompare(
  43              self.target, mode="!=", comparison_value=1)
  44          self.bool_node = XBool(self.target, mode="AND")
  45  
  46      def group_nodes(self):
  47          self.xgroup = XGroup(self.compare_node_0, self.compare_node_1,
  48                               self.bool_node, name=self.name)
  49          self.obj = self.xgroup.obj
  50  
  51      def create_ports(self):
  52          self.real_interface_in = self.obj.AddPort(
  53              c4d.GV_PORT_INPUT, REAL_DESCID_IN)
  54          self.bool_interface_out = self.obj.AddPort(
  55              c4d.GV_PORT_OUTPUT, BOOL_DESCID_OUT)
  56  
  57      def connect_ports(self):
  58          self.real_interface_in.Connect(self.compare_node_0.obj.GetInPort(0))
  59          self.real_interface_in.Connect(self.compare_node_1.obj.GetInPort(0))
  60          self.compare_node_0.obj.GetOutPort(0).Connect(
  61              self.bool_node.obj.GetInPort(0))
  62          self.compare_node_1.obj.GetOutPort(0).Connect(
  63              self.bool_node.obj.GetInPort(1))
  64          self.bool_node.obj.GetOutPort(0).Connect(self.bool_interface_out)
  65  
  66  
  67  class XNotDescending(XPression):
  68      """xpression for checking if completion is NOT descending"""
  69  
  70      def construct(self):
  71          # create nodes
  72          memory_node = XMemory(self.target)
  73          compare_node = XCompare(self.target, mode=">=")
  74          constant_node = XConstant(self.target, value=1)
  75  
  76          # group nodes
  77          self.xgroup = XGroup(memory_node, compare_node,
  78                               constant_node, name=self.name)
  79          self.obj = self.xgroup.obj
  80  
  81          # create ports
  82          self.real_interface_in = self.obj.AddPort(
  83              c4d.GV_PORT_INPUT, REAL_DESCID_IN)
  84          self.bool_interface_out = self.obj.AddPort(
  85              c4d.GV_PORT_OUTPUT, BOOL_DESCID_OUT)
  86  
  87          # connect ports
  88          self.real_interface_in.Connect(memory_node.obj.GetInPort(1))
  89          self.real_interface_in.Connect(compare_node.obj.GetInPort(0))
  90          memory_node.obj.GetOutPort(0).Connect(compare_node.obj.GetInPort(1))
  91          compare_node.obj.GetOutPort(0).Connect(self.bool_interface_out)
  92          constant_node.obj.GetOutPort(0).Connect(memory_node.obj.GetInPort(0))
  93  
  94  
  95  class XOverrideController(XPression):
  96      """xgroup for checking if parameter should be overridden"""
  97  
  98      def construct(self):
  99          # create nodes
 100          active_range_node = XActiveRange(self.target)
 101          not_descending_node = XNotDescending(self.target)
 102          bool_node = XBool(self.target, mode="AND")
 103  
 104          # group nodes
 105          self.xgroup = XGroup(active_range_node, not_descending_node,
 106                               bool_node, name=self.__class__.__name__[1:])
 107          self.obj = self.xgroup.obj
 108  
 109          # create ports
 110          self.real_interface_in = self.obj.AddPort(
 111              c4d.GV_PORT_INPUT, REAL_DESCID_IN)
 112          self.bool_interface_out = self.obj.AddPort(
 113              c4d.GV_PORT_OUTPUT, BOOL_DESCID_OUT)
 114  
 115          # connect ports
 116          self.real_interface_in.Connect(active_range_node.real_interface_in)
 117          self.real_interface_in.Connect(not_descending_node.real_interface_in)
 118          active_range_node.bool_interface_out.Connect(
 119              bool_node.obj.GetInPort(0))
 120          not_descending_node.bool_interface_out.Connect(
 121              bool_node.obj.GetInPort(1))
 122          bool_node.obj.GetOutPort(0).Connect(self.bool_interface_out)
 123  
 124  
 125  class XInterpolator(XPression):
 126      """xpression for interpolating between two values using completion slider"""
 127  
 128      def construct(self):
 129          # create nodes
 130          self.formula_node = XFormula(self.target, variables=[
 131                                       "t", "ini", "fin"], formula="ini+t*(fin-ini)")
 132  
 133          # group nodes
 134          self.xgroup = XGroup(
 135              self.formula_node, name=self.__class__.__name__[1:])
 136          self.obj = self.xgroup.obj
 137  
 138          # create ports
 139          self.final_value_interface_in = self.obj.AddPort(
 140              c4d.GV_PORT_INPUT, REAL_DESCID_IN)
 141          self.completion_interface_in = self.obj.AddPort(
 142              c4d.GV_PORT_INPUT, REAL_DESCID_IN)
 143          self.output_interface_out = self.obj.AddPort(
 144              c4d.GV_PORT_OUTPUT, REAL_DESCID_OUT)
 145  
 146          # connect ports
 147          self.final_value_interface_in.Connect(
 148              self.formula_node.obj.GetInPort(2))
 149          self.completion_interface_in.Connect(
 150              self.formula_node.obj.GetInPort(0))
 151          self.formula_node.obj.GetOutPort(0).Connect(self.output_interface_out)
 152  
 153      def add_input_source(self, source=None):
 154          # create nodes
 155          object_node = XObject(self.target)
 156  
 157          # group nodes
 158          self.xgroup.add(object_node)
 159  
 160          # create ports
 161          initial_value_port = object_node.obj.AddPort(
 162              c4d.GV_PORT_OUTPUT, source.freeze_value.desc_id)
 163  
 164          # connect ports
 165          initial_value_port.Connect(self.formula_node.obj.GetInPort(1))
 166  
 167  
 168  class XFreezer(XPression):
 169      """freezes the initial value in seperate xtag and writes it to udata for sharing it with main xtag"""
 170  
 171      def construct(self):
 172          # create nodes
 173          memory_node = XMemory(self.target, freeze_tag=self.freeze_tag)
 174          constant_node = XConstant(
 175              self.target, value=1, freeze_tag=self.freeze_tag)
 176          self.override_controller = XOverrideController(
 177              self.target, freeze_tag=self.freeze_tag)
 178          compare_node = XCompare(self.target, mode="<=",
 179                                  freeze_tag=self.freeze_tag)
 180          self.freeze_node = XFreeze(self.target, freeze_tag=self.freeze_tag)
 181  
 182          # group nodes
 183          self.xgroup = XGroup(memory_node, constant_node, self.override_controller, compare_node,
 184                               self.freeze_node, name=self.__class__.__name__[1:], freeze_tag=self.freeze_tag)
 185          self.obj = self.xgroup.obj
 186  
 187          # connect ports
 188          self.override_controller.obj.GetOutPort(
 189              0).Connect(memory_node.obj.GetInPort(1))
 190          self.override_controller.obj.GetOutPort(
 191              0).Connect(compare_node.obj.GetInPort(0))
 192          memory_node.obj.GetOutPort(0).Connect(compare_node.obj.GetInPort(1))
 193          constant_node.obj.GetOutPort(0).Connect(memory_node.obj.GetInPort(0))
 194          compare_node.obj.GetOutPort(0).Connect(
 195              self.freeze_node.obj.GetInPort(0))
 196  
 197      def add_input_source(self, source=None, accessed_parameter=None, reverse_parameter_range=False):
 198          """adds the completion slider and the initial value udata to the freezer"""
 199          # create nodes
 200          object_node_out = XObject(self.target, freeze_tag=self.freeze_tag)
 201          object_node_in = XObject(self.target, freeze_tag=self.freeze_tag)
 202          accessed_parameter_node_out = XObject(
 203              self.target, link_target=accessed_parameter.link_target, freeze_tag=self.freeze_tag)
 204          if reverse_parameter_range:
 205              range_mapper_node = XRangeMapper(self.target, reverse=True)
 206              self.xgroup.add(range_mapper_node)
 207  
 208          # group nodes
 209          self.xgroup.add(object_node_out, object_node_in,
 210                          accessed_parameter_node_out)
 211  
 212          # create ports
 213          completion_port_out = object_node_out.obj.AddPort(
 214              c4d.GV_PORT_OUTPUT, source.completion_slider.desc_id)
 215          accessed_parameter_port_out = accessed_parameter_node_out.obj.AddPort(
 216              c4d.GV_PORT_OUTPUT, accessed_parameter.desc_id)
 217          freeze_value_port_in = object_node_in.obj.AddPort(
 218              c4d.GV_PORT_INPUT, source.freeze_value.desc_id)
 219  
 220          # connect ports
 221          completion_port_out.Connect(self.override_controller.real_interface_in)
 222          self.freeze_node.obj.GetOutPort(0).Connect(freeze_value_port_in)
 223          if reverse_parameter_range:
 224              accessed_parameter_port_out.Connect(
 225                  range_mapper_node.obj.GetInPort(0))
 226              range_mapper_node.obj.GetOutPort(0).Connect(
 227                  self.freeze_node.obj.GetInPort(1))
 228          else:
 229              accessed_parameter_port_out.Connect(
 230                  self.freeze_node.obj.GetInPort(1))
 231  
 232  
 233  class XAnimator(XPression):
 234      """template for generic xanimator that drives single parameter using a given formula"""
 235  
 236      def __init__(self, target, formula=None, variables=["t"], name=None, params=[], interpolate=False, composition_level=None):
 237          self.target = target
 238          self.obj_target = target.obj
 239          self.formula = formula
 240          self.variables = variables
 241          self.name = name
 242          self.udatas = [param[0] for param in params]  # get udata of parameters
 243          self.param_names = [param[1]
 244                              for param in params]  # get names of parameters
 245          # stores udatas and interpolation_target for animation
 246          self.animation_parameters = []
 247          self.interpolate = interpolate
 248          self.access_control = None
 249          self.composition_level = composition_level
 250          super().__init__(target, composition_level=self.composition_level)
 251          self.create_mapping()  # creates the mapping
 252  
 253      def construct(self):
 254          # create userdata
 255          uparams = []
 256          # completion slider
 257          self.completion_slider = UCompletion()
 258          uparams.append(self.completion_slider)
 259          # instantiate parameters
 260          for i, udata in enumerate(self.udatas):
 261              self.udatas[i] = udata(name=self.param_names[i])
 262          uparams += self.udatas
 263          # add to animation parameters
 264          self.animation_parameters = self.udatas
 265          # add interpolation target
 266          if self.interpolate:
 267              self.interpolation_target = UStrength(name="interpolation_target")
 268              self.freeze_value = UStrength(name="freeze_value")
 269              uparams += [self.interpolation_target, self.freeze_value]
 270              # add to animation parameters
 271              self.animation_parameters.append(self.interpolation_target)
 272          u_group = UGroup(*uparams, target=self.obj_target, name=self.name)
 273  
 274          # create nodes
 275          self.object_node = XObject(
 276              self.target, composition_level=self.composition_level)
 277          override_controller = XOverrideController(
 278              self.target, composition_level=self.composition_level)
 279  
 280          # group nodes
 281          self.xgroup = XGroup(self.object_node, override_controller, name=self.name + self.__class__.__name__[
 282                               1:], composition_level=self.composition_level)  # rip name from class name
 283          self.obj = self.xgroup.obj
 284  
 285          # create ports
 286          self.active_interface_out = self.obj.AddPort(
 287              c4d.GV_PORT_OUTPUT, BOOL_DESCID_OUT)
 288          self.completion_port_out = self.object_node.obj.AddPort(
 289              c4d.GV_PORT_OUTPUT, self.completion_slider.desc_id)
 290          if self.interpolate:
 291              self.final_interface_out = self.obj.AddPort(
 292                  c4d.GV_PORT_OUTPUT, REAL_DESCID_OUT)
 293              strength_port_out = self.object_node.obj.AddPort(
 294                  c4d.GV_PORT_OUTPUT, self.interpolation_target.desc_id)
 295  
 296          # connect ports
 297          self.completion_port_out.Connect(override_controller.real_interface_in)
 298          override_controller.bool_interface_out.Connect(
 299              self.active_interface_out)
 300          if self.interpolate:
 301              strength_port_out.Connect(self.final_interface_out)
 302  
 303      def create_mapping(self):
 304          """creates a mapping using the formula node"""
 305          # create nodes
 306          if self.interpolate:
 307              # this formula ensures that the output value is 1 at frame = frame_fin - 1
 308              self.formula = "ceil(t*round(1/(delta_t))*(1+delta_t))/round(1/(delta_t))"
 309              self.variables = ["t", "delta_t"]
 310              memory_node = XMemory(self.target)
 311              constant_node = XConstant(self.target, value=1)
 312              delta_node = XDelta(self.target)
 313          formula_node = XFormula(
 314              self.target, variables=self.variables, formula=self.formula)
 315  
 316          # group nodes
 317          if self.interpolate:
 318              self.xgroup.add(memory_node, constant_node, delta_node)
 319          self.xgroup.add(formula_node)
 320  
 321          # create ports
 322          self.driver_interface_out = self.obj.AddPort(
 323              c4d.GV_PORT_OUTPUT, REAL_DESCID_OUT)
 324          param_ports_out = []
 325          param_ports_in = []
 326          driver_port_out = formula_node.obj.GetOutPort(0)
 327          for udata, param_name in zip(self.udatas, self.param_names):
 328              param_port_out = self.object_node.obj.AddPort(
 329                  c4d.GV_PORT_OUTPUT, udata.desc_id)
 330              param_ports_out.append(param_port_out)
 331              param_port_in = formula_node.obj.AddPort(
 332                  c4d.GV_PORT_INPUT, VALUE_DESCID_IN)
 333              param_port_in.SetName(param_name)
 334              param_ports_in.append(param_port_in)
 335  
 336          # connect ports
 337          if self.interpolate:
 338              self.completion_port_out.Connect(memory_node.obj.GetInPort(1))
 339              constant_node.obj.GetOutPort(0).Connect(
 340                  memory_node.obj.GetInPort(0))
 341              memory_node.obj.GetOutPort(0).Connect(delta_node.obj.GetInPort(1))
 342              self.completion_port_out.Connect(delta_node.obj.GetInPort(0))
 343              delta_node.obj.GetOutPort(0).Connect(
 344                  formula_node.variable_ports["delta_t"])
 345          self.completion_port_out.Connect(formula_node.variable_ports["t"])
 346          driver_port_out.Connect(self.driver_interface_out)
 347          for param_port_in, param_port_out in zip(param_ports_in, param_ports_out):
 348              param_port_out.Connect(param_port_in)
 349  
 350  
 351  class XComposer(XAnimator):
 352      """special kind of xanimator used for compositions, uses multiple range mappers instead of formula"""
 353  
 354      def create_mapping(self):
 355          """not used for xcomposer"""
 356          pass
 357  
 358      def add_range_mapping(self, input_range):
 359          """adds a range mapper node and an out port to the xpression"""
 360          # create nodes
 361          # this formula ensures that the output value is 1 at frame = frame_fin - 1
 362          formula = "ceil(t*round(1/(delta_t))*(1+delta_t))/round(1/(delta_t))"
 363          formula_node = XFormula(self.target, variables=[
 364                                  "t", "delta_t"], formula=formula, composition_level=self.composition_level)
 365          constant_node = XConstant(
 366              self.target, value=1, composition_level=self.composition_level)
 367          memory_node = XMemory(
 368              self.target, composition_level=self.composition_level)
 369          range_mapper_node = XRangeMapper(
 370              self.target, input_range=input_range, composition_level=self.composition_level)
 371          delta_node = XDelta(
 372              self.target, composition_level=self.composition_level)
 373  
 374          # group nodes
 375          self.xgroup.add(formula_node, constant_node,
 376                          memory_node, range_mapper_node, delta_node)
 377  
 378          # create ports
 379          self.driver_interface_out = self.obj.AddPort(
 380              c4d.GV_PORT_OUTPUT, REAL_DESCID_OUT)
 381  
 382          # connect ports
 383          self.completion_port_out.Connect(formula_node.variable_ports["t"])
 384          self.completion_port_out.Connect(delta_node.obj.GetInPort(0))
 385          self.completion_port_out.Connect(memory_node.obj.GetInPort(1))
 386          constant_node.obj.GetOutPort(0).Connect(memory_node.obj.GetInPort(0))
 387          memory_node.obj.GetOutPort(0).Connect(delta_node.obj.GetInPort(1))
 388          formula_node.obj.GetOutPort(0).Connect(
 389              range_mapper_node.obj.GetInPort(0))
 390          range_mapper_node.obj.GetOutPort(0).Connect(self.driver_interface_out)
 391          delta_node.obj.GetOutPort(0).Connect(
 392              formula_node.variable_ports["delta_t"])
 393  
 394  
 395  class XAccessControl(XPression):
 396      """xgroup for handling which input should override given parameter"""
 397  
 398      def __init__(self, target, parameter=None, link_target=None, reverse_parameter_range=False, composition_level=None):
 399          # input counter for adding input sources
 400          self.input_count = 0
 401          # specify link target
 402          self.link_target = link_target
 403          # specify if range mapper should be inserted before parameter
 404          self.reverse_parameter_range = reverse_parameter_range
 405          # specify to which xtag of the composition hierarchy the xpression should be applied
 406          self.composition_level = composition_level
 407          # check for xanimator
 408          if type(parameter) in (XAnimator, XComposer):
 409              xanimator = parameter  # is xanimator
 410              self.parameter = xanimator.completion_slider
 411              self.name = xanimator.name
 412          elif type(parameter) is UParameter:
 413              self.parameter = parameter
 414              self.name = parameter.name
 415              target = parameter.target
 416          else:
 417              raise TypeError(
 418                  "parameter must be of type XAnimator, XComposer or UParameter!")
 419          super().__init__(target, composition_level=self.composition_level)
 420  
 421      def construct(self):
 422          # create nodes
 423          self.condition_switch_node = XConditionSwitch(
 424              self.target, composition_level=self.composition_level)
 425          self.condition_node = XCondition(
 426              self.target, composition_level=self.composition_level)
 427          object_node_out = XObject(
 428              self.target, link_target=self.link_target, composition_level=self.composition_level)
 429          object_node_in = XObject(
 430              self.target, link_target=self.link_target, composition_level=self.composition_level)
 431          nodes = [self.condition_switch_node,
 432                   self.condition_node, object_node_out, object_node_in]
 433          if self.reverse_parameter_range:
 434              self.range_mapper_node_in = XRangeMapper(
 435                  self.target, reverse=True, composition_level=self.composition_level)
 436              self.range_mapper_node_out = XRangeMapper(
 437                  self.target, reverse=True, composition_level=self.composition_level)
 438              optional_nodes = [self.range_mapper_node_in,
 439                                self.range_mapper_node_out]
 440              nodes += optional_nodes
 441  
 442          # group nodes
 443          self.xgroup = XGroup(*nodes, name=self.name + "AccessControl",
 444                               composition_level=self.composition_level)
 445          self.obj = self.xgroup.obj
 446  
 447          # create ports
 448          self.active_interfaces_in = []
 449          self.driver_interfaces_in = []
 450          self.parameter_port_out = object_node_out.obj.AddPort(
 451              c4d.GV_PORT_OUTPUT, self.parameter.desc_id)
 452          self.parameter_port_in = object_node_in.obj.AddPort(
 453              c4d.GV_PORT_INPUT, self.parameter.desc_id)
 454  
 455          # connect ports
 456          self.condition_switch_node.obj.GetOutPort(
 457              0).Connect(self.condition_node.obj.GetInPort(0))
 458          self.parameter_port_out.Connect(self.condition_node.obj.GetInPort(1))
 459          self.condition_node.obj.GetOutPort(0).Connect(self.parameter_port_in)
 460          if self.reverse_parameter_range:
 461              self.range_mapper_node_in.obj.GetOutPort(
 462                  0).Connect(self.condition_node.obj.GetInPort(1))
 463              self.range_mapper_node_out.obj.GetOutPort(
 464                  0).Connect(self.parameter_port_in)
 465              self.parameter_port_out.Connect(
 466                  self.range_mapper_node_in.obj.GetInPort(0))
 467              self.condition_node.obj.GetOutPort(0).Connect(
 468                  self.range_mapper_node_out.obj.GetInPort(0))
 469  
 470          # remove unused ports
 471          self.condition_switch_node.obj.RemoveUnusedPorts()
 472          self.condition_node.obj.RemoveUnusedPorts()
 473  
 474          # name ports
 475          self.condition_node.obj.GetInPort(1).SetName("Idle")
 476  
 477      def add_input_source(self, source, interpolate=False):
 478          """adds and connects a bool input and a real input to a given input source"""
 479          # update input count
 480          self.input_count += 1
 481  
 482          # create ports
 483          self.active_interfaces_in.append(
 484              self.obj.AddPort(c4d.GV_PORT_INPUT, BOOL_DESCID_IN))
 485          self.driver_interfaces_in.append(
 486              self.obj.AddPort(c4d.GV_PORT_INPUT, REAL_DESCID_IN))
 487          new_condition_switch_port_in = self.condition_switch_node.obj.AddPort(
 488              c4d.GV_PORT_INPUT, CONDITION_SWITCH_DESCID_IN)
 489          new_condition_port_in = self.condition_node.obj.AddPort(
 490              c4d.GV_PORT_INPUT, CONDITION_DESCID_IN)
 491  
 492          # connect ports
 493          # interior
 494          self.active_interfaces_in[-1].Connect(new_condition_switch_port_in)
 495          self.driver_interfaces_in[-1].Connect(new_condition_port_in)
 496          # exterior
 497          source.active_interface_out.Connect(self.active_interfaces_in[-1])
 498          source.driver_interface_out.Connect(self.driver_interfaces_in[-1])
 499  
 500          # name ports
 501          new_condition_switch_port_in.SetName("Input" + str(self.input_count))
 502          new_condition_port_in.SetName("Input" + str(self.input_count))
 503  
 504          # optionally interpose interpolator
 505          if interpolate:
 506              self.interpose_interpolator(
 507                  source, self.driver_interfaces_in[-1], new_condition_port_in)
 508  
 509      def interpose_interpolator(self, source, completion_source, output_target):
 510          """interposes an interpolator for linear interpolation between initial and final value of target parameter"""
 511          # create nodes
 512          interpolator = XInterpolator(
 513              self.target, composition_level=self.composition_level)
 514          interpolator.add_input_source(source=source)
 515          freezer = XFreezer(self.target, freeze_tag=True)
 516          freezer.add_input_source(source=source, accessed_parameter=self.parameter,
 517                                   reverse_parameter_range=self.reverse_parameter_range)
 518  
 519          # group nodes
 520          self.xgroup.add(interpolator)
 521  
 522          # create ports
 523          final_source = self.obj.AddPort(c4d.GV_PORT_INPUT, REAL_DESCID_IN)
 524  
 525          # connect ports
 526          final_source.Connect(interpolator.final_value_interface_in)
 527          source.final_interface_out.Connect(final_source)
 528          completion_source.Connect(interpolator.completion_interface_in)
 529          interpolator.output_interface_out.Connect(output_target)
 530  
 531  
 532  class XComposition(XPression):
 533      """template for generic composed xanimator"""
 534  
 535      def __init__(self, *xanimator_tuples, target=None, name=None, composition_mode=False, composition_level=1):
 536          self.xanimator_tuples = xanimator_tuples
 537          self.target = target
 538          self.obj_target = target.obj
 539          self.name = name
 540          self.composition_mode = composition_mode
 541          self.composition_level = composition_level
 542          super().__init__(target, composition_level=self.composition_level)
 543  
 544      def construct(self):
 545          # unpack tuples
 546          self.xanimators = [xanimator_tuple[0]
 547                             for xanimator_tuple in self.xanimator_tuples]
 548          input_ranges = [xanimator_tuple[1]
 549                          for xanimator_tuple in self.xanimator_tuples]
 550          # create xcomposer
 551          self.xcomposer = XComposer(
 552              self.target, name=self.name, composition_level=self.composition_level)
 553          self.completion_slider = self.xcomposer.completion_slider
 554          # create access controls
 555          for xanimator, input_range in zip(self.xanimators, input_ranges):
 556              if xanimator.access_control is None:  # add access control if needed
 557                  xanimator.access_control = XAccessControl(
 558                      self.target, parameter=xanimator, composition_level=self.composition_level)
 559              self.xcomposer.add_range_mapping(input_range)
 560              xanimator.access_control.add_input_source(self.xcomposer)
 561              # remember animation parameters of xanimators in case of composition mode
 562              if self.composition_mode:
 563                  self.xcomposer.animation_parameters += xanimator.animation_parameters
 564  
 565  
 566  class XAnimation(XPression):
 567      """connects xanimators to layer zero parameter"""
 568  
 569      def __init__(self, *xanimators, target=None, parameter=None, name=None, reverse_parameter_range=False):
 570          self.target = target
 571          self.xanimators = xanimators
 572          self.parameter = parameter
 573          self.obj_target = target.obj
 574          self.name = name
 575          self.reverse_parameter_range = reverse_parameter_range
 576          super().__init__(target)
 577  
 578      def construct(self):
 579          # create access controls if needed
 580          for xanimator in self.xanimators:
 581              if self.parameter.access_control is None:
 582                  self.parameter.access_control = XAccessControl(
 583                      self.target, parameter=self.parameter, link_target=self.parameter.link_target, reverse_parameter_range=self.reverse_parameter_range)
 584              self.parameter.access_control.add_input_source(
 585                  xanimator, interpolate=xanimator.interpolate)
 586  
 587  
 588  class CustomXPression(XPression):
 589  
 590      def __init__(self, target, priority=0, parent=None, **kwargs):
 591          self.priority = priority
 592          self.parent = parent
 593          super().__init__(target, **kwargs)
 594          self.group_nodes()
 595          self.connect_ports()
 596  
 597      def group_nodes(self):
 598          self.xgroup = XGroup(*self.nodes, custom_tag=True,
 599                               name=self.name)
 600          self.obj = self.xgroup.obj
 601  
 602  
 603  class XRelation(CustomXPression):
 604      """creates a relation between a parameter of a part and the whole of a CustomObject"""
 605  
 606      def __init__(self, part=None, whole=None, desc_ids=[], parameters=None, formula=None, **kwargs):
 607          self.formula = formula
 608          self.desc_ids = desc_ids
 609          self.parameters = parameters
 610          self.part = part
 611          self.whole = whole
 612          super().__init__(self.whole, **kwargs)
 613  
 614      def construct(self):
 615          self.create_whole_node()
 616          self.create_part_node()
 617          self.create_formula_node()
 618  
 619      def create_part_node(self):
 620          self.part_node = XObject(self.whole, link_target=self.part)
 621          for desc_id in self.desc_ids:
 622              self.part_node.obj.AddPort(c4d.GV_PORT_INPUT, desc_id)
 623          self.nodes.append(self.part_node)
 624  
 625      def create_whole_node(self):
 626          self.whole_node = XObject(self.whole)
 627          for parameter in self.parameters:
 628              self.whole_node.obj.AddPort(c4d.GV_PORT_OUTPUT, parameter.desc_id)
 629          self.nodes.append(self.whole_node)
 630  
 631      def create_formula_node(self):
 632          self.formula_node = XFormula(
 633              self.whole, variables=[parameter.name for parameter in self.parameters], formula=self.formula)
 634          self.nodes.append(self.formula_node)
 635  
 636      def connect_ports(self):
 637          for formula_in_port, parameter_port_out in zip(self.formula_node.obj.GetInPorts(), self.whole_node.obj.GetOutPorts()):
 638              parameter_port_out.Connect(formula_in_port)
 639          for part_node_port in self.part_node.obj.GetInPorts():
 640              self.formula_node.obj.GetOutPort(0).Connect(part_node_port)
 641  
 642  
 643  class XIdentity(XRelation):
 644      """creates a direct connection between a parameter of a part and the whole of a CustomObject"""
 645  
 646      def __init__(self, parameter=None, **kwargs):
 647          super().__init__(parameters=[parameter], **kwargs)
 648  
 649      def connect_ports(self):
 650          self.whole_node.obj.GetOutPort(0).Connect(
 651              self.part_node.obj.GetInPort(0))
 652  
 653      def construct(self):
 654          self.create_whole_node()
 655          self.create_part_node()
 656  
 657  
 658  class XInheritPosition(object):
 659      """relates the """
 660  
 661      def __init__(self, arg):
 662          super(XInheritPosition, self).__init__()
 663          self.arg = arg
 664  
 665  
 666  class XClosestPointOnSpline(CustomXPression):
 667      """creates a setup that positions a point on a spline such that the distance to a reference point is minimised"""
 668  
 669      def __init__(self, reference_point=None, spline_point=None, target=None, spline=None, matrices=False, **kwargs):
 670          self.spline = spline
 671          self.spline_point = spline_point
 672          self.reference_point = reference_point
 673          self.matrices = matrices
 674          super().__init__(target, **kwargs)
 675  
 676      def construct(self):
 677          self.create_spline_node()
 678          self.create_reference_point_node()
 679          self.create_spline_point_node()
 680          self.create_nearest_point_on_spline_node()
 681          if self.matrices:
 682              self.create_matrix_nodes()
 683  
 684      def create_spline_node(self):
 685          self.spline_node = XObject(self.target, link_target=self.spline)
 686          self.spline_node.obj.AddPort(
 687              c4d.GV_PORT_OUTPUT, c4d.GV_OBJECT_OPERATOR_OBJECT_OUT)
 688          self.spline_node.obj.AddPort(
 689              c4d.GV_PORT_OUTPUT, c4d.GV_OBJECT_OPERATOR_GLOBAL_OUT)
 690          self.nodes.append(self.spline_node)
 691  
 692      def create_reference_point_node(self):
 693          self.reference_point_node = XObject(
 694              self.target, link_target=self.reference_point)
 695          self.reference_point_node.obj.AddPort(
 696              c4d.GV_PORT_OUTPUT, c4d.ID_BASEOBJECT_GLOBAL_POSITION)
 697          self.nodes.append(self.reference_point_node)
 698  
 699      def create_spline_point_node(self):
 700          self.spline_point_node = XObject(
 701              self.target, link_target=self.spline_point)
 702          self.spline_point_node.obj.AddPort(
 703              c4d.GV_PORT_INPUT, c4d.ID_BASEOBJECT_GLOBAL_POSITION)
 704          self.nodes.append(self.spline_point_node)
 705  
 706      def create_nearest_point_on_spline_node(self):
 707          self.nearest_point_on_spline_node = XNearestPointOnSpline(self.target)
 708          self.nodes.append(self.nearest_point_on_spline_node)
 709  
 710      def create_matrix_nodes(self):
 711          self.left_matrix_node = XMatrixMulVector(self.target)
 712          self.right_matrix_node = XMatrixMulVector(self.target)
 713          self.invert_matrix_node = XInvert(self.target, data_type="matrix")
 714          self.nodes += [self.left_matrix_node,
 715                         self.right_matrix_node, self.invert_matrix_node]
 716  
 717      def connect_ports(self):
 718          if self.matrices:
 719              self.spline_node.obj.GetOutPort(0).Connect(
 720                  self.nearest_point_on_spline_node.obj.GetInPort(0))
 721              self.spline_node.obj.GetOutPort(1).Connect(
 722                  self.invert_matrix_node.obj.GetInPort(0))
 723              self.invert_matrix_node.obj.GetOutPort(0).Connect(
 724                  self.left_matrix_node.obj.GetInPort(0))
 725              self.spline_node.obj.GetOutPort(1).Connect(
 726                  self.right_matrix_node.obj.GetInPort(0))
 727              self.reference_point_node.obj.GetOutPort(0).Connect(
 728                  self.left_matrix_node.obj.GetInPort(1))
 729              self.nearest_point_on_spline_node.obj.GetOutPort(1).Connect(
 730                  self.right_matrix_node.obj.GetInPort(1))
 731              self.left_matrix_node.obj.GetOutPort(0).Connect(
 732                  self.nearest_point_on_spline_node.obj.GetInPort(1))
 733              self.right_matrix_node.obj.GetOutPort(0).Connect(
 734                  self.spline_point_node.obj.GetInPort(0))
 735          else:
 736              self.spline_node.obj.GetOutPort(0).Connect(
 737                  self.nearest_point_on_spline_node.obj.GetInPort(0))
 738              self.reference_point_node.obj.GetOutPort(0).Connect(
 739                  self.nearest_point_on_spline_node.obj.GetInPort(1))
 740              self.nearest_point_on_spline_node.obj.GetOutPort(1).Connect(
 741                  self.spline_point_node.obj.GetInPort(0))
 742  
 743  
 744  class XScaleBetweenPoints(CustomXPression):
 745      """creates a setup that scales and positions an object such that it touches two points on its periphery"""
 746  
 747      def __init__(self, scaled_object=None, point_a=None, point_b=None, target=None, **kwargs):
 748          self.scaled_object = scaled_object
 749          self.point_a = point_a
 750          self.point_b = point_b
 751          super().__init__(target, **kwargs)
 752  
 753      def construct(self):
 754          self.create_distance_node()
 755          self.create_mix_node()
 756          self.create_divide_node()
 757          self.create_point_nodes()
 758          self.create_scaled_object_node()
 759  
 760      def create_distance_node(self):
 761          self.distance_node = XDistance(self.target)
 762          self.nodes.append(self.distance_node)
 763  
 764      def create_mix_node(self):
 765          self.mix_node = XMix(self.target, data_type="vector")
 766          self.nodes.append(self.mix_node)
 767  
 768      def create_divide_node(self):
 769          self.divide_node = XMath(self.target, mode="/")
 770          self.constant_node = XConstant(self.target, value=2)
 771          self.nodes += [self.divide_node, self.constant_node]
 772  
 773      def create_point_nodes(self):
 774          self.point_a_node = XObject(self.target, link_target=self.point_a)
 775          self.point_a_node.obj.AddPort(
 776              c4d.GV_PORT_OUTPUT, c4d.ID_BASEOBJECT_GLOBAL_POSITION)
 777          self.point_b_node = XObject(self.target, link_target=self.point_b)
 778          self.point_b_node.obj.AddPort(
 779              c4d.GV_PORT_OUTPUT, c4d.ID_BASEOBJECT_GLOBAL_POSITION)
 780          self.nodes += [self.point_a_node, self.point_b_node]
 781  
 782      def create_scaled_object_node(self):
 783          self.scaled_object_node = XObject(
 784              self.target, link_target=self.scaled_object)
 785          self.scaled_object_node_global_position_port = self.scaled_object_node.obj.AddPort(
 786              c4d.GV_PORT_INPUT, c4d.ID_BASEOBJECT_GLOBAL_POSITION)
 787          self.scaled_object_node_size_port = self.scaled_object_node.obj.AddPort(
 788              c4d.GV_PORT_INPUT, c4d.SPHERICAL_SIZE)
 789          self.nodes.append(self.scaled_object_node)
 790  
 791      def connect_ports(self):
 792          self.point_a_node.obj.GetOutPort(0).Connect(
 793              self.distance_node.obj.GetInPort(0))
 794          self.point_b_node.obj.GetOutPort(0).Connect(
 795              self.distance_node.obj.GetInPort(1))
 796          self.point_a_node.obj.GetOutPort(0).Connect(
 797              self.mix_node.obj.GetInPort(1))
 798          self.point_b_node.obj.GetOutPort(0).Connect(
 799              self.mix_node.obj.GetInPort(2))
 800          self.distance_node.obj.GetOutPort(0).Connect(
 801              self.divide_node.obj.GetInPort(0))
 802          self.divide_node.obj.GetOutPort(0).Connect(
 803              self.scaled_object_node_size_port)
 804          self.mix_node.obj.GetOutPort(0).Connect(
 805              self.scaled_object_node_global_position_port)
 806          self.constant_node.obj.GetOutPort(0).Connect(
 807              self.divide_node.obj.GetInPort(1))
 808  
 809  
 810  class XPlaceBetweenPoints(CustomXPression):
 811      """creates a setup that scales and positions an object such that it touches two points on its periphery"""
 812  
 813      def __init__(self, placed_object=None, point_a=None, point_b=None, interpolation_parameter=None, target=None, **kwargs):
 814          self.placed_object = placed_object
 815          self.point_a = point_a
 816          self.point_b = point_b
 817          self.interpolation_parameter = interpolation_parameter
 818          super().__init__(target, **kwargs)
 819  
 820      def construct(self):
 821          self.create_mix_node()
 822          self.create_point_nodes()
 823          self.create_placed_object_node()
 824          self.create_parameter_node()
 825  
 826      def create_mix_node(self):
 827          self.mix_node = XMix(self.target, data_type="matrix")
 828          self.nodes.append(self.mix_node)
 829  
 830      def create_point_nodes(self):
 831          self.point_a_node = XObject(self.target, link_target=self.point_a)
 832          self.point_a_node.obj.AddPort(
 833              c4d.GV_PORT_OUTPUT, c4d.GV_OBJECT_OPERATOR_GLOBAL_OUT)
 834          self.point_b_node = XObject(self.target, link_target=self.point_b)
 835          self.point_b_node.obj.AddPort(
 836              c4d.GV_PORT_OUTPUT, c4d.GV_OBJECT_OPERATOR_GLOBAL_OUT)
 837          self.nodes += [self.point_a_node, self.point_b_node]
 838  
 839      def create_placed_object_node(self):
 840          self.placed_object_node = XObject(
 841              self.target, link_target=self.placed_object)
 842          self.placed_object_node_global_matrix_port = self.placed_object_node.obj.AddPort(
 843              c4d.GV_PORT_INPUT, c4d.GV_OBJECT_OPERATOR_GLOBAL_IN)
 844          self.nodes.append(self.placed_object_node)
 845  
 846      def create_parameter_node(self):
 847          self.parameter_node = XObject(self.target)
 848          self.parameter_port = self.parameter_node.obj.AddPort(
 849              c4d.GV_PORT_OUTPUT, self.interpolation_parameter.desc_id)
 850          self.nodes.append(self.parameter_node)
 851  
 852      def connect_ports(self):
 853          self.point_a_node.obj.GetOutPort(0).Connect(
 854              self.mix_node.obj.GetInPort(1))
 855          self.point_b_node.obj.GetOutPort(0).Connect(
 856              self.mix_node.obj.GetInPort(2))
 857          self.mix_node.obj.GetOutPort(0).Connect(
 858              self.placed_object_node_global_matrix_port)
 859          self.parameter_port.Connect(self.mix_node.obj.GetInPort(0))
 860  
 861  
 862  class XSplineLength(CustomXPression):
 863      """writes the length of a spline to a specified parameter"""
 864  
 865      def __init__(self, spline=None, whole=None, parameter=None, **kwargs):
 866          self.spline = spline
 867          self.whole = whole
 868          self.parameter = parameter
 869          super().__init__(self.whole, **kwargs)
 870  
 871      def construct(self):
 872          self.create_spline_node()
 873          self.create_spline_object_node()
 874          self.create_whole_node()
 875  
 876      def create_spline_node(self):
 877          self.spline_node = XSpline(self.target)
 878          self.spline_length_port = self.spline_node.obj.AddPort(
 879              c4d.GV_PORT_OUTPUT, c4d.GV_SPLINE_OUTPUT_LENGTH)
 880          self.nodes.append(self.spline_node)
 881  
 882      def create_spline_object_node(self):
 883          self.spline_object_node = XObject(self.target, link_target=self.spline)
 884          self.spline_object_port = self.spline_object_node.obj.AddPort(
 885              c4d.GV_PORT_OUTPUT, c4d.GV_OBJECT_OPERATOR_OBJECT_OUT)
 886          self.nodes.append(self.spline_object_node)
 887  
 888      def create_whole_node(self):
 889          self.whole_node = XObject(
 890              self.target, link_target=self.whole)
 891          self.parameter_port = self.whole_node.obj.AddPort(
 892              c4d.GV_PORT_INPUT, self.parameter.desc_id)
 893          self.nodes.append(self.whole_node)
 894  
 895      def connect_ports(self):
 896          self.spline_object_port.Connect(self.spline_node.obj.GetInPort(0))
 897          self.spline_length_port.Connect(self.parameter_port)
 898  
 899  
 900  class XAlignToSpline(CustomXPression):
 901      """positions an object on a spline given the relative completion"""
 902  
 903      def __init__(self, part=None, whole=None, spline=None, completion_parameter=None, **kwargs):
 904          self.part = part
 905          self.whole = whole
 906          self.spline = spline
 907          self.completion_parameter = completion_parameter
 908          super().__init__(self.whole, **kwargs)
 909  
 910      def construct(self):
 911          self.create_part_node()
 912          self.create_whole_node()
 913          self.create_spline_object_node()
 914          self.create_spline_node()
 915          self.create_matrix_to_hpb_node()
 916          self.create_vector_to_matrix_node()
 917  
 918      def create_whole_node(self):
 919          self.whole_node = XObject(self.whole)
 920          self.completion_port = self.whole_node.obj.AddPort(
 921              c4d.GV_PORT_OUTPUT, self.completion_parameter.desc_id)
 922          self.nodes.append(self.whole_node)
 923  
 924      def create_part_node(self):
 925          self.part_node = XObject(self.whole, link_target=self.part)
 926          self.global_position_port = self.part_node.obj.AddPort(
 927              c4d.GV_PORT_INPUT, c4d.ID_BASEOBJECT_GLOBAL_POSITION)
 928          self.h_port = self.part_node.obj.AddPort(
 929              c4d.GV_PORT_INPUT, ROT_H)
 930          self.p_port = self.part_node.obj.AddPort(
 931              c4d.GV_PORT_INPUT, ROT_P)
 932          self.b_port = self.part_node.obj.AddPort(
 933              c4d.GV_PORT_INPUT, ROT_B)
 934          self.nodes.append(self.part_node)
 935  
 936      def create_spline_object_node(self):
 937          self.spline_object_node = XObject(self.whole, link_target=self.spline)
 938          self.spline_object_port = self.spline_object_node.obj.AddPort(
 939              c4d.GV_PORT_OUTPUT, c4d.GV_OBJECT_OPERATOR_OBJECT_OUT)
 940          self.nodes.append(self.spline_object_node)
 941  
 942      def create_spline_node(self):
 943          self.spline_node = XSpline(self.whole)
 944          self.tangent_port = self.spline_node.obj.AddPort(
 945              c4d.GV_PORT_OUTPUT, c4d.GV_SPLINE_OUTPUT_TANGENT)
 946          self.nodes.append(self.spline_node)
 947  
 948      def create_matrix_to_hpb_node(self):
 949          self.matrix_to_hpb_node = XMatrix2HPB(self.whole)
 950          self.nodes.append(self.matrix_to_hpb_node)
 951  
 952      def create_vector_to_matrix_node(self):
 953          self.vector_to_matrix_node = XVect2Matrix(self.whole)
 954          self.nodes.append(self.vector_to_matrix_node)
 955  
 956      def connect_ports(self):
 957          self.completion_port.Connect(self.spline_node.obj.GetInPort(1))
 958          self.spline_node.obj.GetOutPort(0).Connect(self.global_position_port)
 959          self.spline_object_port.Connect(self.spline_node.obj.GetInPort(0))
 960          self.tangent_port.Connect(self.vector_to_matrix_node.obj.GetInPort(0))
 961          self.vector_to_matrix_node.obj.GetOutPort(0).Connect(
 962              self.matrix_to_hpb_node.obj.GetInPort(0))
 963          self.matrix_to_hpb_node.obj.GetOutPort(0).Connect(self.h_port)
 964          self.matrix_to_hpb_node.obj.GetOutPort(1).Connect(self.p_port)
 965          self.matrix_to_hpb_node.obj.GetOutPort(2).Connect(self.b_port)
 966  
 967  
 968  class XLinkParamToField(CustomXPression):
 969      """links a field to a userdata parameter"""
 970  
 971      def __init__(self, field=None, target=None, part=None, parameter=None, **kwargs):
 972          self.field = field
 973          self.target = target
 974          self.part = part
 975          self.parameter = parameter
 976          super().__init__(self.target, **kwargs)
 977  
 978      def construct(self):
 979          self.create_part_node_out()
 980          self.create_part_node_in()
 981          self.create_falloff_node()
 982  
 983      def create_falloff_node(self):
 984          self.falloff_node = XFalloff(self.target, fields=[self.field])
 985          self.nodes.append(self.falloff_node)
 986  
 987      def create_part_node_in(self):
 988          self.part_node_in = XObject(self.target, link_target=self.part)
 989          self.parameter_port = self.part_node_in.obj.AddPort(
 990              c4d.GV_PORT_INPUT, self.parameter.desc_id)
 991          self.nodes.append(self.part_node_in)
 992  
 993      def create_part_node_out(self):
 994          self.part_node_out = XObject(self.target, link_target=self.part)
 995          self.center_port = self.part_node_out.obj.AddPort(
 996              c4d.GV_PORT_OUTPUT, self.part.center_parameter.desc_id)
 997          self.nodes.append(self.part_node_out)
 998  
 999      def connect_ports(self):
1000          self.center_port.Connect(self.falloff_node.obj.GetInPort(0))
1001          self.falloff_node.obj.GetOutPort(0).Connect(self.parameter_port)
1002  
1003  
1004  class XBoundingBox(CustomXPression):
1005      """calculates the bounding box of a set of objects"""
1006  
1007      def __init__(self, *elements, target=None, width_parameter=None, height_parameter=None, depth_parameter=None, center_parameter=None, center_x_parameter=None, center_y_parameter=None, center_z_parameter=None, **kwargs):
1008          self.elements = elements
1009          self.width_parameter = width_parameter
1010          self.height_parameter = height_parameter
1011          self.depth_parameter = depth_parameter
1012          self.center_parameter = center_parameter
1013          self.center_x_parameter = center_x_parameter
1014          self.center_y_parameter = center_y_parameter
1015          self.center_z_parameter = center_z_parameter
1016          self.target = target
1017          super().__init__(self.target, **kwargs)
1018  
1019      def construct(self):
1020          self.create_element_nodes()
1021          self.create_target_node()
1022          self.create_bounding_box_node()
1023          self.create_vector_to_reals_nodes()
1024  
1025      def create_element_nodes(self):
1026          self.element_nodes = []
1027          self.object_ports = []
1028          for element in self.elements:
1029              element_node = XObject(self.target, link_target=element)
1030              object_port = element_node.obj.AddPort(
1031                  c4d.GV_PORT_OUTPUT, c4d.GV_OBJECT_OPERATOR_OBJECT_OUT)
1032              self.element_nodes.append(element_node)
1033              self.object_ports.append(object_port)
1034          self.nodes += self.element_nodes
1035  
1036      def create_target_node(self):
1037          self.target_node = XObject(self.target)
1038          self.width_parameter_port = self.target_node.obj.AddPort(
1039              c4d.GV_PORT_INPUT, self.width_parameter.desc_id)
1040          self.height_parameter_port = self.target_node.obj.AddPort(
1041              c4d.GV_PORT_INPUT, self.height_parameter.desc_id)
1042          self.depth_parameter_port = self.target_node.obj.AddPort(
1043              c4d.GV_PORT_INPUT, self.depth_parameter.desc_id)
1044          self.center_parameter_port = self.target_node.obj.AddPort(
1045              c4d.GV_PORT_INPUT, self.center_parameter.desc_id)
1046          self.center_x_parameter_port = self.target_node.obj.AddPort(
1047              c4d.GV_PORT_INPUT, self.center_x_parameter.desc_id)
1048          self.center_y_parameter_port = self.target_node.obj.AddPort(
1049              c4d.GV_PORT_INPUT, self.center_y_parameter.desc_id)
1050          self.center_z_parameter_port = self.target_node.obj.AddPort(
1051              c4d.GV_PORT_INPUT, self.center_z_parameter.desc_id)
1052          self.nodes.append(self.target_node)
1053  
1054      def create_bounding_box_node(self):
1055          self.bounding_box_node = XBBox(self.target)
1056          self.nodes.append(self.bounding_box_node)
1057  
1058      def create_vector_to_reals_nodes(self):
1059          self.diameter_vector_to_reals_node = XVec2Reals(self.target)
1060          self.center_vector_to_reals_node = XVec2Reals(self.target)
1061          self.nodes.append(self.diameter_vector_to_reals_node)
1062          self.nodes.append(self.center_vector_to_reals_node)
1063  
1064      def connect_ports(self):
1065          self.connect_diameter_bounding_box_node_to_parameters()
1066          self.connect_center_bounding_box_node_to_parameters()
1067          self.connect_element_nodes_to_bounding_box_node()
1068  
1069      def connect_diameter_bounding_box_node_to_parameters(self):
1070          self.bounding_box_node.diameter_port_out.Connect(
1071              self.diameter_vector_to_reals_node.obj.GetInPort(0))
1072          self.diameter_vector_to_reals_node.obj.GetOutPort(
1073              0).Connect(self.width_parameter_port)
1074          self.diameter_vector_to_reals_node.obj.GetOutPort(
1075              1).Connect(self.height_parameter_port)
1076          self.diameter_vector_to_reals_node.obj.GetOutPort(
1077              2).Connect(self.depth_parameter_port)
1078  
1079      def connect_center_bounding_box_node_to_parameters(self):
1080          self.bounding_box_node.center_port_out.Connect(
1081              self.center_parameter_port)
1082          self.bounding_box_node.center_port_out.Connect(
1083              self.center_vector_to_reals_node.obj.GetInPort(0))
1084          self.center_vector_to_reals_node.obj.GetOutPort(
1085              0).Connect(self.center_x_parameter_port)
1086          self.center_vector_to_reals_node.obj.GetOutPort(
1087              1).Connect(self.center_y_parameter_port)
1088          self.center_vector_to_reals_node.obj.GetOutPort(
1089              2).Connect(self.center_z_parameter_port)
1090  
1091      def connect_element_nodes_to_bounding_box_node(self):
1092          for object_port in self.object_ports:
1093              object_port.Connect(self.bounding_box_node.add_object_port())
1094  
1095  
1096  class XInheritGlobalMatrix(CustomXPression):
1097      """creates a simple setup which inherits the global matrix from the inheritor to the target object"""
1098  
1099      def __init__(self, inheritor=None, target=None, **kwargs):
1100          self.inheritor = inheritor
1101          self.target = target
1102          super().__init__(self.target, **kwargs)
1103  
1104      def construct(self):
1105          self.create_inheritor_node()
1106          self.create_target_node()
1107  
1108      def create_inheritor_node(self):
1109          self.inheritor_node = XObject(self.target, link_target=self.inheritor)
1110          self.global_matrix_port_out = self.inheritor_node.obj.AddPort(
1111              c4d.GV_PORT_OUTPUT, c4d.GV_OBJECT_OPERATOR_GLOBAL_OUT)
1112          self.nodes.append(self.inheritor_node)
1113  
1114      def create_target_node(self):
1115          self.target_node = XObject(self.target)
1116          self.global_matrix_port_in = self.target_node.obj.AddPort(
1117              c4d.GV_PORT_INPUT, c4d.GV_OBJECT_OPERATOR_GLOBAL_IN)
1118          self.nodes.append(self.target_node)
1119  
1120      def connect_ports(self):
1121          self.global_matrix_port_out.Connect(self.global_matrix_port_in)
1122  
1123  
1124  class Movement:
1125      """holds the information for a single movement which can be chained together by XAction into an action"""
1126  
1127      def __init__(self, parameter, timing, output=(0, 1), part=None, easing=True):
1128          self.parameter = parameter
1129          self.timing = timing
1130          self.output = output
1131          self.part = part
1132          self.easing = easing
1133  
1134  
1135  class XAction(CustomXPression):
1136      """specifies a series of overlapping linear parameter movements"""
1137  
1138      def __init__(self, *movements, completion_parameter=None, target=None, name=None, priority=100, **kwargs):
1139          self.movements = list(movements)
1140          self.completion_parameter = completion_parameter
1141          self.target = target
1142          super().__init__(self.target, priority=priority, **kwargs)
1143  
1144      def construct(self):
1145          self.create_object_node_out()
1146          self.create_object_node_in()
1147          self.create_range_mapper_nodes()
1148  
1149      def create_object_node_out(self):
1150          self.object_node_out = XObject(self.target)
1151          self.completion_port = self.object_node_out.obj.AddPort(
1152              c4d.GV_PORT_OUTPUT, self.completion_parameter.desc_id)
1153          self.nodes.append(self.object_node_out)
1154  
1155      def create_object_node_in(self):
1156          self.object_nodes_in = []
1157          self.parameter_ports = []
1158          for movement in self.movements:
1159              if movement.part:
1160                  object_node_in = XObject(
1161                      self.target, link_target=movement.part)
1162              else:
1163                  object_node_in = XObject(self.target)
1164              parameter_port = object_node_in.obj.AddPort(
1165                  c4d.GV_PORT_INPUT, movement.parameter.desc_id)
1166              self.parameter_ports.append(parameter_port)
1167              self.object_nodes_in.append(object_node_in)
1168          self.nodes += self.object_nodes_in
1169  
1170      def create_range_mapper_nodes(self):
1171          self.range_mapper_nodes = []
1172          for movement in self.movements:
1173              range_mapper_node = XRangeMapper(
1174                  self.target, input_range=movement.timing, output_range=movement.output, easing=movement.easing)
1175              self.range_mapper_nodes.append(range_mapper_node)
1176          self.nodes += self.range_mapper_nodes
1177  
1178      def connect_ports(self):
1179          for range_mapper_node, parameter_port in zip(self.range_mapper_nodes, self.parameter_ports):
1180              self.completion_port.Connect(range_mapper_node.obj.GetInPort(0))
1181              range_mapper_node.obj.GetOutPort(0).Connect(parameter_port)
1182  
1183  
1184  class XCorrectMoSplineTransform(CustomXPression):
1185      """feeds the inverted global matrix of the parent null into the local matrix of the mospline to fix the transform behaviour"""
1186  
1187      def __init__(self, mospline, target=None, **kwargs):
1188          self.mospline = mospline
1189          self.target = target
1190          super().__init__(self.target, **kwargs)
1191  
1192      def construct(self):
1193          self.create_target_node()
1194          self.create_invert_node()
1195          self.create_mospline_node()
1196  
1197      def create_target_node(self):
1198          self.target_node = XObject(self.target)
1199          self.global_matrix_port_out = self.target_node.obj.AddPort(
1200              c4d.GV_PORT_OUTPUT, c4d.GV_OBJECT_OPERATOR_GLOBAL_OUT)
1201          self.nodes.append(self.target_node)
1202  
1203      def create_mospline_node(self):
1204          self.mospline_node = XObject(self.target, link_target=self.mospline)
1205          self.local_matrix_port_in = self.mospline_node.obj.AddPort(
1206              c4d.GV_PORT_INPUT, c4d.GV_OBJECT_OPERATOR_LOCAL_IN)
1207          self.nodes.append(self.mospline_node)
1208  
1209      def create_invert_node(self):
1210          self.invert_node = XInvert(self.target, data_type="matrix")
1211          self.nodes.append(self.invert_node)
1212  
1213      def connect_ports(self):
1214          self.global_matrix_port_out.Connect(self.invert_node.obj.GetInPort(0))
1215          self.invert_node.obj.GetOutPort(0).Connect(self.local_matrix_port_in)
1216  
1217  
1218  class XVisibilityControl(CustomXPression):
1219      """toggles the visibility of the specified objects as a function of the driving parameter
1220          if only initial objects are defined their visibility switches off and on again
1221          if final objects are defined the visibility transitions to them"""
1222  
1223      def __init__(self, target=None, driving_parameter=None, initial_objects=[], effect_objects=[], final_objects=[], invisibility_interval=(0, 1), **kwargs):
1224          self.target = target
1225          self.driving_parameter = driving_parameter
1226          self.initial_objects = initial_objects
1227          self.effect_objects = effect_objects
1228          self.final_objects = final_objects
1229          self.invisibility_interval = invisibility_interval
1230          super().__init__(self.target, **kwargs)
1231  
1232      def construct(self):
1233          self.create_initial_object_nodes()
1234          self.create_effect_object_nodes()
1235          self.create_final_object_nodes()
1236          self.create_target_node()
1237          self.create_compare_nodes()
1238  
1239      def create_initial_object_nodes(self):
1240          self.initial_object_nodes = []
1241          self.initial_visibility_ports = []
1242          for initial_object in self.initial_objects:
1243              initial_object_node = XObject(
1244                  self.target, link_target=initial_object)
1245              visibility_port_in = initial_object_node.obj.AddPort(
1246                  c4d.GV_PORT_INPUT, initial_object.visibility_parameter.desc_id)
1247              self.initial_object_nodes.append(initial_object_node)
1248              self.initial_visibility_ports.append(visibility_port_in)
1249          self.nodes += self.initial_object_nodes
1250  
1251      def create_effect_object_nodes(self):
1252          self.effect_object_nodes = []
1253          self.effect_visibility_ports = []
1254          for effect_object in self.effect_objects:
1255              effect_object_node = XObject(
1256                  self.target, link_target=effect_object)
1257              visibility_port_in = effect_object_node.obj.AddPort(
1258                  c4d.GV_PORT_INPUT, effect_object.visibility_parameter.desc_id)
1259              self.effect_object_nodes.append(effect_object_node)
1260              self.effect_visibility_ports.append(visibility_port_in)
1261          self.nodes += self.effect_object_nodes
1262  
1263      def create_final_object_nodes(self):
1264          self.final_object_nodes = []
1265          self.final_visibility_ports = []
1266          for final_object in self.final_objects:
1267              final_object_node = XObject(
1268                  self.target, link_target=final_object)
1269              visibility_port_in = final_object_node.obj.AddPort(
1270                  c4d.GV_PORT_INPUT, final_object.visibility_parameter.desc_id)
1271              self.final_object_nodes.append(final_object_node)
1272              self.final_visibility_ports.append(visibility_port_in)
1273          self.nodes += self.final_object_nodes
1274  
1275      def create_target_node(self):
1276          self.target_node = XObject(self.target)
1277          self.driving_parameter_port_out = self.target_node.obj.AddPort(
1278              c4d.GV_PORT_OUTPUT, self.driving_parameter.desc_id)
1279          self.nodes.append(self.target_node)
1280  
1281      def create_compare_nodes(self):
1282          self.compare_node_lower = XCompare(
1283              self.target, mode="<=", comparison_value=self.invisibility_interval[0])
1284          self.compare_node_upper = XCompare(
1285              self.target, mode=">=", comparison_value=self.invisibility_interval[1])
1286          self.not_node_lower = XNot(self.target)
1287          self.not_node_upper = XNot(self.target)
1288          if not self.final_objects:  # is used by action object
1289              self.not_node_final = XNot(self.target)
1290              self.nodes.append(self.not_node_final)
1291          self.bool_node = XBool(self.target, mode="AND")
1292          self.nodes += [self.compare_node_lower, self.compare_node_upper,
1293                         self.not_node_lower, self.not_node_upper, self.bool_node]
1294  
1295      def connect_ports(self):
1296          self.driving_parameter_port_out.Connect(
1297              self.compare_node_lower.obj.GetInPort(0))
1298          self.driving_parameter_port_out.Connect(
1299              self.compare_node_upper.obj.GetInPort(0))
1300          self.compare_node_upper.obj.GetOutPort(0).Connect(
1301              self.not_node_upper.obj.GetInPort(0))
1302          self.compare_node_lower.obj.GetOutPort(0).Connect(
1303              self.not_node_lower.obj.GetInPort(0))
1304          self.not_node_upper.obj.GetOutPort(0).Connect(
1305              self.bool_node.obj.GetInPort(0))
1306          self.not_node_lower.obj.GetOutPort(0).Connect(
1307              self.bool_node.obj.GetInPort(1))
1308          for effect_visibility_port in self.effect_visibility_ports:
1309              self.bool_node.obj.GetOutPort(0).Connect(
1310                  effect_visibility_port)
1311          if self.final_objects:  # is used for transition object
1312              for initial_visibility_port in self.initial_visibility_ports:
1313                  self.compare_node_lower.obj.GetOutPort(0).Connect(
1314                      initial_visibility_port)
1315              for final_visibility_port in self.final_visibility_ports:
1316                  self.compare_node_upper.obj.GetOutPort(0).Connect(
1317                      final_visibility_port)
1318          else:  # is used for action object
1319              self.bool_node.obj.GetOutPort(0).Connect(
1320                  self.not_node_final.obj.GetInPort(0))
1321              for initial_visibility_port in self.initial_visibility_ports:
1322                  self.not_node_final.obj.GetOutPort(0).Connect(
1323                      initial_visibility_port)
1324  
1325  
1326  class XColorBlend(CustomXPression):
1327      """blends the color of the target object between two colors
1328      the colors should be userdata of the target object"""
1329  
1330      def __init__(self, target=None, blend_parameter=None, color_ini_parameter=None, color_fin_parameter=None, **kwargs):
1331          self.target = target
1332          self.blend_parameter = blend_parameter
1333          self.color_ini_parameter = color_ini_parameter
1334          self.color_fin_parameter = color_fin_parameter
1335          super().__init__(self.target, **kwargs)
1336  
1337      def construct(self):
1338          self.create_mix_node()
1339          self.create_color_node()
1340          self.create_target_node()
1341  
1342      def create_mix_node(self):
1343          self.mix_node = XMix(self.target, data_type="color")
1344          self.nodes.append(self.mix_node)
1345  
1346      def create_color_node(self):
1347          self.color_node = XObject(self.target)
1348          self.color_ini_port = self.color_node.obj.AddPort(
1349              c4d.GV_PORT_OUTPUT, self.color_ini_parameter.desc_id)
1350          self.color_fin_port = self.color_node.obj.AddPort(
1351              c4d.GV_PORT_OUTPUT, self.color_fin_parameter.desc_id)
1352          self.blend_parameter_port = self.color_node.obj.AddPort(
1353              c4d.GV_PORT_OUTPUT, self.blend_parameter.desc_id)
1354          self.nodes.append(self.color_node)
1355  
1356      def create_target_node(self):
1357          self.target_nodes = []
1358          self.target_color_ports = []
1359          for mospline in self.target.mosplines:
1360              target_node = XObject(self.target, link_target=mospline)
1361              target_color_port = target_node.obj.AddPort(
1362                  c4d.GV_PORT_INPUT, mospline.color_parameter.desc_id)
1363              self.target_color_ports.append(target_color_port)
1364              self.target_nodes.append(target_node)
1365          self.nodes += self.target_nodes
1366  
1367      def connect_ports(self):
1368          self.blend_parameter_port.Connect(
1369              self.mix_node.obj.GetInPort(0))
1370          self.color_ini_port.Connect(
1371              self.mix_node.obj.GetInPort(1))
1372          self.color_fin_port.Connect(
1373              self.mix_node.obj.GetInPort(2))
1374          for target_color_port in self.target_color_ports:
1375              self.mix_node.obj.GetOutPort(0).Connect(
1376                  target_color_port)
1377  
1378  
1379  class XConnectNearestClones(CustomXPression):
1380      """connects the nearest clones of the target object to the target object"""
1381  
1382      def __init__(self, *matrices, neighbour_count_parameter=None, max_distance_parameter=None, target=None, **kwargs):
1383          self.target = target
1384          self.matrices = matrices
1385          self.neighbour_count_parameter = neighbour_count_parameter
1386          self.max_distance_parameter = max_distance_parameter
1387          self.nodes = []
1388          super().__init__(self.target, **kwargs)
1389  
1390      def construct(self):
1391          self.create_matrix_nodes()
1392          self.create_target_node()
1393          self.create_proximity_connector_node()
1394  
1395      def create_matrix_nodes(self):
1396          self.matrix_nodes = []
1397          self.matrix_ports = []
1398          for matrix in self.matrices:
1399              matrix_node = XObject(self.target, link_target=matrix)
1400              matrix_port = matrix_node.obj.AddPort(
1401                  c4d.GV_PORT_OUTPUT, c4d.GV_OBJECT_OPERATOR_OBJECT_OUT)
1402              self.matrix_nodes.append(matrix_node)
1403              self.matrix_ports.append(matrix_port)
1404          self.nodes += self.matrix_nodes
1405  
1406      def create_target_node(self):
1407          self.target_node = XObject(self.target)
1408          self.target_neighbour_count_port = self.target_node.obj.AddPort(
1409              c4d.GV_PORT_OUTPUT, self.neighbour_count_parameter.desc_id)
1410          self.target_max_distance_port = self.target_node.obj.AddPort(
1411              c4d.GV_PORT_OUTPUT, self.max_distance_parameter.desc_id)
1412          self.nodes.append(self.target_node)
1413  
1414      def create_proximity_connector_node(self):
1415          self.proximity_connector_node = XProximityConnector(self.target, matrix_count=len(self.matrices))
1416          self.nodes.append(self.proximity_connector_node)
1417  
1418      def connect_ports(self):
1419          for i, matrix_port in enumerate(self.matrix_ports):
1420              matrix_port.Connect(
1421                  self.proximity_connector_node.obj.GetInPort(i+2))  # skip first two ports
1422          self.target_neighbour_count_port.Connect(
1423              self.proximity_connector_node.neighbour_count_port)
1424          self.target_max_distance_port.Connect(
1425              self.proximity_connector_node.max_distance_port)
1426  
1427  class XExplosion(CustomXPression):
1428      """takes a list of parts as input and multiplies the distance of a chosen child object from a given origin along that distance creating an explosion effect
1429          e.g in the context of the dicer object we use the dicer as origin, the rectangle as child object and the splinemasks as input
1430          if we would not use child objects we would run into an infinite loop"""
1431  
1432      def __init__(self, target=None, parts=None, children=None, completion_parameter=None, strength_parameter=None, **kwargs):
1433          self.target = target
1434          self.parts = parts
1435          self.children = children
1436          self.completion_parameter = completion_parameter
1437          self.strength_parameter = strength_parameter
1438          super().__init__(self.target, **kwargs)
1439  
1440      def construct(self):
1441          self.create_children_nodes()
1442          self.create_part_nodes()
1443          self.create_target_node()
1444          self.create_math_nodes()
1445  
1446      def create_children_nodes(self):
1447          self.children_nodes = []
1448          self.child_positiion_ports= []
1449          for child in self.children:
1450              child_node = XObject(self.target, link_target=child)
1451              child_position_port = child_node.obj.AddPort(
1452                  c4d.GV_PORT_OUTPUT, c4d.ID_BASEOBJECT_POSITION)
1453              self.children_nodes.append(child_node)
1454              self.child_positiion_ports.append(child_position_port)
1455          self.nodes += self.children_nodes
1456  
1457      def create_part_nodes(self):
1458          self.part_nodes = []
1459          self.part_positiion_ports = []
1460          for part in self.parts:
1461              part_node = XObject(self.target, link_target=part)
1462              part_position_port = part_node.obj.AddPort(
1463                  c4d.GV_PORT_INPUT, c4d.ID_BASEOBJECT_POSITION)
1464              self.part_nodes.append(part_node)
1465              self.part_positiion_ports.append(part_position_port)
1466          self.nodes += self.part_nodes
1467  
1468      def create_target_node(self):
1469          self.target_node = XObject(self.target)
1470          self.target_completion_port = self.target_node.obj.AddPort(
1471              c4d.GV_PORT_OUTPUT, self.completion_parameter.desc_id)
1472          self.target_strength_port = self.target_node.obj.AddPort(
1473              c4d.GV_PORT_OUTPUT, self.strength_parameter.desc_id)
1474          self.target_origin_port = self.target_node.obj.AddPort(
1475              c4d.GV_PORT_OUTPUT, c4d.ID_BASEOBJECT_GLOBAL_POSITION)
1476          self.nodes.append(self.target_node)
1477  
1478      def create_math_nodes(self):
1479          # we use a subtraction node to get the vector from origin to child
1480          # then we use two multiplication node to multiply the vector with the product of the strength and completion parameter and feed the result in the parts position
1481          self.subtraction_nodes = []
1482          self.vector_multiplication_nodes = []
1483          self.real_multiplication_nodes = []
1484          for i, child_node in enumerate(self.children_nodes):
1485              subtraction_node = XMath(self.target, mode="-", data_type="vector")
1486              vector_multiplication_node = XMath(self.target, mode="*", data_type="vector")
1487              real_multiplication_node = XMath(self.target, mode="*", data_type="real")
1488              self.subtraction_nodes.append(subtraction_node)
1489              self.vector_multiplication_nodes.append(vector_multiplication_node)
1490              self.real_multiplication_nodes.append(real_multiplication_node)
1491          self.nodes += self.subtraction_nodes
1492          self.nodes += self.vector_multiplication_nodes
1493          self.nodes += self.real_multiplication_nodes
1494  
1495      def connect_ports(self):
1496          for i, child_node in enumerate(self.children_nodes):
1497              self.child_positiion_ports[i].Connect(
1498                  self.subtraction_nodes[i].obj.GetInPort(0))
1499              self.target_origin_port.Connect(
1500                  self.subtraction_nodes[i].obj.GetInPort(1))
1501              self.subtraction_nodes[i].obj.GetOutPort(0).Connect(
1502                  self.vector_multiplication_nodes[i].obj.GetInPort(0))
1503              self.target_strength_port.Connect(
1504                  self.real_multiplication_nodes[i].obj.GetInPort(0))
1505              self.target_completion_port.Connect(
1506                  self.real_multiplication_nodes[i].obj.GetInPort(1))
1507              self.real_multiplication_nodes[i].obj.GetOutPort(0).Connect(
1508                  self.vector_multiplication_nodes[i].obj.GetInPort(1))
1509              self.vector_multiplication_nodes[i].obj.GetOutPort(0).Connect(
1510                  self.part_positiion_ports[i])
1511  
1512  class XVisiblityHandler(CustomXPression):
1513      """handles multiple inputs of visibility controls, taking the min() function"""
1514  
1515      def __init__(self, target=None, **kwargs):
1516          self.target = target
1517          super().__init__(self.target, **kwargs)
1518  
1519      def construct(self):
1520          self.create_python_node()
1521          self.create_target_node()