Jump to content

zipit

Limited Member
  • Posts

    15
  • Joined

  • Last visited

  • Days Won

    1

zipit last won the day on March 13 2021

zipit had the most liked content!

Profile Information

  • First Name
    Ferdinand
  • Last Name
    -
  • Location
    Germany

Recent Profile Visitors

The recent visitors block is disabled and is not being shown to other users.

zipit's Achievements

Newbie

Newbie (1/14)

  • Dedicated
  • First Post
  • Collaborator
  • Reacting Well
  • Week One Done

Recent Badges

16

Reputation

2

Community Answers

  1. zipit

    Copy / Delete track

    Hey @everyone, there has been some great help given here, but since the OP never talked about two scripts or the clipboard, I just wanted emphasize that the clipboard is not required at all for what you, @Lumes, want to do. Since a track, i.e., `CTrack` is derived from `BaseList2D` and therefore `C4DAtom`, you can simply copy a track with `C4DAtom.GetClone()`. You can then insert such track into another `BaseList2D` with `.InsertTrackSorted()`. The major problem in your feature wish lies in detecting the the currently active track(s). This is a problem because, there is more than one timeline manager (up to four can exist which do not share their selection state) and that you cannot access the content (tracks) of a timeline manager directly. What you effectively will have to do, is to iterate over the scene graph, to find all tracks in it, and then check track by track for each node if a track is selected in one of the TL Managers via `GeListNode.GetNBit(c4d.NBIT_TLx_SELECT)` where x is the TL number. There is more to explore, especially regarding how to select a target node to copy to, you could for example use a popup menu for that or selection states. But I just wanted to make clear that it is very much possible to do this in one script. If you have more questions, I would invite you to contact the Maxon SDK support at Home | PluginCafΓ© (maxon.net). Cheers, Ferdinand PS: Without wanting to be rude, since I pay nothing for the service of this forum, but does this forum really not have inline code/typewriter emphasis? Or am I just overlooking the feature?
  2. Hello Marco, there is the Python Resources page in our Python documentation which lines out some internal and external learning resources. The primary learning resource for the Cinema 4D Python API is the The Official Cinema 4D R25.113 Python Documentation and its accompanying Official Python API examples for Cinema 4D GitHub repository. Finally, there is the PluginCafΓ© forum where the Maxon SDK-Team provides development support. Cheers, Ferdinand
  3. I do not really see what should crash here in my example. Although we are outside of the main thread in a Python node, modifying parameters is fine in such context. Which is what we are doing. Modifying a BaseTime paramater of a key in Python programming Tag scenario would also be fine, I would even say that is the exact usage scenario of a (Python) Tag. Inserting nodes or invoking events would be off-limits in both scenarios, but that is not what we are doing here. What could go wrong with such Xpresso/Python setups, is that one could create feed-back loops, but for that one would have to use a parameter both as in input and output. But we are not doing this either. And this should also not crash; it is just that it would constantly evaluate each frame and by that converge to some value. I am also not sure what you mean by evaluating "the tree". We are here in the expression pass of the scene evaluation in Xpresso. I do not really see how this would make modifying parameters off limits, as this is what the expression pass is for. While I agree that this ugly and also somewhat pointless, a Python Programming Tag would be a much better solution, I would disagree that this will crash.
  4. Hello @everyone, Yes, it is, but it is a bit clunky. While this is probably practically true, it is technically not. You can always spice up your Xpresso with Python, which also means you can do whatever you want to do. The question then is how "Xpresso-esque" the results are when 99% of the stuff happens inside a Python node. There is also the problem of Xpresso that it really dislikes linking classic API node types other than BaseObjects, i.e., objects, so that one must use the user data trick for linking other node types. The attached example has a null called "Xpresso" which has a user data field "Slot". With this Slot field we pass in some node into our Xpresso graph. In this case the track of an animation. Also passed in is the x-position of another null. The final result is then that one can select a key via its index. The frame time value of that key is then driven by the position x-component of that driving null. I.e., effectively, when the null is at (40, 0, 0) units, then the second key in that track will be at 40 frames in the example file. You could animate that null and the key frame value will follow. The result would be rather non-sensical, as that track animation would change over time, but it works ^^ How practical all that is, is up to you. It is certainly technically possible. But it makes much more sense to do such things right away as a Python Programming Tag IMHO. But opinions can differ here, and some people find such mixed setups useful. Cheers, Ferdinand xpresso_keyframes.c4d
  5. Well, the answer to that is a sound yesn't ^^ You can iterate over all GeListNodes with that function and filter by type. But it will only have that magic unpacking feature for BaseObject and BaseTag, i.e., that it knows that it should branch out into tags from objects. As I said in the description, Cinema 4D's classic API scene graph is complex. And it is also in a certain sense disjunct, as you cannot just traverse down one giant tree. E.g., if you would throw a BaseDocument intop that function, it would not spit out the objects, tags, materials, shaders, etc. which are in that document. Because while a BaseDocument is also a GeListNode, it does not organize its content directly as children. Instead, the scene graph has been severed intentionally in multiple places, so that you have to call specific methods to pick up these severed endings. Which in turn makes it labor intensive to do the whole semantic relation "throw something in and get everything somehow related out"-thing. But if ou have for example a tree of layer objects or a shader-tree, you could use the same function as they are all based on GeListNode. Cheers, Ferdinand
  6. Hi @Robert Krawczyk, node iteration is a bit of a hornet's nest in the Cinema 4D SDK, because although it might look like just a "trivial" graph walk, it can become quite complex due to all the corner cases that lurk in the depth of how Cinema 4D does organize its scene graph. And graph walks are also often not trivial in itself when one has to meet certain conditions like starting at an arbitrary point which cannot be "overshot" or having crazy requirements like not producing stack overflows πŸ˜‰ We are aware of the problem at the SDK-Team and have it on our bucket list, i.e., want to provide an interface for it at some point. But for now, it must be done manually. Your problem has two components: Finding all Xpresso tags in a scene and iterating over all their nodes. For the latter GetDown()/Next() were the correct approach in principal, but the devil is in the detail here, one can get easily lost in these graphs. The solution provided by @JED has certainly its strengths in its simplicity, but it will not yield any nodes that are within a group node. The solution provided below uses a more abstract approach, which allows one to use it for example to also retrieve all the Xpresso tags in the scene. In practice it would be nice if we already had an node iteration interface in the SDK for the classic API, but currently we do not. My example provides a simple version, tailored to the specific case. Which can be treated as a black box if one feels uncomfortable with the code. The core logic of what you want to do, is then only this relatively accessible bit of code: # We iterate over all Xpresso tags in the document. We pass in the first # object in the scene and specify that we are only interested in nodes of # type c4d.Texpresso (Xpresso tags). for xpressoTag in NodeIterator(doc.GetFirstObject(), c4d.Texpresso): # Print out some stuff about the currently yielded Xpresso tag. name, nid = xpressoTag.GetName(), id(xpressoTag) print(f"The Xpresso tag {name} at {nid} has the nodes:") # Get its master node and root node in the Xpresso graph. masterNode = xpressoTag.GetNodeMaster() root = masterNode.GetRoot() # And iterate over all nodes in that root node. for xpressoNode in NodeIterator(root): print(f"\t{xpressoNode}") If ran on a scene, it looks like this: Cheers, Ferdinand The full code: """Example for iterating over all Xpresso nodes in a scene. Node iteration can be quite a complex topic in Cinema 4D due to the many graph relations that are contained in a document and the problems that arise from recursive solutions for the problem - stack overflows or in Python the safety measures of Python preventing them. We have this topic on our bucket list in the SDK-Team, but for now one has to approach it with self-provided solutions. This is an example pattern for a node iterator (which does not take caches into account). One can throw it at any kind of GeListNode and it will yield in a stack-safe manner all next-siblings and descendants of a node. How this iteration is carried out (depth or breadth first), to include any kind of siblings, not just next-siblings, to include also ancestors and many things more could be done differently. This pattern is tailored relatively closely to what the topic demanded. A more versatile solution would have to be provided in the SDK. """ import c4d # This is a node iteration implementation. To a certain degree this can be # treated as a black box and does not have to be understood. This will # however change when one wants to include other use-case scenarios for which # one would have to modify it. def NodeIterator(node, types=None): """An iterator for a GeListNode node graph with optional filtering. Will yield all downward siblings and all descendants of a node. Will not yield any ancestors of the node. Is stack overflow (prevention) safe due to being truly iterative. Alsob provides a filter mechanism which accepts an ineteger type symbol filter for the yielded nodes. Args: node (c4d.GeListNode): The starting node for which to yield all next- siblings and descendants. types (None | int | tuple[int]), optional): The optional type filter. If None, all nodes will bey yielded. If not None, only nodes that inherit from at least one of the type symbols defined in the filter tuple will be yielded. Defaults to None. Yields: c4d.GeListNode: A node in the iteration sequence. Raises: TypeError: On argument type oopses. """ # Some argument massaging and validation. if not isinstance(node, c4d.GeListNode): msg = "Expected a GeListNode or derived class, got: {0}" raise TypeError(msg.format(node.__class__.__name__)) if isinstance(types, int): types = (types, ) if not isinstance(types, (tuple, list, type(None))): msg = "Expected a tuple, list or None, got: {0}" raise TypeError(msg.format(types.__class__.__name__)) def iterate(node, types=None): """The iteration function, walking a graph depth first. Args: same as outer function. Yields: same as outer function. """ # This or specifically the usage of the lookup later is not ideal # performance-wise. But without making this super-complicated, this # is the best solution I see here (as we otherwise would end up with # three versions of what the code does below). visisted = [] terminal = node.GetUp() if isinstance(node, c4d.GeListNode) else None while node: if node not in visisted: if types is None or any([node.IsInstanceOf(t) for t in types]): yield node visisted.append(node) if node.GetDown() and node.GetDown() not in visisted: node = node.GetDown() elif node.GetNext(): node = node.GetNext() else: node = node.GetUp() if node == terminal: break # For each next-sibling or descendant of the node passed to this call: for iterant in iterate(node): # Yield it if it does match our type filter. if types is None or any([iterant.IsInstanceOf(t) for t in types]): yield iterant # And iterate its tags if it is BaseObject. if isinstance(iterant, c4d.BaseObject): for tag in iterate(iterant.GetFirstTag(), types): yield tag def main(): """Entry point. """ # We iterate over all Xpresso tags in the document. We pass in the first # object in the scene and specify that we are only interested in nodes of # type c4d.Texpresso (Xpresso tags). for xpressoTag in NodeIterator(doc.GetFirstObject(), c4d.Texpresso): # Print out some stuff about the currently yielded Xpresso tag. name, nid = xpressoTag.GetName(), id(xpressoTag) print(f"The Xpresso tag {name} at {nid} has the nodes:") # Get its master node and root node in the Xpresso graph. masterNode = xpressoTag.GetNodeMaster() root = masterNode.GetRoot() # And iterate over all nodes in that root node. for xpressoNode in NodeIterator(root): print(f"\t{xpressoNode}") if __name__ == '__main__': main()
  7. Hi @bjlotus, This was not me be pedantic about terminology and you are computing the mean and the normals. I was just pointing out that you were just computing the normal for one of the planes in a polygon - unless I am overlooking something here. But for quads there are two, one for each triangle in the quad. Below is an illustration of a quad which I did manually triangulate for illustration purposes. You can see the vertex normals in white (which are each equal to the normal of the triangle/plane the lie in). And two plane normals in black, one for each triangle. The actual polygon normal is then the arithmetic mean of both plane normals. So when you just pick one vertex in a quad to calculate the polygon normal, i.e., simply say one of the white normals is the polygon normal, then you assume that both tris of the quad lie in the same plane, i.e., that the quad is coplanar. Which of course can happen, but there is no guarantee. Quads are today usually quite close to being coplanar (and not that comically deformed as my example), but you will still introduce a little bit of imprecision by assuming all quads to be coplanar. Cheers, Ferdinand
  8. Hi @Cairyn, hm, I did not want to imply otherwise. I was just polite and said hi to everyone before waltzing in here πŸ˜‰. Assuming my "hi @Cairyn" was the cause for that misunderstanding. Cheers, Ferdinand
  9. Hi @bjlotus, hi @Cairyn, there is one problem with your normals calculation. You calculate just a vertex normal in the polygon and then declare it to be the polygon normal πŸ˜‰ (your variable "cross"), i.e., you assume all your polygons to be coplanar. With today's high density meshes you can probably get away with that to a certain degree, but it would not hurt to actually calculate the mean vertex normal of a polygon, a.k.a., the polygon normal to have a precise result. The problem with your "flattening" is that it is not one. Unless I have overread something here in the thread, the missing keyword would be a point-plane projection. You just translate all selected points by a fixed amount, which was if I understood that correctly only a work in progress, but obviously won't work. Things could also be written a bit more tidely and compact in a pythonic fashion, but that has very little impact on the performance and is mostly me being nitpicky πŸ˜‰ I did attach a version of how I would do this at the end (there are of course many ways to do this, but maybe it will help you). Cheers, Ferdinand """'Flattens' the active polygon selection of a PolygonObject. Projects the points which are part of the active polygon selection into the mean plane of the polygon selection. """ import c4d def GetMean(collection): """Returns the mean value of collection. In Python 3.4+ we could also use statistics.mean() instead. Args: collection (iterable): An iterable of types that support addition, whose product supports multiplication. Returns: any: The mean value of collection. """ return sum(collection) * (1. / len(collection)) def GetPolygonNormal(cpoly, points): """Returns the mean of all vertex normals of a polygon. You could also use PolygonObject.CreatePhongNormals, in case you expect to always have a phong tag present and want to respect phong breaks. Args: cpoly (c4d.Cpolygon): A polygon. points (list[c4d.vector]): All the points of the object. Returns: c4d.Vector: The polygon normal. """ # The points in question. a, b, c, d = (points[cpoly.a], points[cpoly.b], points[cpoly.c], points[cpoly.d]) points = [a, b, c] if c == d else [a, b, c, d] step = len(points) - 1 # We now could do some mathematical gymnastics to figure out just two # vertices we want to use to compute the normal of the two triangles in # the quad. But this would not only be harder to read, but also most # likely slower. So we are going to be 'lazy' and just compute all vertex # normals in the polygon and then compute the mean value for them. normals = [] for i in range(step + 1): o = points[i - 1] if i > 0 else points[step] p = points[i] q = points[i + 1] if i < step else points[0] # The modulo operator is the cross product. normals.append(((p - q)) % (p - o)) # Return the normalized (with the inversion operator) mean of them. return ~GetMean(normals) def ProjectOnPlane(p, q, normal): """Projects p into the plane defined by q and normal. Args: p (c4d.Vector): The point to project. q (c4d.Vector): A point in the plane. normal (c4d.Vector): The normal of the plane (expected to be a unit vector). Returns: c4d.Vector: The projected point. """ # The distance from p to the plane. distance = (p - q) * normal # Return p minus that distance. return p - normal * distance def FlattenPolygonSelection(node): """'Flattens' the active polygon selection of a PolygonObject. Projects the points which are part of the active polygon selection into the mean plane of the polygon selection. Args: node (c4d.PolygonObject): The polygon node. Returns: bool: If the operation has been carried out or not. Raises: TypeError: When node is not a c4d.PolygonObject. """ if not isinstance(op, c4d.PolygonObject): raise TypeError("Expected a PolygonObject for 'node'.") # Get the point, polygons and polygon selection of the node. points = node.GetAllPoints() polygons = node.GetAllPolygons() polygonCount = len(polygons) baseSelect = node.GetPolygonS() # This is a list of booleans, e.g., for a PolygonObject with three # polygons and the first and third polygon being selected, it would be # [True, False, True]. polygonSelection = baseSelect.GetAll(polygonCount) # The selected polygons and the points which are part of these polygons. selectedPolygonIds = [i for i, v in enumerate(polygonSelection) if v] selectedPolygons = [polygons[i] for i in selectedPolygonIds] selectedPointIds = list({p for cpoly in selectedPolygons for p in [cpoly.a, cpoly.b, cpoly.c, cpoly.d]}) selectedPoints = [points[i] for i in selectedPointIds] # There is nothing to do for us here. if not polygonCount or not selectedPolygons: return False # The polygon normals, the mean normal and the mean point. The mean point # and the mean normal define the plane we have to project into. Your # image implied picking the bottom plane of the bounding box of the # selected vertices as the origin of the plane, you would have to do that # yourself. Not that hard to do, but I wanted to keep things short ;) polygonNormals = [GetPolygonNormal(polygons[pid], points) for pid in selectedPolygonIds] meanNormal = ~GetMean(polygonNormals) meanPoint = GetMean(selectedPoints) # Project all the selected points. for pid in selectedPointIds: points[pid] = ProjectOnPlane(points[pid], meanPoint, meanNormal) # Create an undo, write the points back into the polygon node and tell # it that we did modify it. doc.StartUndo() doc.AddUndo(c4d.UNDOTYPE_CHANGE, node) node.SetAllPoints(points) doc.EndUndo() node.Message(c4d.MSG_UPDATE) # Things went without any major hiccups :) return True def main(): """Entry point. """ if FlattenPolygonSelection(op): c4d.EventAdd() if __name__ == '__main__': main()
  10. Hi @stepahneFont, using `c4d.CallCommand` is perfectly possible in any Python code run by Cinema 4D. You however never select your cube object, which in turn makes the job for the "Frame Selected Objects" function, i.e., c4d.CallCommand(12151), rather hard πŸ˜‰ Try something like this: """Runs the Frame Selected Objects CallCommand. """ import c4d def main(): """ """ obj = c4d.BaseObject(c4d.Ocube) doc.InsertObject(obj) # Since we want to rely on a active object with our CallCommand, i.e., # frame it, we have to make it actually any active one. Use # c4d.SELECTION_ADD if you want to add that object to the current # selection instead of creating a new one. doc.SetActiveObject(obj, c4d.SELECTION_NEW) # After that we can run the CallCommand on that modified scene. c4d.CallCommand(12151) # Frame Selected Objects if __name__=='__main__': main() Cheers, Ferdinand
  11. Hi @stepahneFont, hi @Mike A, I think the op is looking for a programmatic solution and not a short cut @Mike A, but I might be wrong about this. About the question - doing that is not impossible but I might be more complicated than anticipated by you. In principal Cinema 4D does not expose its "Frame Selected Objects" functionality in its Python SDK aside from the CallCommand AFAIK (I am assuming that you are on Python). So you would have to do that on your own, which is basically just some math. Replicating one to one what "Frame Selected Objects" does, can however be tricky, due to all the special cases that are associated with a camera object - projection modes, zoom, etc. Also the framing part can be a bit more involved, so when you want to place the camera in such way that the target object fills the frame. Aligning the camera view direction with the object is however not so hard, you can see an example at the end. This is however not what "Frame Selected Objects" does, its logic is more sophisticated and it tries to preserve to current camera orientation. But since you did only mention centering, this might actually be what you want to do. Cheers, Ferdinand """Orients the camera of the active viewport towards the selected object. """ import c4d def main(): """ """ # op is predefined in a script as the currently selected object. doc is # the currently active document. if op is None: raise RuntimeError("Please select an object.") # Get the active BaseDraw, i.e., editor view. bd = doc.GetActiveBaseDraw() if bd is None: raise RuntimeError("Could not access active view.") # Get the attached camera. camera = bd.GetSceneCamera(doc) if camera is None: raise RuntimeError("Could not access active view camera.") # Construct a frame/transform for our camera looking at the object. # The v3/k/z axis of the frame is just the unit vector from the camera # to the object. z = ~(op.GetMg().off - camera.GetMg().off) # Now we just make up the rest for it, for that we need an up vector that # is not (anti) parallel to z (because the cross product is the zero vector # for parallel vectors). up = c4d.Vector(0, 1, 0) up = up if (1. - abs(z * up)) >= .001 else c4d.Vector(0.001, 1, 0) # Build the rest of the frame. x = ~(up % z) y = ~(z % x) # And create a matrix/transform out of it. mg = c4d.Matrix(off=camera.GetMg().off, v1=x, v2=y, v3=z) # Set the new matrix for the camera object and create an undo for it. doc.StartUndo() doc.AddUndo(c4d.UNDOTYPE_CHANGE, camera) camera.SetMg(mg) doc.EndUndo() c4d.EventAdd() # Execute main() if __name__=='__main__': main()
  12. zipit

    Solar system simulation

    That is pretty cool @srek, much more detailed than mine with fancy things like rotation around own axis and inclination. There is only one problem; in this system something terrible has happened and our beloved sun has collapsed into a white dwarf πŸ˜‰.
  13. zipit

    Solar system simulation

    Hi, the file contains a Python Generator object and not a Python (Programming) tag. I actually see just now that you are on R14, I digged up a R14 license and made the modifications necessary to run the file in principle on R14, the object color thing won't work there though. About the programming thing: I was under the impression that this a programming question since this is a programming forum πŸ˜‰ But my little file might be useful in the sense that it could be helpful to find a good scaling factor for a hand crafted system, because when you use real world values, you won't be able to see much. edit: almost forgot the file πŸ˜‰ Its attached at the end. Cheers, Ferdinand solar_system_14.c4d
  14. zipit

    Solar system simulation

    Hi @israeldavid, hi @srek, I think that is a fun topic πŸ˜„ . But I also think you underestimate a bit the size of our solar system ^^ Neptune, the last planet in our system (sorry, Pluto 😞 ), has a radius of about 25,000 km but is about 4.5 billion kilometers away from the sun. So its not hard to imagine that when you work with real data, that you won't see much except the sun and maybee a planet when you are close enough. So you will need some sort of scaling when you want to show the whole solar system. Srek's approach of animating stuff more or less manually is probably much more production focused, but I thought I'd give it a go with a Python generator. It took me surprisingly only about 90 lines of code to do a somewhat working solar system. You can find the code and a scene file below. Please keep in mind that I mostly did this as a "fun thing", so it will likely not be exactly what you want, but maybe you can still borrow bits from it (for example to do the expressions Srek talked about). My approach is deliberately data driven, since this is what I thought to be fun πŸ˜‰ A little animation done with the generator, the little blue pixel is earth. The solar system is at 1% distance scale of the real solar system, so 1km is 10m in this system. solar_system.mp4 Cheers, Ferdinand The code: """Summary Attributes: ORBITAL_SYSTEM (TYPE): Description """ import c4d import math INVERSE_EARTH_PERIOD = 1. / 365. # The solar system as a hierarchical data structure. Distances and radii are # measured in kilometers, orbital periods are measured in (earth) days. You # can extend the system easily by just adding satellites (technically you # could also let the sun orbit something). The code below is however setup in # such fashion that you can only have one root in the system, i.e., you cannot # have two primaries in the data structure at level zero. ORBITAL_SYSTEM = { "Sun": { # The name of the primary "radius": 696340, # The radius of the primary "distance": 0, # The distance to its primary "color": c4d.Vector(1, .75, 0), # The viewport color of the primary "period": 0, # The orbital period of the primary "satellites": { # The satellites of the primary "Mercury": { "radius": 2439.7, "distance": 57.91E6, # i.e., 57.91 million kilometers "color": c4d.Vector(.55, .55, .55), "period": 88, "satellites": {}, }, "Venus": { "radius": 6051.8, "distance": 108.2E6, "color": c4d.Vector(.9, .9, .9), "period": 225, "satellites": {}, }, "Earth": { "radius": 6371, "distance": 148.55E6, "color": c4d.Vector(.1, .5, .9), "period": 365, "satellites": { "Moon": { "radius": 1737.1, "distance": 384400, "color": c4d.Vector(.3, .3, .3), "period": 27, "satellites": {} }, }, }, "Mars": { "radius": 3389.5, "distance": 240.16E6, "color": c4d.Vector(.85, .35, .1), "period": 687, "satellites": {}, }, "Jupiter": { "radius": 69911, "distance": 778.5E6, "color": c4d.Vector(.85, .7, .1), "period": 4380, "satellites": {}, }, "Saturn": { "radius": 58232, "distance": 1489.6E6, "color": c4d.Vector(.55, .4, .25), "period": 10585, "satellites": {}, }, "Uranus": { "radius": 25362, "distance": 2871E6, "color": c4d.Vector(.65, .85, .95), "period": 30660, "satellites": {}, }, "Neptune": { "radius": 24622, "distance": 4495E6, "color": c4d.Vector(.05, .45, .65), "period": 60225, "satellites": {}, }, }, }, } TWO_PI = math.pi * 2. def build_system(name, data, settings): """Builds a system from a fragment of an ORBITAL_SYSTEM data structure. This function builds recursively all the sub-systems of a system. Args: name (str): The name of the primary. data (dict): Its data dictionary taken from ORBITAL_SYSTEM. settings (dict): The user data parameters. Returns: c4d.BaseObject: The system fragment. """ # The primary as a sphere object. primary = c4d.BaseList2D(c4d.Osphere) primary.SetName("body") primary.MakeTag(c4d.Tphong) # Write its node data. combined_scale = settings["global_scale"] * settings["distance_scale"] xpos = data["distance"] * combined_scale primary[c4d.PRIM_SPHERE_SUB] = 32 primary[c4d.PRIM_SPHERE_RAD] = data["radius"] * settings["global_scale"] primary[c4d.ID_BASEOBJECT_REL_POSITION, c4d.VECTOR_X] = xpos primary[c4d.ID_BASEOBJECT_USECOLOR] = 2 primary[c4d.ID_BASEOBJECT_COLOR] = data["color"] # And build its satellites. for satellite_name, satellite_data in data["satellites"].items(): satellite = build_system(satellite_name, satellite_data, settings) satellite.InsertUnder(primary) # A null so that we can easily animate the orbit arround its primary. root = c4d.BaseList2D(c4d.Onull) root.SetName(name) primary.InsertUnder(root) # The current document time. t = doc.GetTime().Get() # Animation period of of that primary in relation to earth period. p = data["period"] * settings["orbit_duration"] * INVERSE_EARTH_PERIOD # Bails if the object has no period. if p == 0.0: return root # The rotational value as the fraction of period remainder of the # document time. We then have to multiply by 2Ο€ since Cinema handles # roatation in radians. theta = TWO_PI * (t % p / p) # Set the heading of the null. root[c4d.ID_BASEOBJECT_REL_ROTATION, c4d.VECTOR_X] = theta return root def main(): """Entry point for Cinema 4D to evaluate the Python generator object. """ settings = { # Out input data is in real world units, we have to scale this # a bit down. "global_scale": op[c4d.ID_USERDATA, 1], # Just our solar system alone is fricking huge, so we propbably want # to scale down distances a bit, so that we can actually see # something. "distance_scale": op[c4d.ID_USERDATA, 2], # The time in seconds on year takes, i.e., one full earth orbit. "orbit_duration": op[c4d.ID_USERDATA, 4], } # The root element. root_name = list(ORBITAL_SYSTEM.keys())[0] root_data = ORBITAL_SYSTEM[root_name] # Build the system. root = build_system(root_name, root_data, settings) # This is only needed for when the user collapses the Python generator via # CSTO, so that we get then a nice hierarchy. system = c4d.BaseList2D(c4d.Onull) system.SetName(op.GetName()) root.InsertUnder(system) return system solar_system.c4d
  15. HI @Lomarx, this is possible with the Redshift API and the graph view module of Cinema's Python SDK/API. The redshift API is public, but only semi-documented I think. So it's mostly poking stuff left and right to figure things out πŸ˜‰ See attached example at the end for details. Cheers, Ferdinand """Run this in the script manger with a Redshift material selected. """ import c4d def main(): """ """ # Try to import the Redshift module. try: import redshift except: raise RuntimeError("Could not import Redshift.") # Get the active material. material = doc.GetActiveMaterial() # Bail if it is not a redshift material. if material.GetType() != redshift.Mrsmaterial: raise TypeError("Selected material is not a Redshift material.") # Get the graph view node master of the material. master = redshift.GetRSMaterialNodeMaster(material) if master is None: raise RuntimeError("Whoopsie!") # From here on out its is basically smooth sailing, and just standard # graph view stuff. # Get the root node. root = master.GetRoot() # The shader graph stuff is here. node = root.GetDown() # Iterate over the nodes in the graph. while node: # When the node is selected and a Redshift Shader: if (node.GetBit(c4d.BIT_ACTIVE) is True and node.GetTypeName() == "Redshift Shader"): # Get the diffuse color diffuse = node[c4d.REDSHIFT_SHADER_MATERIAL_DIFFUSE_COLOR] print (diffuse) node = node.GetNext() # Execute main() if __name__=='__main__': main()
Γ—
Γ—
  • Create New...