Jump to content

Aw heck, that post about the measure object lead to this


Recommended Posts

Ever wanted to snap to the arrowheads of a Measure Object? Well, now you can using this script I wrote, which creates Null objects as children of a Measure Object and positions them at its arrowheads' tips (that can then be snapped to using Axis Snap).

 

Use the Script Manager to copy this script in, name it, save it, and perhaps create a toolbar button for it if it's something you will use often. If you come up with reasonable suggestions for enhancement of this script or find any bugs, please feel free to provide the details in a reply to this post and I will consider implementing/fixing them.

 

Important note: The Measure Object has to be part of the Object Hierarchy and selected. You can add it to the Object Hierarchy by clicking on the Create Object button after creating the measurement points using the Measure & Construction tool. Also, you can run the script multiple times for the same Measure Object, after changing its points coordinates, in order to get new Null object children to be created at the modified point locations (the positions of existing Null children of the Measure Object will not auto-refresh if the Measure Object's points get modified. This allows for the recording of historic positions of the Measure object's arrowhead points by running the script each time a change is made to the Measure Object's measurement arrows):

 

nulls_on_measure_object_arrowheads.py

import c4d
import c4d.gui

# Functionality Provided:
#    Creates nulls at active Measure Object's arrowhead points. Can be used to allow "Axis Snap" at those points.
#    The colors of the nulls will correspond with the (current) colors of the arrows, for clarity.
#    The script is written in a "professional" manner (i.e., not as the sort of hack you oft see posted) and contains
#    proper comments describing what various code does.
#    Also, supports proper Undo functionality, like the existing Core C4D tools

# Create a null object at one of the measure object's arrowheads
def create_null_at_measure_point(measure_obj,null_name,null_pos,color):
    # Create the null
    null_obj=c4d.BaseObject(c4d.Onull)

    # Set various attributes of the null
    null_obj.SetName(null_name)
    null_obj.SetAbsPos(null_pos)
    null_obj[c4d.NULLOBJECT_DISPLAY]=c4d.NULLOBJECT_DISPLAY_POINT # Use "locator" null object shape
    null_obj[c4d.ID_BASEOBJECT_USECOLOR]=1 # Automatic
    null_obj[c4d.ID_BASEOBJECT_COLOR]=color # Get from measure

    # Let's add the new null object as the last child of the Measure Object

    # Get the last child of the Measure (if any children are present), otherwise will be set to None
    last_child_obj_of_measure=measure_obj.GetDownLast()    

    # Add the null object to the Object Hierarchy as the last child of the Measure Object
    # Note: Setting checknames to True will cause C4D to add a .1, .2, etc., suffix if there is a name collision
    doc.InsertObject(null_obj,measure_obj,pred=last_child_obj_of_measure,checknames=True) 

    # Needs to be called after creation (and insertion into the document, thanks to C4D Cafe user
    #     MighT for pointing out the UNDO step sequencing with regard to document insertion)
    doc.AddUndo(c4d.UNDOTYPE_NEW, null_obj)

# Entry point into script
def main():
    # Make sure that there is an active object and that it is a Measure Object
    if op is not None and op.GetType()==c4d.ID_MEASURE_OBJECT:
        point1_pos=op[c4d.MDATA_MEASURE_PNT1_VECTOR] # Unused, for now

        # Start Undoable action
        doc.StartUndo()

        # If point 2 is in "World Mode"
        # BTW: World mode is MDATA_MEASURE_PNT_MODE_FREE (who would have thunk it?!)
        if op[c4d.MDATA_MEASURE_PNT1_MODE]==c4d.MDATA_MEASURE_PNT_MODE_FREE:
            # Create a locator null at its arrowhead's tip
            create_null_at_measure_point(
                op,
                null_name="Second point tip",
                null_pos=op[c4d.MDATA_MEASURE_PNT2_VECTOR],
                color=op[c4d.MDATA_MEASURE_COLOR1]) # Use color of second point, that's how C4D has them numbered (COLOR1 is for point 2)

        # Is a third point present (and in "World Mode?")
        if op[c4d.MDATA_MEASURE_3RD_POINT] and op[c4d.MDATA_MEASURE_PNT2_MODE]==c4d.MDATA_MEASURE_PNT_MODE_FREE:
            # Throw a locator null on that puppy, too
            create_null_at_measure_point(
                op,
                null_name="Third point tip",
                null_pos=op[c4d.MDATA_MEASURE_PNT3_VECTOR],
                color=op[c4d.MDATA_MEASURE_COLOR2]) # Use color of third point, yes COLOR2 is the color of the third point

        # Done (with proper undo handling)
        doc.EndUndo()

        # Trigger UI refresh/repaint
        c4d.EventAdd()
    else:
        c4d.gui.MessageDialog('Please make sure that a Measure Object is the current active selection, before running this script.')
        
# Execute main()
if __name__=='__main__':
    main()

 

Link to post

There's an issue with the undo handling. The AddUndo(UNDOTYPE_NEW) needs to be called after the insertion of the object. Here the docs (see the Note: "In case of creation... after insertion..."). Also AddUndo(UNDOTYPE_CHANGE), while it needs to be called before the actual change, it may only be called on entities which are part of the document.

 

Think about it. The undo functions are functions of BaseDocument. The undo system wants to keep track of changes inside a document.
For a change (matrix, parameter change,...) of an entity (entity, because it's the same for objects, tags, materials...), which is already part of the document, you call AddUndo() before the change, because C4D needs to back up the state before the change (simplified, it will copy the entity's BaseContainer onto the undo stack).
For a new entity, though, it will need to keep some reference to the new entity, in order to be able to remove it on undo (simplified, internally it will store a BaseLink (sorry, link to C++ docs, BaseLink class does exist only in C++) to the new entity on the undo stack). This can only work´reliably, if the entity is already part of the document, as BaseLinks are always evaluated in a document context, so AddUndo() has to be called after the insertion.

 

You may be of the opinion, I'm nit picking, because you script seems to be working. Yet, I'm not. Wrong usage of the Undo functions can severely damage C4D stability.

 

For your script the steps are:

1) Create Null

2) Insert Null
3) AddUndo(NEW, Null)
4) Actually no AddUndo(CHANGE, Null) needed at all, as you just modify a freshly inserted object. Where would be the point in backing up it's initial state in the same undo step as its creation? If the user presses undo, the object will be removed, there's no use of C4D restoring the initial state prior to removal...

 

Cheers

Link to post
7 hours ago, MighT said:

There's an issue with the undo handling. The AddUndo(UNDOTYPE_NEW) needs to be called after the insertion of the object. Here the docs (see the Note: "In case of creation... after insertion..."). Also AddUndo(UNDOTYPE_CHANGE), while it needs to be called before the actual change, it may only be called on entities which are part of the document.

 

Think about it. The undo functions are functions of BaseDocument. The undo system wants to keep track of changes inside a document.
For a change (matrix, parameter change,...) of an entity (entity, because it's the same for objects, tags, materials...), which is already part of the document, you call AddUndo() before the change, because C4D needs to back up the state before the change (simplified, it will copy the entity's BaseContainer onto the undo stack).
For a new entity, though, it will need to keep some reference to the new entity, in order to be able to remove it on undo (simplified, internally it will store a BaseLink (sorry, link to C++ docs, BaseLink class does exist only in C++) to the new entity on the undo stack). This can only work´reliably, if the entity is already part of the document, as BaseLinks are always evaluated in a document context, so AddUndo() has to be called after the insertion.

 

You may be of the opinion, I'm nit picking, because you script seems to be working. Yet, I'm not. Wrong usage of the Undo functions can severely damage C4D stability.

 

For your script the steps are:

1) Create Null

2) Insert Null
3) AddUndo(NEW, Null)
4) Actually no AddUndo(CHANGE, Null) needed at all, as you just modify a freshly inserted object. Where would be the point in backing up it's initial state in the same undo step as its creation? If the user presses undo, the object will be removed, there's no use of C4D restoring the initial state prior to removal...

 

Cheers

 

You're not nitpicking - these are all perfectly valid points and I have corrected the source in the above post, accordingly.

 

Michael

 

P.S. The next feature I am planning to add is to record the distance of each arrow in the name of the Null, for further clarity and information/disambiguation, especially when multiple runs of this script are used to store historic states of a Measure Object.

 

Stay tuned and let me know if you can think of any other useful tidbits that should be added...

 

Link to post

More Useful Measure Object 2.0, initial proof of concept (note the additional gradations that can also be used as Axis Snap points at 25%, 50%, and 75% of the lengths of the measure arrow vectors):

 

From this: Base-MeasureObject.thumb.png.45d386c567f01a19ffaa56cce6e60390.png to this: MeasureObject-2.0.thumb.png.e5b4e78493d8e630ab6993a0b8bee476.png

Link to post
  • Silver Contributor

Keep up the good work - I'll be trying this out!
If you can add a the ability to have a user specified number divisions on the arrows - evn better : )

Link to post
3 hours ago, MikeA said:

Keep up the good work - I'll be trying this out!
If you can add a the ability to have a user specified number divisions on the arrows - evn better : )

 

 with ability snapping to division crosses

Link to post

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...