Can you add User Text to Block Definition?

rs.SetUserText(block_definition_id, "key", "value")
gave this error:
Message: 9d0ecdd8-b3ad-4d9d-80bb-020002811910 does not exist in ObjectTable

Some context:

I’m trying to add “Edit in place” functionality to linked blocks - I think that will be useful in case you move your block from original position and need to change it with reference to objects in the active document.

Opening them separately in a new rhino instance loses the context. Worksessions preserve the context, but you can’t move parts of worksessions to new place the way you can transform linked blocks (also I don’t like keeping a separate .rws file)

So I’m trying to change block into an embedded type, make changes and export it again into a linked type (overwriting the previous file).

I wanted to store source archive path on the block definition - saving it on block instance isn’t the safest, because that one instance may be deleted. Saving them in document globally isn’t great neither, because block definition can be renamed/reinsterted. So ideally you could store them on block definitions.

import rhinoscriptsyntax as rs
import scriptcontext as sc
import Rhino

block_id = rs.GetObject(filter=4096, preselect=True)
block_definition = rs.coercerhinoobject(block_id).InstanceDefinition

update_type = rs.ComboListBox(["Embedded", "Linked"])
if update_type == "Embedded" and \
block_definition.UpdateType == Rhino.DocObjects.InstanceDefinitionUpdateType.Linked:
    if rs.SetUserText(block_id, key="SourceArchive", 
                    value=block_definition.SourceArchive):
        sc.doc.InstanceDefinitions.ModifySourceArchive(block_definition.Index,
                                         block_definition.SourceArchive,
                                         Rhino.DocObjects.InstanceDefinitionUpdateType.Static,
                                         quiet=1)

# use Static instead of Embedded
# Rhino will default to Static even if Embedded was set in ModifySourceArchive function
# https://github.com/mcneel/rhinocommon/blob/master/dotnet/rhino/rhinosdkinstance.cs#L23
if update_type == "Linked" and \
block_definition.UpdateType == Rhino.DocObjects.InstanceDefinitionUpdateType.Static:
    # you can't store user text on block definition 
    # and I'd rather not use the description field
    # so store it on the first block instance user clicked and
    # iterate through all of them (hoping that wasn't deleted)
    # don't want to store it in document, because the block may be renamed/reinserted
    # https://discourse.mcneel.com/t/can-you-add-user-text-to-block-definition/150530
    for block_instance_id in rs.BlockInstances(block_definition.Name):
        block_instance_source_archive = rs.GetUserText(block_instance_id, key="SourceArchive")
        if block_instance_source_archive:
            model_base_point = Rhino.FileIO.File3dm.Read(block_instance_source_archive).Settings.ModelBasepoint
            sc.doc.ModelBasepoint = model_base_point
            rs.Command("-Cplane World Top")
            rs.Command("-Insert File=No " + chr(34) + block_definition.Name + chr(34) + " Block " + str(model_base_point) + " 1 0")
            rs.Command("SelLast")
            rs.Command("Explode")
            rs.Command("-Export " + chr(34) + block_instance_source_archive + chr(34))
            rs.Command("Delete")
            sc.doc.ModelBasepoint = Rhino.Geometry.Point3d(0,0,0)
            
            sc.doc.InstanceDefinitions.ModifySourceArchive(block_definition.Index,
                                         block_instance_source_archive,
                                         Rhino.DocObjects.InstanceDefinitionUpdateType.Linked,
                                         quiet=1)
            
            # use the first one found - problably should 
            # add extra checks in case there are multiple paths
            break
        else:
            raise Exception("Couldn't find file path for the Linked Block in any of its instances")

As instance definitions inherit from CommonObject - they have Userdata.
The answer to the question of the title is Yes.

The second question is - how to do it with python. Use Rhinocommon directly.

…without testing - but hopefully points in the right direction:
the rhinoscriptsyntax might not search the InstanceDefinitionstable of the document.
use rhinocommon to find the correct instance definition:

RhinoDoc.InstanceDefinitions Property

InstanceDefinitionTable.Find

The 3rd aspect - not sure what s the best practice to edit external block definitions.

hope this helps / points out some starting points. kind regards -tom

1 Like

did you mange to get it work ?
(add userdata of objectdefinition and write it to the document, so it is persistent also when saving, closing, reopening the document)
if yes - would be great to see / post an example.

I did not try any code - but for me it looks unclear how the direct modification of the Userdata of the instance definition is passed to the document (or if this is not necessary).
I am wondering, because i can not find anything similar to CommitChanges
and the add / modifiy methods of InstanceDefinitonTable do not allow to set UserData.

I’ve had to jump on another task, but I’ve added it to the list and will update this thread as soon as it’ll be ready. Thank you very much for confirming that it’s doable, like you said, I could have noticed that it inherits from CommonObject.

I’ll check if InstanceDefinition.Text attribute (I think that’s the one) will play along with the rest of the script. The script otherwise seems to work fine (you get an “Edit In Place” like experience of turning a linked block into an embedded one and resaving it in the same place and with the same ModelBasePoint) unless it has some side effects that I haven’t yet forseen. I’ll try to record it in action later on.

I need to test that, but I expected that just setting InstanceDefinition.Text attribute (if that’s the one) will work straight away.

seams to work without any further action to bring it to the document:

set it

import rhinoscriptsyntax as rs
import Rhino as rh
import scriptcontext as sc

def SetUserTextInstanceDef():
    block_id = rs.GetObject("select instance", filter=4096, preselect=False)
    instanceDef = rs.coercerhinoobject(block_id).InstanceDefinition
    instanceDef.UserDictionary.Set("key4","value4")

if __name__=="__main__":
    SetUserTextInstanceDef()

get it

import rhinoscriptsyntax as rs
import Rhino as rh
import scriptcontext as sc

def GetUserTextInstanceDef():
    block_id = rs.GetObject("select instance", filter=4096, preselect=False)
    instanceDef = rs.coercerhinoobject(block_id).InstanceDefinition
    key = "key4"
    if instanceDef.UserDictionary.ContainsKey(key):
        value = instanceDef.UserDictionary.GetString(key)
        print('value = ' + value)
    else:
        print('key not found')

if __name__=="__main__":
    GetUserTextInstanceDef()
1 Like