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()