ComponentDefinition#guid at the model level?

Hi all,

Whenever the user (or a script) makes changes, the currently-opened component gets its ComponentDefinition#guid value updated to a new, apparently random number. If we undo or redo, we see again the same previous values of ComponentDefinition#guid. This can be useful to refer to specific revisions, or to specific changes.

However, there is nothing I can find if we’re not currently editing any component, but the top level. There is Model#guid but its value doesn’t update for every change.

Is there anything you could think of that would allow me to identify a specific revision? Any attribute to read or computation to do that could give me a unique ID for the revision, so that any change to the model gives a different ID, and undo/redo comes back to the old IDs?

Maybe inject my own random ID in onTransationStart (with model.set_attribute)? Does it look like a good idea or is there unfortunate drawbacks—e.g. transactions would no longer ever be empty?

…No, onTransactionStart is called before the transaction starts, and onTransactionCommit is called after it commits. So no way to inject an extra set_attribute change here.

FYI for dealing with revisions …

There are also persistent_id for entity classes: Sketchup::Entity#persistent_id()

There is a observer callback to detect changes: Sketchup::ModelObserver#onPidChanged()

I myself logged a bug issue for one scenario where it should, but does not:

When you are editing a component definition file directly, these properties are the definition name and description.

I also logged this one …

Any geometry changes should fire off Sketchup::ModelObserver and / or Sketchup::EntitiesObserver callbacks. Changes to any of the model’s collections should fire callbacks for those collection classes (if they’ve been implemented.)

I think you are talking about setting an ID value in your extension’s attribute dictionary.

UPDATE (2020-10-04):
I knew there was somewhere in Ruby to get a guid or uuid, and stumbled across it.

require 'securerandom'
my_uuid = SecureRandon.uuid

There is a trick to get a GUID from the API …

Other methods involve the use of a standard library that has issues in older releases, best avoided.

def get_new_guid()
  model = Sketchup.active_model
  puts "before ... Model GUID: #{model.guid}"
  dlist = model.definitions
  model.start_operation("Temp Definition")
    guid = dlist.add("TEMP-GUID").guid
  # We create a blank operation in case the next operation
  # is transparently attached to it's predecessor.
  model.start_operation("BLANK OP")
  puts "after ... Model GUID: #{model.guid}"
  puts "Model modified? #{model.modified?}"
  puts "\nThe new guid for plugin use: #{guid}"
  return guid

Because adding an attribute changes the model, you can see that doing an undo after set_revision_guid (and checking model.modified?,) shows that it is undone.

def set_revision_guid
  model = Sketchup.active_model
  guid = get_new_guid()
  model.start_operation("Set Rev GUID")
  puts "Model modified? #{model.modified?}"

def get_revision_guid
  model = Sketchup.active_model
  model.get_attribute("Rigo_Revision","GUID","not set!")

I may be missing something, but your get_new_guid function returns a random guid every time it is called, right? For that I could just use existing random number generators. What I’d like is to get an ID that refers to the specific, current revision of the model, so that (1) calling twice get_new_guid in a row should give the same result if and only if no changes where done to the model; (2) if later the user uses the undo and redo commands, we get again the same value of the guid as we did back then. It’s what I get with ComponentDefinition#guid if all edits/undos/redos happen to be geometrical changes done inside that specific component.

To be clearer, the current implementation of my plugin uses onTransactionCommit, onTransactionUndo and onTransactionRedo to manually keep track of the changes (with a list of changes, and an index that counts the number of undos we have seen). This is basically duplicating the kind of maintenance done inside SketchUp. It mostly works but not always. Sometimes the model revision we obtain after the user calls “undo” three times, say, fails to be the revision recorded as the third-to-last one in the list (though it generally is); there is an unexpected shift. I haven’t been able to pinpoint when this happens so far. But this just shows the approach is fragile, and that’s why I’m asking about some guid instead, in order to replace that careful logic with something more robust. After the user picks any combination of undos and redos, I could reliably know where in my list we are right now by checking this “global guid”.

CORRECT. It just serves as a library method to generate a GUID.
Which is supposed to be universally unique every time.

Yes, but this gives a way that is right there in the API without having to package gems with your plugin, or (as said) run into the severe problems with older version of the Ruby OpenSSL library.)

Perhaps. You spoke of an issue involving the API’s model.guid method. I acknowleded this and spoke about known bugs I’ve logged.

Then you seemed to discuss workarounds. So I also discussed a possible workaround.

… and you missed the point that get_new_guid() has the word “new” in it’s name, and would be part of a workaround in which your plugin needs to store the returned value into an attribute dictionary attached to the model using set_revision_guid or similar.

And this would be true once the custom “guid” attribute is saved and attached to the model.

Subsequent calls to read the custom guid would use: get_revision_guid or a similar getter method.

(Yes, and I do realize that setting an attribute is actually a model change that may fire observer callbacks.)

I’m sorry the current API doesn’t have what you need. Please log any issues to the GitHub Issue Tacker.
I also just remembered that Fredo logged an issue with transaction undo and redo problems.

Well ever since they added the transparent parameters to the operation method (allowing other extension code to “piggy-back” upon someone else’s operations,) things can get really complicated.

Well … the model.guid isn’t based upon edits (undos or redos) it’s based upon saved editions.

So you’ll need to generate your own ID somehow from a set of observers and save in some way similar to what I showed.

Thanks. Let me just point out for future readers that the core module Random (or just the rand built-in function) looks like a better approach to do that, IMHO, without either OpenSSL or gems.

Ah, I got it now. Yes, I could make my own revision numbers and store them in an attribute dictionary in the model via a transparent additional operation, in onTransactionCommit. Thanks!

1 Like

Random numbers are not guaranteed unique. If you really wanted unique numbers you could always start at one, and go on up. Your extension would keep track of the next number to use.

Some plugin authors use or which is unlikely to reoccur (for the same model) during this epoch.

The model GUID may only update on model save. The component GUID needs to change directly on each change as components can be copied through the clipboard between models. The revision identifier is needed to determine if a new component should be added to that model or if an existing can just be re-used. The active model as a whole can only be inserted as a component in its latest saved state, not in any current unsaved state.

GUIDs are basically guaranteed to be unique due to their size. In theory there could be a collision but I think there is a greater risk of a neutron start to obliterate earth and stuff like that. GUIDs are used in a number of places due to this property, e.g. as commit IDs in Git.

Uh huh, hence my comment on random numbers (which Ruby’s #rand produce only pseudo-random anyway.)

But, we are talking about revision numbers that only need be unique within a model.

Back when I worked as draftsman the drawing revisions were only unique for a particular drawing and were basically part of the identifier when a drawing (or part / assembly it described) was specified on some other datalist.

Drawing rev numbers were not random, but began at 1 or A and were incremented on upwards.

On the subject of unquie number generation …

I knew there was somewhere in Ruby to get a guid or uuid, and stumbled across it today looking for something else.

require 'securerandom'
my_uuid = SecureRandom.uuid

SecureRandom is in the standard library, and it’s ::uuid method comes from it’s Random::Formatter mixin module.