rs.CopyObject with a Group Issue / Possible Bug

Using RhinoScript (Python).

When using the CopyObject or CopyObjects commands on a group of object, the newly created objects end up being part of that original group. Is this the way the developers intended RhinoScript to work?

See the attached Rhino and Python files. Run the script selecting Curve 1 which is not grouped. You will see the new object is not part of any group. Now run the script selecting Curve 2 which is grouped. You will see the new object is part of the original group.

CopyObject Group Issue.3dm (33.6 KB)
CopyObject Group Issue.py (205 Bytes)

Hi Mike - as far as I can see at least, that is the expected behavior - whether it is right or not is a different question - @dale - is that correct, the Rhino command must do something extra to separate the grouping?

-Pascal

@pascal - Thanks for including Dale. I just thought that this would not be the default behavior and if anything there would be an option to group the new objects into the old group. I’m curious as to what Dale has to say.

Hi MIke - it looks like the underlying Duplicate function on a Rhino object does just that - including attributes like group membership. I can see how that is not exactly desirable or expected at the top level for a user, however…

-Pascal

Hi @Mike24,

If you want to copy objects to a new location and have the new copies in their own groups, then you can do something like this:

import Rhino
import scriptcontext as sc

def test_copyobjects():
    # Select objects
    go = Rhino.Input.Custom.GetObject()
    go.SetCommandPrompt("Select objects to copy")
    go.GeometryFilter = Rhino.DocObjects.ObjectType.AnyObject
    go.GroupSelect = True
    go.SubObjectSelect = False
    go.GetMultiple(1, 0)
    if go.CommandResult() != Rhino.Commands.Result.Success:
        return
    
    # Simple transformation
    direction = Rhino.Geometry.Vector3d(10.0, 0.0, 0.0)
    xform = Rhino.Geometry.Transform.Translation(direction)
    
    dictionary = {} # group remap dictionary
    
    # Iterate selection
    for objref in go.Objects():
        obj = objref.Object()
        if obj:
            # Transform geometry
            geometry = obj.Geometry.Duplicate()
            geometry.Transform(xform)
            # Remap group, if needed
            attributes = obj.Attributes.Duplicate()
            if attributes.GroupCount > 0:
                groups = attributes.GetGroupList()
                top_group = groups[len(groups) - 1]
                if not dictionary.has_key(top_group):
                    dictionary[top_group] = sc.doc.Groups.Add()
                attributes.RemoveFromAllGroups()
                attributes.AddToGroup(dictionary[top_group])
            # Add new object
            sc.doc.Objects.Add(geometry, attributes)
    sc.doc.Views.Redraw()

if __name__ == "__main__":
    test_copyobjects()

– Dale

@dale - I expected the CopyObject method to simply copy the object and not copy the group memberships also. I looked at the documentation here and there is no metion of it automatically making the new object part of the same groups as the copied object. Was the CopyObject designed so that it’s supposed to include the same group membership as the original copied object? Can there be a switch / boolean passed in the next version so that we can turn this off or can it be off by default and have the switch turn it on?

Right now the only way I have found to copy objects without group memberships is to copy the objects and then remove the membership. Below is the modified code to doing this…

import rhinoscriptsyntax as rs

def Main():
    crvobj = rs.GetCurveObject()
    print 'Selected curve guid: ' + str(crvobj[0])

    print 'Curve belongs to: ' + str(rs.ObjectGroups(crvobj[0]))
    
    v = rs.CreateVector(100, 0, 0)
    
    newcrvobj = rs.CopyObject(crvobj[0], v)

    # CopyObject for whatever reason automatically groups the new object with all groups that the copyied object belongs to.
    # Need to remove the new object from all the groups.
    print ''
    print 'New curve group membership count: ' + str(len(rs.ObjectGroups(newcrvobj)))

    print 'New curve is part of the group(s): ' + str(rs.ObjectGroups(newcrvobj))

    for i in range(len(rs.ObjectGroups(newcrvobj))):
        rs.RemoveObjectFromGroup(newcrvobj, rs.ObjectGroups(newcrvobj)[0])

Main()

@dale - At the very least, the documentation needs to be updated to explain this behavior so that others don’t create scripts and then find out later the new objects are attached to the original object’s groups.

@pascal @dale - I went ahead and stepped into the rhinoscript code. It ended up the TransformObjects method in object.py. The specific line / command that copies the object is:

id = scriptcontext.doc.Objects.Transform(old_id, xform, not copy)

The debugger would not allow me to step into Objects.Transform. Is there anyway go look inside of that method?

I went ahead and took snippets from TransformObjects to copy an object that is grouped with other objects just to verify that the Objects.Transform method is in fact behind the scenes copying group memberships. As suspected, it is copying group memberships by default.

Here’s the code…

x = rs.GetObject()
matrix = Rhino.Geometry.Transform.Identity
xform = rhutil.coercexform(matrix, True)
old_id = rhutil.coerceguid(x, False)
id = sc.doc.Objects.Transform(old_id, xform, False)

So if I can look inside the Objects.Transform method, then I can copy snippets from there and create my own copy command that does not copy group memberships by default. In my opinion, the CopyObject command should not copy group memberships by default, or at least there should be a flag that could be set to tell it not to copy them.

Hi Mike - at this point, look for the Transforms on Rhino objects in the RC docs:
https://developer.rhino3d.com/api/RhinoCommon/html/Overload_Rhino_DocObjects_Tables_ObjectTable_Transform.htm

Note this transforms a Rhino object and is not the same as the Geometry.Transform methods which transform geometry but do not directly affect existing Rhino objects

-Pascal

@pascal -Thanks Pascal. Unfortunately that doesn’t give me anymore information really than I already know. It sounds like there’s nothing I can do and simply have to live with the fact that the CopyObject and Transform methods will copy group memberships regardless and every time I copy an object where I don’t want the group memberships, I have to remove them. Kind of a pain. I would think that copying an object would simply do that without the group memberships. Do you concur that there’s nothing I can do at this point than what I have stated here or is there someone there that can be of further assistance? I’m assuming that you and Dale are some of the “top” guys so I’m just asking to ask. Just don’t want to spin my wheels if they aren’t going to go anywhere. Thanks as usual for your assistance BTW.

Hi @Mike24 ,

You can write your own version of CopyObject.

We’re happy to log your wish to change the behavior of rs.CopyObject. Keep in mind that there may be users who want the behavior as-is. Thus, making a change would break this function for them - something we take seriously. Also, if we were to make a change, you’d likely not see it for a while.

That said, the environment is very robust, and its not too difficult to work around most issues like this. The sample I wrote (above) does exactly what you want.

Those who write a lot of scripts have developed their own function libraries with all their favorite stuff. They include them in their scripts just like how you include rhinoscriptsyntax. This is what you should learn to do.

Thanks,

– Dale

@dale - Thanks for your input. My goal was to copy objects WITHOUT the group memberships so I went ahead and created my own copy object method in hopes that there was something more to the Rhino script CopyObject method but it sounds like there isn’t.

I realized that if CopyObject was altered then it would break existing code. So I was skeptical in the ability to have it changed.

I’ll continue with my own method.

Thanks.

@dale @pascal - Just some further input. Regarding the issue with grouping, I would have expected the CopyObjects command to act similar to the Rhino copy command in the GUI. If I copy a objects that all belong to a group the copy command creates a separate group for those items, even if I selected the items individually. This is how I expected CopyObjects to work.

I have developed a script that allows a user to copy objects without any grouping or in separate groups. I also included the option to copy the default CopyObjects way, including the objects in the original groups so that the user would only have to use one function. Let me know if you want it. It uses RhinoScript since I’m not familiar enough with RhinoCommon yet.

Pascal directed me to this thread from here:

I agree with Mike that the current implementation isn’t intuitive for new users who just use rhinoscriptsyntax.

I fully see this point, but for simple scripters like my self rhinoscriptsyntax is an easy way to do complex stuff in seconds, and as Mike says it would be great if this emulated how the tools in Rhino behaves when normal modelling is done. I think this would make it as easy as possible to move into the scripting world. I for one would NEVER have managed to learn scripting if it was too complex. And using libraries is not something I have started to do yet either.

I vote for rs.CopyObjects to make new groups as default, and have an option for adding new objects to existing groups. That would be the best of both worlds IMO.
(Or opposite, add a “makenewgroups=False” as default so an update to Rhino doesn’t mess with old scripts)

Thanks for considering.

Hi @Holo,

I’ve logged the wish.

https://mcneel.myjetbrains.com/youtrack/issue/RH-71162

– Dale

2 Likes