Change layer of block *definition*?

Having a bit of trouble finding this one - scriptwise, how does one change the layer of a block definition (not an instance)? I don’t see any reference to the layer on which a definition resides in the InstanceDefinition Class. But it does indeed reside on a layer, as if you want to delete a layer which has a block definition on it, you get a warning message.

The idea being that a definition might reside on a layer which is otherwise empty, but it has instances on other layers. If you want to purge the layer on which the definition is, you lose the blocks on the other layers. So I would like to transfer the block definition to a layer that will not be deleted…

Edit:

Hmm, learning new stuff all the time. Apparently a block definition does not exist on just one layer… It is linked to all the layers that the original objects had. This is easily tested, make a block with objects on a couple of different layers, then copy some instances to some other layers and delete the originals. Then try to delete any layer one of the original block objects were on. You will get the message that you can’t.

So I guess I just answered my own question. There’s no way to do this actually.

1 Like

Hi Mitch - I think I made something for this - let me look…

Probably IDef.GetObjects()…

hum - looks like I used rs functions in my thing - does this do what you want? I have not had coffee yet.

MoveBlockObjectstoLayer.py (1003 Bytes)

But yeah - it uses iDef.GetObjects() in the rs function.

-Pascal

1 Like

Those blocks are highly confusing. It took me quite a while to discover what you just described in your edit. In a way it is logical, but one has to be very organized when creating blocks.
It would be much more visible to the user if a block item would create its own layer with sub layers of the layers that the objects were originally on.

1 Like

Hi Gijs - then, if I understand, there would be a new layer in the document for each layer that had a thing in a block. So, for example, turning off one original layer of block objects would do nothing to the visibility of the objects in the block. Would the objects be copied or moved to the new sublayer?

I can see how this might be clearer at one level but lead to confusion at another… Say you have layers Desks and Chairs - and you make a block including a desk and a chair. Currently, turning off Chairs would affect the visibility of any block that used a chair, but with your scenario, you’d need to find all the new sublayers and turn them off individually to hide all chairs… am I thinking about this right?

-Pascal

Yes, it’s not that I suggest that it should be changed, because it will generate all sorts of other issues if people work the way you just describe. Ideally though you would use blocks in such a way that when you use an item multiple times, you make it into a block first. To follow your example, the chair would be its own block item IF it is being used in other block definitions.
So instead of using the layer system to hide certain objects, there would be a similar structure for blocks. And now that I think of it: I wonder why blockmanager is a popup screen and not a panel

Another essential script that should be a part of standard Rhino (from day one, in this instance). :wink:

I noticed with this script that the objects themselves remain on the old layers, even if the definitions move which is straight up what was asked for, so it works as intended to the letter. But in my case, this is making it so that it’s necessary to have two layers visible in order to show essentially one object (yes, I’ve been through this many times before… it’s still super difficult to wrap my head around).

The script is short and neat, but I’m still having trouble understanding it, because I don’t see the word “definition” mentioned even once in it…

Could anyone give me pointers how to change it so that it moves everything selected to a single layer? Definitions, instances AND objects?

You mean the block instances, right ? :slight_smile:
But the objects contained in the block definition change layer.

As Mitch, Gijs and Pascal said, a block definition is just a collection of (links to) objects, any of them belonging to a layer.
( Yeah, I have been looking for definition layers too, only to understand (later …) that there is not such a layer )

Pascal’s script just move the objects in the block definition to a different layer.

To move anything, that is objects contained in the definition and block instances, to a single layer,
I think you can use Pascal’s script to move the original objects and regular Rhino commands to move the instances.

Makes sense ? :slight_smile:

Sorry, no. I’ve given up on trying to understand this crap, honestly. I’m just so tired of it.

All I know is that after running Pascal’s script, something still remains on the original layer, and I want to move it all.

Right now, I have to run the script, and then go to the layers panel, do “select objects” on the original layer, choose the new layer and do “change object layer”.

I just want this to be one step.

You maybe need to re-read my original post at the top…

You cannot move a block definition to a layer because it doesn’t have a layer as an attribute - it is simply linked to all the layers of the objects in the definition.

Don’t confuse block definitions with block instances. A block definition is a recipe for creating instances, using the original geometry objects that were chosen when the definition was created. These objects do not need to actually exist once the block definition has been created, they are just a set of instructions for recreating the objects as block instances. That set of instructions includes the layers of the original objects. A block definition resides in the instance definition table in a Rhino file, but you cannot see it. As the block definition references the layers of the original objects, Rhino will not let you delete these layers until you have deleted the definition.

A block instance is a new object created from the definition. It is what you see on the screen. You can create many instances of the same definition, with different insertion points, orientations, scales, etc.

And why Can’t Rhino create those layers “at the moment” of inserting an instance if there are no instances on the model? If Rhino would work like that, we could be able to delete empty layers without the need of deleting any non used block definitions.

Don’t ask me, I didn’t write the code… I have no idea what the constraints are.

Sorry Mitch, I wasn’t asking you specifically, it meant to be a rhetorical question :wink:.
I’m asking because this is not a problem for other software when working with blocks, and I just want to understand why Rhino does it this way. It is very complicated to understand for new and intermediate users how to deal with this cumbersome workflow of purging “empty” layers.

@jespizua fyi I’ve made a couple of scripts to make blocks in a file more structured in case you want to create an organized block from scratch or if you carelessly inserted blocks and don’t know on which layer they are. You can find them here.

Thanks @Gijs! I will definitely take a look at those scripts. Just yesterday I imported a SketchUp file with lots of blocks and it was a real nightmare to organize the imported file due to the way Rhino imports SketchUp’s layers when working with nested blocks…

OK, I added a line to Pascal’s script ( I hope Pascal doesn’t mind )
to also move the selected block instances to the chosen layer.

HTH

import rhinoscriptsyntax as rs


def MoveBlockObjectsToLayer():
    
    ids = rs.GetObjects("Select block instances", 4096, preselect=True)
    if not ids:return
    
    targ = rs.GetLayer()
    if not targ:return
    
    names = list(set([rs.BlockInstanceName(id) for id in ids]))
    done = []

    # EDIT: also move block instances to the selected layer
    rs.ObjectLayer( ids, targ )
    # /EDIT
    
    def BlockDrill(names):
        while True:
            if len (names) > 0 :
                name = names.pop()
            else: break
            
            done.append(name)
            temp = rs.BlockObjects(name)
            rs.ObjectLayer(temp, targ)
            
            for tempId in temp:
                if rs.IsBlockInstance(tempId):
                    tempName = rs.BlockInstanceName(tempId)
                    if tempName not in names and tempName not in done:
                        names.append(tempName)
                        BlockDrill(names)
            
    BlockDrill(names)
    
if __name__ == "__main__": MoveBlockObjectsToLayer()

EDIT:
Huh … forgot nested blocks …

import rhinoscriptsyntax as rs


def MoveBlockObjectsToLayer():
    
    ids = rs.GetObjects("Select block instances", 4096, preselect=True)
    if not ids:return
    
    targ = rs.GetLayer()
    if not targ:return
    
    names = list(set([rs.BlockInstanceName(id) for id in ids]))
    done = []

    # EDIT: also move block instances to the selected layer
    rs.ObjectLayer( ids, targ )
    # /EDIT
    
    def BlockDrill(names):
        while True:
            if len (names) > 0 :
                name = names.pop()
            else: break
            
            done.append(name)
            temp = rs.BlockObjects(name)
            rs.ObjectLayer(temp, targ)
            
            for tempId in temp:
                if rs.IsBlockInstance(tempId):

                    # EDIT: also move nested instances to the selected layer
                    rs.ObjectLayer( tempId, targ )
                    # /EDIT

                    tempName = rs.BlockInstanceName(tempId)
                    if tempName not in names and tempName not in done:
                        names.append(tempName)
                        BlockDrill(names)
            
    BlockDrill(names)
    
if __name__ == "__main__": MoveBlockObjectsToLayer()

I’m not sure though how this performs with nested blocks. In fact I try to avoid nested blocks wherever I possibly can

Add second input to the method = parent layer (=“Default” as default value :wink: ) and use recursion at the end of the method.

Well, I tried to write a RhinoCommon “Nuke all block definitions” script, it works on un-nested blocks, but despite my recursion, it’s not working on nested blocks. Somehow it’s skipping the recursion at a certain point when one of the objects in the definition is an InstanceObject, and I can’t figure out why…

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino

#Not working for nested blocks...

def NukeAllBlockDefs(b_def,layer_index):
    block_objs=b_def.GetObjects()
    for block_obj in block_objs:
        print type(block_obj)
        if isinstance(block_obj,Rhino.DocObjects.InstanceDefinition):
            #recurse
            NukeAllBlockDefs(block_obj,layer_index)
        elif isinstance(block_obj,Rhino.DocObjects.InstanceObject):
            indef=block_obj.InstanceDefinition
            NukeAllBlockDefs(indef,layer_index)
        block_obj.Attributes.LayerIndex=layer_index
        block_obj.CommitChanges()

b_defs=sc.doc.InstanceDefinitions
if b_defs:
    msg="Layer to send all block objects to"
    dest_layer=rs.GetLayer(msg,show_new_button=True)
    if dest_layer:
        layers=sc.doc.Layers
        layer_index=sc.doc.Layers.Find(dest_layer,True)
        for b_def in b_defs: NukeAllBlockDefs(b_def,layer_index)
        sc.doc.Views.Redraw()

Here’s the file I used FWIW…

Block_Test_File.3dm (821.4 KB)

Create the method to accept one block as input, and use the recursion inside the method in case the child of the input-block is also a block.