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