abstract_objects_legacy.py
1 import importlib 2 import DreamTalk.materials 3 importlib.reload(DreamTalk.materials) 4 from DreamTalk.materials import FillMaterial, SketchMaterial 5 from DreamTalk.tags import FillTag, SketchTag, XPressoTag, AlignToSplineTag 6 from DreamTalk.objects.stroke_objects import StrokeGen, StrokeMaterial, SilhouetteSplineGen, MeshStroke 7 from DreamTalk.constants import WHITE, SCALE_X, SCALE_Y, SCALE_Z 8 from DreamTalk.animation.animation import VectorAnimation, ScalarAnimation, ColorAnimation 9 from DreamTalk.xpresso.userdata import * 10 from DreamTalk.xpresso.xpressions import XRelation, XIdentity, XSplineLength, XBoundingBox, XAction, Movement 11 # Lazy imports to avoid circular dependencies - these modules import from this file 12 # Import them inside methods that need them instead of at module level 13 # import DreamTalk.objects.effect_objects as effect_objects 14 # import DreamTalk.objects.helper_objects as helper_objects 15 # import DreamTalk.objects.custom_objects as custom_objects 16 from abc import ABC, abstractmethod 17 import c4d.utils 18 import c4d 19 20 21 def _get_effect_objects(): 22 """Lazy import to avoid circular dependency""" 23 import DreamTalk.objects.effect_objects as effect_objects 24 return effect_objects 25 26 27 def _get_helper_objects(): 28 """Lazy import to avoid circular dependency""" 29 import DreamTalk.objects.helper_objects as helper_objects 30 return helper_objects 31 32 33 def _get_custom_objects(): 34 """Lazy import to avoid circular dependency""" 35 import DreamTalk.objects.custom_objects as custom_objects 36 return custom_objects 37 38 39 class ProtoObject(ABC): 40 41 def __init__(self, name=None, x=0, y=0, z=0, h=0, p=0, b=0, scale=1, position=None, rotation=None, plane="xy"): 42 self.document = c4d.documents.GetActiveDocument() # get document 43 self.specify_object() 44 self.set_xpresso_tags() 45 self.set_unique_desc_ids() 46 self.insert_to_document() 47 self.set_name(name=name) 48 self.set_position(x=x, y=y, z=z, position=position) 49 self.set_rotation(h=h, p=p, b=b, rotation=rotation) 50 self.set_scale(scale=scale) 51 self.set_object_properties() 52 self.plane = plane 53 self.set_plane() 54 self.relations = [] 55 self.actions = [] 56 self.xpressions = {} # keeps track of animators, composers etc. 57 self.accessed_parameters = {} # keeps track which parameters have AccessControl 58 self.helper_objects = {} # keeps track of helper objects created by Animators 59 self.parent = None 60 61 def __repr__(self): 62 """sets the string representation for printing""" 63 return self.name 64 65 @abstractmethod 66 def specify_object(self): 67 pass 68 69 def set_xpresso_tags(self): 70 """initializes the necessary xpresso tags on the object""" 71 # the composition tags hold the hierarchy of compositions and ensure execution from highest to lowest 72 #self.composition_tags = [] 73 # the animator tag holds the acting of the animators on the actual parameters 74 # set priority to be executed last 75 # self.animator_tag = XPressoTag( 76 # target=self, name="AnimatorTag", priority=1, priority_mode="expression") 77 # the freeze tag holds the freezing xpressions that are executed before the animators 78 # set priority to be executed after compositions and before animators 79 # self.freeze_tag = XPressoTag( 80 # target=self, name="FreezeTag", priority=0, priority_mode="animation") 81 # inserts an xpresso tag used for custom xpressions 82 self.custom_tag = XPressoTag( 83 target=self, name="CustomTag", priority_mode="expression") 84 85 def add_composition_tag(self): 86 """adds another layer to the composition hierarchy""" 87 # set priority according to position in composition hierarchy 88 tag_name = "CompositionTag" + str(len(self.composition_tags)) 89 tag_priority = -len(self.composition_tags) 90 composition_tag = XPressoTag( 91 target=self, name=tag_name, priority=tag_priority, priority_mode="initial") 92 self.composition_tags.append(composition_tag) 93 return composition_tag.obj 94 95 def set_unique_desc_ids(self): 96 """optional method to make unique descIds easily accessible""" 97 pass 98 99 def set_name(self, name=None): 100 if name is None: 101 self.name = self.__class__.__name__ 102 else: 103 self.name = name 104 self.obj.SetName(self.name) 105 106 def set_plane(self): 107 """sets the plane of the custom object""" 108 if self.plane == "xy": 109 self.rotate(rotation=(0, 0, 0)) 110 elif self.plane == "yz": 111 self.rotate(rotation=(PI / 2, 0, 0)) 112 elif self.plane == "xz": 113 self.rotate(rotation=(0, -PI / 2, 0)) 114 115 def specify_parameters(self): 116 """specifies optional parameters for the custom object""" 117 pass 118 119 def insert_parameters(self): 120 """inserts the specified parameters as userdata""" 121 if self.parameters: 122 self.parameters_u_group = UGroup( 123 *self.parameters, target=self.obj, name=self.name + "Parameters") 124 125 def specify_relations(self): 126 """specifies the relations between the part's parameters using xpresso""" 127 pass 128 129 def set_position(self, x=0, y=0, z=0, position=None, relative=False): 130 if position is None: 131 position = c4d.Vector(x, y, z) 132 elif type(position) is not c4d.Vector: 133 position = c4d.Vector(*position) 134 if relative: 135 self.obj[c4d.ID_BASEOBJECT_POSITION] += position 136 else: 137 self.obj[c4d.ID_BASEOBJECT_POSITION] = position 138 139 def set_rotation(self, h=0, p=0, b=0, rotation=None, relative=False): 140 if rotation is None: 141 rotation = c4d.Vector(h, p, b) 142 elif type(rotation) is not c4d.Vector: 143 rotation = c4d.Vector(*rotation) 144 if relative: 145 self.obj[c4d.ID_BASEOBJECT_ROTATION] += rotation 146 else: 147 self.obj[c4d.ID_BASEOBJECT_ROTATION] = rotation 148 149 def set_frozen_rotation(self, h=0, p=0, b=0, rotation=None, relative=False): 150 if rotation is None: 151 rotation = c4d.Vector(h, p, b) 152 if relative: 153 self.obj[c4d.ID_BASEOBJECT_FROZEN_ROTATION] += rotation 154 else: 155 self.obj[c4d.ID_BASEOBJECT_FROZEN_ROTATION] = rotation 156 157 def set_scale(self, scale=1, relative=False): 158 if relative: 159 self.obj[c4d.ID_BASEOBJECT_SCALE] *= scale 160 else: 161 scale = c4d.Vector(scale, scale, scale) 162 self.obj[c4d.ID_BASEOBJECT_SCALE] = scale 163 164 def move(self, x=0, y=0, z=0, position=None, relative=True): 165 if position is None: 166 position = c4d.Vector(x, y, z) 167 elif type(position) is not c4d.Vector: 168 position = c4d.Vector(*position) 169 descriptor = c4d.ID_BASEOBJECT_POSITION 170 animation = VectorAnimation( 171 target=self, descriptor=descriptor, vector=position, relative=relative) 172 if relative: 173 self.obj[descriptor] += position 174 else: 175 self.obj[descriptor] = position 176 return animation 177 178 def rotate(self, h=0, p=0, b=0, rotation=None): 179 if rotation is None: 180 rotation = c4d.Vector(h, p, b) 181 elif type(rotation) is not c4d.Vector: 182 rotation = c4d.Vector(*rotation) 183 descriptor = c4d.ID_BASEOBJECT_ROTATION 184 animation = VectorAnimation( 185 target=self, descriptor=descriptor, vector=rotation, relative=True) 186 self.obj[descriptor] += rotation 187 return animation 188 189 def scale(self, x=0, y=0, z=0, scale=None): 190 if scale is None: 191 scale = c4d.Vector(x, y, z) 192 elif type(scale) in (tuple, list): 193 scale = c4d.Vector(*scale) 194 elif type(scale) in (int, float): 195 scale = c4d.Vector(scale, scale, scale) 196 descriptor = c4d.ID_BASEOBJECT_SCALE 197 animation = VectorAnimation( 198 target=self, descriptor=descriptor, vector=scale, relative=True, multiplicative=True) 199 self.obj[descriptor] += scale 200 return animation 201 202 def insert_to_document(self): 203 self.document.InsertObject(self.obj) 204 205 def get_segment_count(self): 206 # returns the length of the spline or a specific segment 207 spline_help = c4d.utils.SplineHelp() 208 spline_help.InitSplineWith(self.obj) 209 segment_count = spline_help.GetSegmentCount() 210 return segment_count 211 212 def get_length(self, segment=None): 213 # returns the length of the spline or a specific segment 214 spline_help = c4d.utils.SplineHelp() 215 spline_help.InitSplineWith(self.obj) 216 217 if segment: 218 segment_length = spline_help.GetSegmentLength(segment) 219 return segment_length 220 else: 221 spline_length = spline_help.GetSplineLength() 222 return spline_length 223 224 def get_spline_segment_lengths(self): 225 # get the length of each segment 226 segment_lengths = [] 227 for i in range(self.get_segment_count()): 228 segment_lengths.append(self.get_length(segment=i)) 229 return segment_lengths 230 231 def set_object_properties(self): 232 """used to set the unique properties of a specific object""" 233 pass 234 235 def sort_relations_by_priority(self): 236 """sorts the relations by priority""" 237 238 # right now it oly ensures that the actions are inserted above the relations 239 # in the future we will implement priority and sorting by sub-xpression dependencies 240 241 # get node master 242 master = self.custom_tag.obj.GetNodeMaster() 243 parent = master.GetRoot() 244 245 """ 246 # resort by parent reference 247 for relation in self.relations: 248 if relation.parent: 249 self.relations.remove(relation) 250 parent_idx = self.relations.index(relation.parent) 251 self.relations.insert(parent_idx, relation) 252 for action in self.actions: 253 if action.parent: 254 self.actions.remove(action) 255 parent_idx = self.actions.index(action.parent) 256 self.actions.insert(parent_idx, action) 257 """ 258 if self.relations: 259 for relation in self.relations: 260 master.InsertFirst(parent, relation.obj) 261 if self.actions: 262 for action in self.actions: 263 master.InsertFirst(parent, action.obj) 264 265 266 class VisibleObject(ProtoObject): 267 # visible objects 268 269 def __init__(self, visible=True, creation=0, **kwargs): 270 super().__init__(**kwargs) 271 self.creation = creation 272 self.visible = visible 273 self.specify_visibility_parameter() 274 self.insert_visibility_parameter() 275 self.specify_visibility_relation() 276 self.specify_live_bounding_box_parameters() 277 self.insert_live_bounding_box_parameters() 278 self.specify_live_bounding_box_relation() 279 self.add_bounding_box_information() 280 281 def specify_action_parameters(self): 282 pass 283 284 def specify_creation_parameter(self): 285 self.creation_parameter = UCompletion( 286 name="Creation", default_value=self.creation) 287 self.action_parameters += [self.creation_parameter] 288 289 def insert_action_parameters(self): 290 """inserts the specified action_parameters as userdata""" 291 if self.action_parameters: 292 self.actions_u_group = UGroup( 293 *self.action_parameters, target=self.obj, name=self.name + "Actions") 294 295 def specify_actions(self): 296 """specifies actions that coordinate parameters""" 297 pass 298 299 def specify_creation(self): 300 """specifies the creation action""" 301 pass 302 303 def get_clone(self): 304 """clones an object and inserts it into the scene""" 305 clone = self.obj.GetClone() 306 self.document.InsertObject(clone) 307 return clone 308 309 def get_editable(self): 310 """returns an editable clone of the object""" 311 clone = self.get_clone() 312 editable_clone = c4d.utils.SendModelingCommand(command=c4d.MCOMMAND_MAKEEDITABLE, list=[ 313 clone], mode=c4d.MODELINGCOMMANDMODE_ALL, doc=self.document)[0] 314 return editable_clone 315 316 def attach_to(self, target, direction="front", offset=0): 317 """places the object such that the bounding boxes touch along a given direction and makes object child of target""" 318 bounding_box = self.obj.GetRad() 319 bounding_box_position = self.obj.GetMp() 320 bounding_box_target = target.obj.GetRad() 321 bounding_box_position_target = target.obj.GetMp() 322 new_position = bounding_box_position_target - bounding_box_position 323 if direction == "top": 324 new_position.y += bounding_box_target.y + bounding_box.y + offset 325 if direction == "bottom": 326 new_position.y -= bounding_box_target.y + bounding_box.y + offset 327 if direction == "left": 328 new_position.x -= bounding_box_target.x + bounding_box.x + offset 329 if direction == "right": 330 new_position.x += bounding_box_target.x + bounding_box.x + offset 331 if direction == "front": 332 new_position.z -= bounding_box_target.z + bounding_box.z + offset 333 if direction == "back": 334 new_position.z += bounding_box_target.z + bounding_box.z + offset 335 if direction == "center": 336 new_position = bounding_box_position_target - bounding_box_position 337 self.obj.InsertUnder(target.obj) 338 self.set_position(position=new_position) 339 340 def specify_visibility_parameter(self): 341 """specifies visibility parameter""" 342 self.visibility_parameter = UCheckBox( 343 name="Visibility", default_value=self.visible) 344 345 def insert_visibility_parameter(self): 346 """inserts the visibility parameter as userdata""" 347 self.visibility_u_group = UGroup( 348 self.visibility_parameter, target=self.obj, name="Visibility") 349 350 def specify_visibility_relation(self): 351 """link parameter to visibility""" 352 visibility_relation = XRelation(part=self, whole=self, desc_ids=[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR, c4d.ID_BASEOBJECT_VISIBILITY_RENDER], 353 parameters=[self.visibility_parameter], formula=f"1-{self.visibility_parameter.name}") 354 355 def specify_live_bounding_box_parameters(self): 356 """specifies bounding box parameters""" 357 self.width_parameter = ULength(name="Width") 358 self.height_parameter = ULength(name="Height") 359 self.depth_parameter = ULength(name="Depth") 360 self.center_parameter = UVector(name="Center") 361 self.center_x_parameter = ULength(name="CenterX") 362 self.center_y_parameter = ULength(name="CenterY") 363 self.center_z_parameter = ULength(name="CenterZ") 364 self.live_bounding_box_parameters = [self.width_parameter, 365 self.height_parameter, 366 self.depth_parameter, 367 self.center_parameter, 368 self.center_x_parameter, 369 self.center_y_parameter, 370 self.center_z_parameter] 371 372 def insert_live_bounding_box_parameters(self): 373 """inserts the bounding box parameters as userdata""" 374 self.live_bounding_box_u_group = UGroup( 375 *self.live_bounding_box_parameters, target=self.obj, name="LiveBoundingBox") 376 377 def specify_live_bounding_box_relation(self): 378 """feed bounding box information into parameters""" 379 live_bounding_box_relation = XBoundingBox(self, target=self, width_parameter=self.width_parameter, height_parameter=self.height_parameter, 380 depth_parameter=self.depth_parameter, center_parameter=self.center_parameter, 381 center_x_parameter=self.center_x_parameter, center_y_parameter=self.center_y_parameter, center_z_parameter=self.center_z_parameter) 382 383 def add_bounding_box_information(self): 384 bounding_box_center, bounding_radius = c4d.utils.GetBBox( 385 self.obj, self.obj.GetMg()) 386 self.width = bounding_radius.x * 2 387 self.height = bounding_radius.y * 2 388 self.depth = bounding_radius.z * 2 389 self.center = bounding_box_center 390 391 def get_center(self): 392 # returns the center position from the live bounding box information 393 center_position = self.obj.GetMp() * self.obj.GetMg() 394 return center_position 395 396 def get_radius(self): 397 # returns the radius from the live bounding box information 398 __, radius = c4d.utils.GetBBox(self.obj, self.obj.GetMg()) 399 return radius 400 401 def get_diameter(self): 402 # returns the diameter from the longest radius dimension 403 radius = self.get_radius() 404 diameter = max(radius.x, radius.y, radius.z) * 2 405 return diameter 406 407 def register_connections(self, connections): 408 # saves the connections for later functionality of UnConnect 409 self.connections = connections 410 411 def create(self, completion=1): 412 """specifies the creation animation""" 413 desc_id = self.creation_parameter.desc_id 414 animation = ScalarAnimation( 415 target=self, descriptor=desc_id, value_fin=completion) 416 self.obj[desc_id] = completion 417 return animation 418 419 def un_create(self, completion=0): 420 """specifies the uncreation animation""" 421 desc_id = self.creation_parameter.desc_id 422 animation = ScalarAnimation( 423 target=self, descriptor=desc_id, value_fin=completion) 424 self.obj[desc_id] = completion 425 return animation 426 427 def align_to_spline(self, spline=None): 428 self.align_to_spline_tag = AlignToSplineTag(target=self, spline=spline) 429 430 def wrap_around(self, target=None): 431 helper_objects = _get_helper_objects() 432 self.shrink_wrap = helper_objects.ShrinkWrap(target=target) 433 self.shrink_wrap.obj.InsertUnder(self.obj) 434 435 def project_to_surface(self, projection_surface=None, **kwargs): 436 helper_objects = _get_helper_objects() 437 self.projection = helper_objects.Projection(target=self, projection_surface=projection_surface, **kwargs) 438 439 def move_axis(self, position=(0, 0, 0)): 440 """moves the axis without moving the geometry""" 441 vec = c4d.Vector(*position) 442 print(vec) 443 print(position) 444 points = self.obj.GetAllPoints() 445 for i, point in enumerate(points): 446 points[i] = point - vec 447 if i < 3: 448 print(points[i] - point) 449 self.obj.SetAbsPos(self.obj.GetAbsPos() + vec) 450 self.obj.SetAllPoints(points) 451 self.obj.Message(c4d.MSG_UPDATE) 452 c4d.EventAdd() 453 454 455 class CustomObject(VisibleObject): 456 """this class is used to create custom objects that are basically 457 groups with coupling of the childrens parameters through xpresso 458 459 Supports two modes: 460 - Standard mode (default): Uses XPresso for parameter relationships 461 - Generator mode (generator_mode=True): Uses Python Generator for MoGraph compatibility 462 """ 463 464 def __init__(self, diameter=None, generator_mode=False, **kwargs): 465 self.generator_mode = generator_mode 466 super().__init__(**kwargs) 467 self.parts = [] 468 self.specify_parts() 469 self.insert_parts() 470 self.parameters = [] 471 self.specify_parameters() 472 self.insert_parameters() 473 self.relations = [] # Initialize relations list before specify_relations 474 self.specify_relations() 475 476 # Skip XPresso setup in generator mode 477 if not self.generator_mode: 478 self.action_parameters = [] 479 self.specify_action_parameters() 480 self.specify_creation_parameter() 481 self.insert_action_parameters() 482 self.specify_actions() 483 self.specify_creation() 484 self.diameter = diameter 485 self.add_bounding_box_information() 486 self.specify_bounding_box_parameters() 487 self.insert_bounding_box_parameters() 488 self.specify_bounding_box_relations() 489 self.specify_visibility_inheritance_relations() 490 self.specify_position_inheritance() 491 self.sort_relations_by_priority() 492 else: 493 # Generator mode: still need creation_parameter for keyframe animations 494 self.action_parameters = [] 495 self.specify_creation_parameter() 496 self.diameter = diameter 497 self.insert_action_parameters() 498 # Note: Subclasses with custom _convert_to_generator (like FoldableCube) 499 # will override this. Otherwise, use the generic GeneratorMixin conversion. 500 # The conversion happens AFTER all parameters are set up. 501 self._convert_to_generator() 502 503 def _convert_to_generator(self): 504 """Convert this CustomObject to a Python Generator for MoGraph compatibility. 505 506 This replaces self.obj (a Null) with a Python Generator that: 507 - Has the same UserData parameters 508 - Contains the same children (recursively converted if they have GeneratorMixin) 509 - Uses Python code instead of XPresso for relationships 510 """ 511 if not hasattr(self, 'create_as_generator'): 512 # No GeneratorMixin - can't convert 513 return 514 515 # Store old obj reference 516 old_obj = self.obj 517 old_parent = old_obj.GetUp() 518 old_pred = old_obj.GetPred() 519 520 # Create the generator version 521 gen = self.create_as_generator(recursive=True) 522 523 # Replace old_obj with gen in the document 524 if old_parent: 525 if old_pred: 526 gen.InsertAfter(old_pred) 527 else: 528 gen.InsertUnder(old_parent) 529 else: 530 # Was at root level 531 doc = c4d.documents.GetActiveDocument() 532 doc.InsertObject(gen) 533 534 old_obj.Remove() 535 536 # Update self.obj to point to the generator 537 self.obj = gen 538 539 def specify_creation(self): 540 """used to specify the unique creation animation for each individual custom object""" 541 pass 542 543 def inherit_creation(self): 544 """inheriting creation from parts""" 545 movements = [Movement(part.creation_parameter, (0, 1), part=part, easing=False) 546 for part in self.parts] 547 self.creation_action = XAction(*movements, 548 target=self, completion_parameter=self.creation_parameter, name="Creation") 549 550 def specify_position_inheritance(self): 551 """used to specify how the position should be determined""" 552 pass 553 554 @abstractmethod 555 def specify_parts(self): 556 """save parts as attributes and write them to self.parts""" 557 pass 558 559 def insert_parts(self): 560 """inserts the parts as children""" 561 for part in self.parts: 562 # check if part is not already child so existing hierarchies won't be disturbed 563 if not part.obj.GetUp(): 564 part.obj.InsertUnder(self.obj) 565 part.parent = self 566 # check for membranes 567 if hasattr(part, "membrane"): 568 part.membrane.obj.InsertUnder(self.obj) 569 part.membrane.parent = self 570 571 def specify_object(self): 572 self.obj = c4d.BaseObject(c4d.Onull) 573 574 def specify_visibility_inheritance_relations(self): 575 """inherits visibility to parts""" 576 effect_objects = _get_effect_objects() 577 visibility_relations = [] 578 for part in self.parts: 579 if hasattr(part, "visibility_parameter") and not isinstance(part, effect_objects.Morpher): 580 visibility_relation = XIdentity( 581 part=part, whole=self, desc_ids=[part.visibility_parameter.desc_id], parameter=self.visibility_parameter, name="VisibilityInheritance") 582 visibility_relations.append(visibility_relation) 583 584 def specify_bounding_box_parameters(self): 585 """specifies bounding box parameters""" 586 default_diameter = self.diameter if self.diameter else max( 587 self.width, self.height, self.depth) 588 self.diameter_parameter = ULength( 589 name="Diameter", default_value=default_diameter) 590 self.default_width_parameter = ULength( 591 name="DefaultWidth", default_value=self.width) 592 self.default_height_parameter = ULength( 593 name="DefaultHeight", default_value=self.height) 594 self.default_depth_parameter = ULength( 595 name="DefaultDepth", default_value=self.depth) 596 self.bounding_box_parameters = [self.diameter_parameter, self.default_width_parameter, 597 self.default_height_parameter, self.default_depth_parameter] 598 599 def insert_bounding_box_parameters(self): 600 """inserts the bounding box parameters""" 601 self.bounding_box_u_group = UGroup( 602 *self.bounding_box_parameters, target=self.obj, name="BoundingBox") 603 604 def specify_bounding_box_relations(self): 605 """gives the custom object basic control over the bounding box diameter""" 606 diameter_relation = XRelation(part=self, whole=self, desc_ids=[SCALE_X, SCALE_Y, SCALE_Z], parameters=[self.diameter_parameter, self.default_width_parameter, self.default_height_parameter, self.default_depth_parameter], 607 formula=f"{self.diameter_parameter.name}/max({self.default_width_parameter.name};max({self.default_height_parameter.name};{self.default_depth_parameter.name}))") 608 609 610 class LineObject(VisibleObject): 611 """line objects consist of splines and only require a sketch material 612 613 Supports two rendering modes: 614 - Sketch mode (default): Uses Sketch & Toon post-effect 615 - Stroke mode (stroke_mode=True): Uses geometry-based strokes (MoGraph compatible, faster) 616 """ 617 618 def __init__(self, color=WHITE, plane="xy", arrow_start=False, arrow_end=False, draw_completion=0, opacity=1, helper_mode=False, draw_order="long_to_short", filled=False, fill_color=None, stroke_width=None, stroke_mode=False, **kwargs): 619 self.stroke_width = stroke_width if stroke_width is not None else 3.0 620 self.stroke_mode = stroke_mode 621 super().__init__(**kwargs) 622 self.color = color 623 self.plane = plane 624 self.arrow_start = arrow_start 625 self.arrow_end = arrow_end 626 self.draw_completion = draw_completion 627 self.opacity = opacity 628 self.draw_order = draw_order 629 if not helper_mode: 630 if self.stroke_mode: 631 # Geometry-based stroke rendering 632 self._setup_stroke_mode() 633 self.sketch_parameter_setup() # Sets up Draw, Opacity, Color parameters 634 else: 635 # Traditional Sketch & Toon rendering 636 self.set_sketch_material() 637 self.set_sketch_tag() 638 self.sketch_parameter_setup() 639 self.set_plane() 640 self.spline_length_parameter_setup() 641 self.parameters = [] 642 self.specify_parameters() 643 self.insert_parameters() 644 self.specify_relations() 645 self.action_parameters = [] 646 self.specify_action_parameters() 647 self.specify_creation_parameter() 648 self.insert_action_parameters() 649 self.specify_actions() 650 self.specify_creation() 651 self.sort_relations_by_priority() 652 self.filled = filled 653 self.fill_color = fill_color 654 if self.filled: 655 self.create_membrane() 656 657 def create_membrane(self): 658 custom_objects = _get_custom_objects() 659 if self.fill_color: 660 color = self.fill_color 661 else: 662 color = self.color 663 664 self.membrane = custom_objects.Membrane( 665 self, name=self.name + "Membrane", creation=True, color=color) 666 667 def _setup_stroke_mode(self): 668 """Set up geometry-based stroke rendering using StrokeGen. 669 670 In stroke mode: 671 - self.spline holds the original spline object 672 - self.obj becomes the StrokeGen (for position/rotation/UserData compatibility) 673 - The spline is a child of the stroke generator 674 675 This maintains the mental model that self.obj is "the thing" while 676 enabling geometry-based stroke rendering. 677 """ 678 from DreamTalk.objects.stroke_objects import STROKE_GEN_CODE, StrokeMaterial 679 680 # Store spline reference 681 self.spline = self.obj 682 683 # Create the stroke generator 684 self.stroke_gen = c4d.BaseObject(1023866) # Python Generator 685 self.stroke_gen[c4d.OPYTHON_CODE] = STROKE_GEN_CODE 686 self.stroke_gen[c4d.OPYTHON_OPTIMIZE] = False # Critical for MoGraph! 687 self.stroke_gen.SetName(self.name) 688 689 # Add Stroke Width UserData 690 bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_REAL) 691 bc[c4d.DESC_NAME] = "Stroke Width" 692 bc[c4d.DESC_DEFAULT] = 3.0 693 bc[c4d.DESC_MIN] = 0.1 694 bc[c4d.DESC_MAX] = 100.0 695 bc[c4d.DESC_STEP] = 0.5 696 bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_METER 697 self.stroke_width_id = self.stroke_gen.AddUserData(bc) 698 self.stroke_gen[self.stroke_width_id] = self.stroke_width 699 700 # Add Draw UserData (0-1 completion for animation) 701 bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_REAL) 702 bc[c4d.DESC_NAME] = "Draw" 703 bc[c4d.DESC_DEFAULT] = 1.0 704 bc[c4d.DESC_MIN] = 0.0 705 bc[c4d.DESC_MAX] = 1.0 706 bc[c4d.DESC_STEP] = 0.01 707 bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_PERCENT 708 self.stroke_draw_id = self.stroke_gen.AddUserData(bc) 709 self.stroke_gen[self.stroke_draw_id] = self.draw_completion 710 711 # Insert stroke gen into document 712 self.document.InsertObject(self.stroke_gen) 713 714 # Move spline under stroke gen 715 self.spline.Remove() 716 self.spline.InsertUnder(self.stroke_gen) 717 718 # Create and apply stroke material 719 self.stroke_material = StrokeMaterial( 720 color=self.color, 721 opacity=self.opacity, 722 name=f"{self.name}_StrokeMat" 723 ) 724 tag = self.stroke_gen.MakeTag(c4d.Ttexture) 725 tag[c4d.TEXTURETAG_MATERIAL] = self.stroke_material.obj 726 727 # Swap obj to be the stroke_gen - this is the "main" object now 728 self.obj = self.stroke_gen 729 730 def _stroke_sketch_parameter_setup(self): 731 """Set up parameters for stroke mode that mirror sketch parameters. 732 733 In stroke mode, Draw/Opacity/Color UserData already exists on 734 the stroke generator (self.obj). We just need to create parameter 735 wrappers that reference the existing desc_ids. 736 """ 737 # Draw parameter - already on stroke_gen via _setup_stroke_mode 738 self.draw_parameter = UCompletion( 739 name="Draw", default_value=self.draw_completion) 740 self.draw_parameter.desc_id = self.stroke_draw_id # Point to existing 741 742 # For Opacity and Color, we use the material directly 743 # Create parameter objects but they'll control the material 744 self.opacity_parameter = UCompletion( 745 name="Opacity", default_value=self.opacity) 746 self.color_parameter = UColor( 747 name="Color", default_value=self.color) 748 749 self.sketch_parameters = [self.draw_parameter, 750 self.opacity_parameter, self.color_parameter] 751 752 def spline_length_parameter_setup(self): 753 self.specify_spline_length_parameter() 754 self.insert_spline_length_parameter() 755 self.specify_spline_length_relation() 756 757 def sketch_parameter_setup(self): 758 if self.stroke_mode: 759 self._stroke_sketch_parameter_setup() 760 self._insert_stroke_sketch_parameters() 761 self._specify_stroke_sketch_relations() 762 else: 763 self.specify_sketch_parameters() 764 self.insert_sketch_parameters() 765 self.specify_sketch_relations() 766 767 def _insert_stroke_sketch_parameters(self): 768 """Insert additional UserData on stroke_gen for Opacity and Color. 769 770 Draw is already on the generator (read by generator code). 771 Opacity and Color are added for animation compatibility but 772 actually control the material programmatically. 773 """ 774 # Opacity UserData (for animation keyframing) 775 bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_REAL) 776 bc[c4d.DESC_NAME] = "Opacity" 777 bc[c4d.DESC_DEFAULT] = 1.0 778 bc[c4d.DESC_MIN] = 0.0 779 bc[c4d.DESC_MAX] = 1.0 780 bc[c4d.DESC_STEP] = 0.01 781 bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_PERCENT 782 self.stroke_opacity_id = self.obj.AddUserData(bc) 783 self.obj[self.stroke_opacity_id] = self.opacity 784 self.opacity_parameter.desc_id = self.stroke_opacity_id 785 786 # Color UserData (for animation keyframing) 787 bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_COLOR) 788 bc[c4d.DESC_NAME] = "Color" 789 self.stroke_color_id = self.obj.AddUserData(bc) 790 self.obj[self.stroke_color_id] = self.color 791 self.color_parameter.desc_id = self.stroke_color_id 792 793 def _specify_stroke_sketch_relations(self): 794 """Link UserData to stroke material. 795 796 In stroke mode: 797 - Draw is handled by the generator code directly (reads UserData by name) 798 - Opacity and Color are set at construction time on the material 799 800 For animated opacity/color, use set_opacity() and set_color() methods 801 or animate via Python Generator code. 802 """ 803 # No XPresso relations needed - generator reads Draw directly 804 # Opacity/Color are set on material at construction time 805 pass 806 807 def specify_creation(self): 808 """specifies the creation action""" 809 if self.stroke_mode: 810 # In stroke mode, creation animation is simpler - just animate the Draw UserData 811 # XAction uses XPresso which is complex; stroke_mode uses direct UserData access 812 # The Create animator will animate the creation_parameter which maps to draw 813 pass 814 else: 815 creation_action = XAction( 816 Movement(self.draw_parameter, (0, 1)), 817 target=self, completion_parameter=self.creation_parameter, name="Creation") 818 self.actions.append(creation_action) 819 820 def set_sketch_material(self): 821 self.sketch_material = SketchMaterial( 822 name=self.__class__.__name__, draw_order=self.draw_order, color=self.color, arrow_start=self.arrow_start, arrow_end=self.arrow_end, stroke_width=self.stroke_width) 823 824 def set_sketch_tag(self): 825 self.sketch_tag = SketchTag(target=self, material=self.sketch_material) 826 827 def specify_sketch_parameters(self): 828 self.draw_parameter = UCompletion( 829 name="Draw", default_value=self.draw_completion) 830 self.opacity_parameter = UCompletion( 831 name="Opacity", default_value=self.opacity) 832 self.color_parameter = UColor( 833 name="Color", default_value=self.color) 834 self.sketch_parameters = [self.draw_parameter, 835 self.opacity_parameter, self.color_parameter] 836 837 def insert_sketch_parameters(self): 838 self.draw_u_group = UGroup( 839 *self.sketch_parameters, target=self.obj, name="Sketch") 840 841 def specify_sketch_relations(self): 842 draw_relation = XIdentity(part=self.sketch_material, whole=self, desc_ids=[self.sketch_material.desc_ids["draw_completion"]], 843 parameter=self.draw_parameter) 844 opacity_relation = XIdentity(part=self.sketch_material, whole=self, desc_ids=[self.sketch_material.desc_ids["opacity"]], 845 parameter=self.opacity_parameter) 846 color_relation = XIdentity(part=self.sketch_material, whole=self, desc_ids=[self.sketch_material.desc_ids["color"]], 847 parameter=self.color_parameter) 848 849 def set_plane(self): 850 planes = {"xy": 0, "zy": 1, "xz": 2} 851 self.obj[c4d.PRIM_PLANE] = planes[self.plane] 852 853 def specify_spline_length_parameter(self): 854 self.spline_length_parameter = ULength(name="SplineLength") 855 856 def insert_spline_length_parameter(self): 857 self.spline_length_u_group = UGroup( 858 self.spline_length_parameter, target=self.obj, name="Spline") 859 860 def specify_spline_length_relation(self): 861 self.spline_length_relation = XSplineLength( 862 spline=self, whole=self, parameter=self.spline_length_parameter) 863 864 def draw(self, completion=1): 865 """specifies the draw animation""" 866 desc_id = self.draw_parameter.desc_id 867 animation = ScalarAnimation( 868 target=self, descriptor=desc_id, value_fin=completion) 869 self.obj[desc_id] = completion 870 return animation 871 872 def un_draw(self, completion=0): 873 """specifies the undraw animation""" 874 desc_id = self.draw_parameter.desc_id 875 animation = ScalarAnimation( 876 target=self, descriptor=desc_id, value_fin=completion) 877 self.obj[desc_id] = completion 878 return animation 879 880 def fade_in(self, completion=1): 881 """specifies the fade in animation""" 882 desc_id = self.opacity_parameter.desc_id 883 animation = ScalarAnimation( 884 target=self, descriptor=desc_id, value_fin=completion) 885 self.obj[desc_id] = completion 886 return animation 887 888 def fade_out(self, completion=0): 889 """specifies the fade out animation""" 890 desc_id = self.opacity_parameter.desc_id 891 animation = ScalarAnimation( 892 target=self, descriptor=desc_id, value_fin=completion) 893 self.obj[desc_id] = completion 894 return animation 895 896 def change_color(self, color): 897 """specifies the color change animation""" 898 desc_id = self.color_parameter.desc_id 899 animation = ColorAnimation( 900 target=self, descriptor=desc_id, vector=color) 901 self.obj[desc_id] = color 902 return animation 903 904 905 class SolidObject(VisibleObject): 906 """solid objects only require a fill material 907 908 Supports two rendering modes: 909 - Sketch mode (default): Uses Sketch & Toon post-effect for silhouette lines 910 - Stroke mode (stroke_mode=True): Uses geometry-based strokes (MoGraph compatible, faster) 911 """ 912 913 def __init__(self, filled=0, glowing=0, color=WHITE, fill_color=None, sketch_color=None, draw_order="long_to_short", draw_completion=0, arrow_start=False, arrow_end=False, fill_opacity=1, sketch_opacity=1, hidden_material=True, stroke_width=None, sketch_outline=True, sketch_folds=False, sketch_creases=True, sketch_border=False, sketch_contour=True, sketch_splines=True, stroke_mode=False, **kwargs): 914 """ 915 Base class for 3D solid objects with fill and sketch materials. 916 917 Args: 918 filled: Fill visibility (0-1) 919 glowing: Glow intensity (0-1) 920 color: Base color (used for both fill and sketch if not specified) 921 fill_color: Override fill color 922 sketch_color: Override sketch color 923 draw_order: Sketch draw order ("long_to_short" or "short_to_long") 924 draw_completion: Initial sketch draw completion (0-1) 925 arrow_start: Add arrow at spline start 926 arrow_end: Add arrow at spline end 927 fill_opacity: Fill opacity (0-1) 928 sketch_opacity: Sketch opacity (0-1) 929 hidden_material: Control hidden line rendering: 930 - True (default): Same material for hidden lines (solid look) 931 - False/None: No hidden material (X-ray see-through effect) 932 - Material object: Custom hidden line material 933 stroke_width: Override sketch line thickness 934 sketch_outline: Enable outline rendering (default True) 935 sketch_folds: Enable fold lines (default False) 936 sketch_creases: Enable crease lines (default True) 937 sketch_border: Enable border lines (default False) 938 sketch_contour: Enable contour lines (default True) 939 sketch_splines: Enable spline rendering (default True) 940 stroke_mode: Use geometry-based strokes instead of Sketch & Toon (default False) 941 **kwargs: Parent class arguments 942 """ 943 self.stroke_mode = stroke_mode 944 self.stroke_width = stroke_width if stroke_width is not None else 3.0 945 self.filled = filled 946 self.glowing = glowing 947 self.color = color 948 self.fill_color = fill_color 949 self.sketch_color = sketch_color 950 self.draw_order = draw_order 951 self.draw_completion = draw_completion 952 self.arrow_start = arrow_start 953 self.arrow_end = arrow_end 954 self.sketch_opacity = sketch_opacity 955 self.fill_opacity = fill_opacity 956 self.hidden_material = hidden_material 957 self.sketch_outline = sketch_outline 958 self.sketch_folds = sketch_folds 959 self.sketch_creases = sketch_creases 960 self.sketch_border = sketch_border 961 self.sketch_contour = sketch_contour 962 self.sketch_splines = sketch_splines 963 super().__init__(**kwargs) 964 self.derive_colors() 965 # fill setup 966 self.set_fill_material() 967 self.set_fill_tag() 968 self.fill_parameter_setup() 969 # sketch setup 970 if self.stroke_mode: 971 # Geometry-based stroke rendering 972 self._setup_stroke_mode() 973 self.sketch_parameter_setup() # Sets up Draw, Opacity, Color parameters 974 else: 975 # Traditional Sketch & Toon rendering 976 self.set_sketch_material() 977 self.set_sketch_tag() 978 self.sketch_parameter_setup() 979 self.parameters = [] 980 self.specify_parameters() 981 self.insert_parameters() 982 self.specify_relations() 983 self.action_parameters = [] 984 self.specify_action_parameters() 985 self.specify_creation_parameter() 986 self.insert_action_parameters() 987 self.specify_actions() 988 self.specify_creation() 989 self.sort_relations_by_priority() 990 991 def derive_colors(self): 992 if not self.fill_color: 993 self.fill_color = self.color 994 if not self.sketch_color: 995 self.sketch_color = self.color 996 997 def _setup_stroke_mode(self): 998 """Set up geometry-based stroke rendering using MeshStroke. 999 1000 In stroke mode for solids: 1001 - self.mesh holds the original mesh object 1002 - self.obj becomes the MeshStroke wrapper (for position/rotation/UserData compatibility) 1003 - The mesh is nested inside: StrokeGen > SilhouetteSplineGen > Mesh 1004 1005 This extracts silhouette edges and renders them as geometry-based strokes. 1006 """ 1007 from DreamTalk.objects.stroke_objects import STROKE_GEN_CODE, SILHOUETTE_SPLINE_GEN_CODE, StrokeMaterial 1008 1009 # Store mesh reference 1010 self.mesh = self.obj 1011 1012 # Create the silhouette spline generator 1013 self.silhouette_gen = c4d.BaseObject(1023866) # Python Generator 1014 self.silhouette_gen[c4d.OPYTHON_CODE] = SILHOUETTE_SPLINE_GEN_CODE 1015 self.silhouette_gen[c4d.OPYTHON_OPTIMIZE] = False # Critical for MoGraph! 1016 self.silhouette_gen.SetName("SilhouetteSpline") 1017 1018 # Create the stroke generator 1019 self.stroke_gen = c4d.BaseObject(1023866) # Python Generator 1020 self.stroke_gen[c4d.OPYTHON_CODE] = STROKE_GEN_CODE 1021 self.stroke_gen[c4d.OPYTHON_OPTIMIZE] = False # Critical for MoGraph! 1022 self.stroke_gen.SetName(self.name) 1023 1024 # Add Stroke Width UserData to stroke gen 1025 bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_REAL) 1026 bc[c4d.DESC_NAME] = "Stroke Width" 1027 bc[c4d.DESC_DEFAULT] = 3.0 1028 bc[c4d.DESC_MIN] = 0.1 1029 bc[c4d.DESC_MAX] = 100.0 1030 bc[c4d.DESC_STEP] = 0.5 1031 bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_METER 1032 self.stroke_width_id = self.stroke_gen.AddUserData(bc) 1033 self.stroke_gen[self.stroke_width_id] = self.stroke_width 1034 1035 # Add Draw UserData (0-1 completion for animation) 1036 bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_REAL) 1037 bc[c4d.DESC_NAME] = "Draw" 1038 bc[c4d.DESC_DEFAULT] = 1.0 1039 bc[c4d.DESC_MIN] = 0.0 1040 bc[c4d.DESC_MAX] = 1.0 1041 bc[c4d.DESC_STEP] = 0.01 1042 bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_PERCENT 1043 self.stroke_draw_id = self.stroke_gen.AddUserData(bc) 1044 self.stroke_gen[self.stroke_draw_id] = self.draw_completion 1045 1046 # Insert stroke gen into document 1047 self.document.InsertObject(self.stroke_gen) 1048 1049 # Build hierarchy: StrokeGen > SilhouetteSplineGen > Mesh 1050 self.silhouette_gen.InsertUnder(self.stroke_gen) 1051 self.mesh.Remove() 1052 self.mesh.InsertUnder(self.silhouette_gen) 1053 1054 # Create and apply stroke material 1055 self.stroke_material = StrokeMaterial( 1056 color=self.sketch_color, 1057 opacity=self.sketch_opacity, 1058 name=f"{self.name}_StrokeMat" 1059 ) 1060 tag = self.stroke_gen.MakeTag(c4d.Ttexture) 1061 tag[c4d.TEXTURETAG_MATERIAL] = self.stroke_material.obj 1062 1063 # Swap obj to be the stroke_gen - this is the "main" object now 1064 self.obj = self.stroke_gen 1065 1066 def sketch_parameter_setup(self): 1067 if self.stroke_mode: 1068 self._stroke_sketch_parameter_setup() 1069 self._insert_stroke_sketch_parameters() 1070 self._specify_stroke_sketch_relations() 1071 else: 1072 self.specify_sketch_parameters() 1073 self.insert_sketch_parameters() 1074 self.specify_sketch_relations() 1075 1076 def _stroke_sketch_parameter_setup(self): 1077 """Set up parameters for stroke mode that mirror sketch parameters.""" 1078 # Draw parameter - already on stroke_gen via _setup_stroke_mode 1079 self.draw_parameter = UCompletion( 1080 name="Draw", default_value=self.draw_completion) 1081 self.draw_parameter.desc_id = self.stroke_draw_id # Point to existing 1082 1083 # For Opacity and Color, we use the material directly 1084 self.opacity_parameter = UCompletion( 1085 name="SketchOpacity", default_value=self.sketch_opacity) 1086 self.color_parameter = UColor( 1087 name="Color", default_value=self.sketch_color) 1088 1089 self.sketch_parameters = [self.draw_parameter, 1090 self.opacity_parameter, self.color_parameter] 1091 1092 def _insert_stroke_sketch_parameters(self): 1093 """Insert additional UserData on stroke_gen for Opacity and Color.""" 1094 # Opacity UserData (for animation keyframing) 1095 bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_REAL) 1096 bc[c4d.DESC_NAME] = "SketchOpacity" 1097 bc[c4d.DESC_DEFAULT] = 1.0 1098 bc[c4d.DESC_MIN] = 0.0 1099 bc[c4d.DESC_MAX] = 1.0 1100 bc[c4d.DESC_STEP] = 0.01 1101 bc[c4d.DESC_UNIT] = c4d.DESC_UNIT_PERCENT 1102 self.stroke_opacity_id = self.obj.AddUserData(bc) 1103 self.obj[self.stroke_opacity_id] = self.sketch_opacity 1104 self.opacity_parameter.desc_id = self.stroke_opacity_id 1105 1106 # Color UserData (for animation keyframing) 1107 bc = c4d.GetCustomDataTypeDefault(c4d.DTYPE_COLOR) 1108 bc[c4d.DESC_NAME] = "Color" 1109 self.stroke_color_id = self.obj.AddUserData(bc) 1110 self.obj[self.stroke_color_id] = self.sketch_color 1111 self.color_parameter.desc_id = self.stroke_color_id 1112 1113 def _specify_stroke_sketch_relations(self): 1114 """Link UserData to stroke material. 1115 1116 In stroke mode: 1117 - Draw is handled by the generator code directly (reads UserData by name) 1118 - Opacity and Color are set at construction time on the material 1119 """ 1120 # No XPresso relations needed - generator reads Draw directly 1121 pass 1122 1123 def specify_creation(self): 1124 """specifies the creation action""" 1125 if self.stroke_mode: 1126 # In stroke mode, skip XAction - use direct UserData access 1127 # Create animator will animate the creation_parameter directly 1128 pass 1129 else: 1130 creation_action = XAction( 1131 Movement(self.fill_parameter, (1 / 3, 1), 1132 output=(0, self.fill_opacity)), 1133 Movement(self.draw_parameter, (0, 2 / 3)), 1134 target=self, completion_parameter=self.creation_parameter, name="Creation") 1135 1136 def fill_parameter_setup(self): 1137 self.specify_fill_parameter() 1138 self.insert_fill_parameter() 1139 self.specify_fill_relation() 1140 1141 def set_fill_material(self): 1142 self.fill_material = FillMaterial( 1143 name=self.name, fill=self.filled, color=self.fill_color) 1144 1145 def set_fill_tag(self): 1146 self.fill_tag = FillTag(target=self, material=self.fill_material) 1147 1148 def specify_fill_parameter(self): 1149 self.fill_parameter = UCompletion( 1150 name="Fill", default_value=self.filled) 1151 self.glow_parameter = UCompletion( 1152 name="Glow", default_value=self.glowing) 1153 self.fill_parameters = [self.fill_parameter, self.glow_parameter] 1154 1155 def insert_fill_parameter(self): 1156 self.fill_u_group = UGroup( 1157 *self.fill_parameters, target=self.obj, name="Solid") 1158 1159 def specify_fill_relation(self): 1160 self.fill_relation = XRelation(part=self.fill_material, whole=self, desc_ids=[self.fill_material.desc_ids["transparency"]], 1161 parameters=[self.fill_parameter], formula=f"1-{self.fill_parameter.name}") 1162 self.glow_relation = XRelation(part=self.fill_material, whole=self, desc_ids=[self.fill_material.desc_ids["glow_brightness"]], 1163 parameters=[self.glow_parameter], formula=f"{self.glow_parameter.name}") 1164 1165 def fill(self, completion=1): 1166 """specifies the fill animation""" 1167 desc_id = self.fill_parameter.desc_id 1168 animation = ScalarAnimation( 1169 target=self, descriptor=desc_id, value_fin=completion) 1170 self.obj[desc_id] = completion 1171 return animation 1172 1173 def un_fill(self, completion=0): 1174 """specifies the unfill animation""" 1175 desc_id = self.fill_parameter.desc_id 1176 animation = ScalarAnimation( 1177 target=self, descriptor=desc_id, value_fin=completion) 1178 self.obj[desc_id] = completion 1179 return animation 1180 1181 def set_sketch_material(self): 1182 self.sketch_material = SketchMaterial( 1183 name=self.__class__.__name__, draw_order=self.draw_order, color=self.sketch_color, arrow_start=self.arrow_start, arrow_end=self.arrow_end, stroke_width=self.stroke_width) 1184 1185 def set_sketch_tag(self): 1186 self.sketch_tag = SketchTag( 1187 target=self, 1188 material=self.sketch_material, 1189 outline=self.sketch_outline, 1190 folds=self.sketch_folds, 1191 creases=self.sketch_creases, 1192 border=self.sketch_border, 1193 contour=self.sketch_contour, 1194 splines=self.sketch_splines, 1195 hidden_material=self.hidden_material 1196 ) 1197 1198 def specify_sketch_parameters(self): 1199 self.draw_parameter = UCompletion( 1200 name="Draw", default_value=self.draw_completion) 1201 self.opacity_parameter = UCompletion( 1202 name="SketchOpacity", default_value=self.sketch_opacity) 1203 self.color_parameter = UColor( 1204 name="Color", default_value=self.sketch_color) 1205 self.sketch_parameters = [self.draw_parameter, 1206 self.opacity_parameter, self.color_parameter] 1207 1208 def insert_sketch_parameters(self): 1209 self.draw_u_group = UGroup( 1210 *self.sketch_parameters, target=self.obj, name="Sketch") 1211 1212 def specify_sketch_relations(self): 1213 draw_relation = XIdentity(part=self.sketch_material, whole=self, desc_ids=[self.sketch_material.desc_ids["draw_completion"]], 1214 parameter=self.draw_parameter) 1215 opacity_relation = XIdentity(part=self.sketch_material, whole=self, desc_ids=[self.sketch_material.desc_ids["opacity"]], 1216 parameter=self.opacity_parameter) 1217 color_relation = XIdentity(part=self.sketch_material, whole=self, desc_ids=[self.sketch_material.desc_ids["color"]], 1218 parameter=self.color_parameter) 1219 1220 def draw(self, completion=1): 1221 """specifies the draw animation""" 1222 desc_id = self.draw_parameter.desc_id 1223 animation = ScalarAnimation( 1224 target=self, descriptor=desc_id, value_fin=completion) 1225 self.obj[desc_id] = completion 1226 return animation 1227 1228 def un_draw(self, completion=0): 1229 """specifies the undraw animation""" 1230 desc_id = self.draw_parameter.desc_id 1231 animation = ScalarAnimation( 1232 target=self, descriptor=desc_id, value_fin=completion) 1233 self.obj[desc_id] = completion 1234 return animation 1235 1236 def glow(self, completion=1): 1237 """specifies the glow animation""" 1238 desc_id = self.glow_parameter.desc_id 1239 animation = ScalarAnimation( 1240 target=self, descriptor=desc_id, value_fin=completion) 1241 self.obj[desc_id] = completion 1242 return animation 1243 1244 def un_glow(self, completion=0): 1245 """specifies the unglow animation""" 1246 desc_id = self.glow_parameter.desc_id 1247 animation = ScalarAnimation( 1248 target=self, descriptor=desc_id, value_fin=completion) 1249 self.obj[desc_id] = completion 1250 return animation