Weird problem about definition guid and start_operation method

In a blank model, import an instance like this:

path = 'some/path.skp'
d = Sketchup.active_model.definitions.load(path)
instance = Sketchup.active_model.entities.add_instance(d, IDENTITY)
puts "definition guid: #{d.guid}"
# redraw instance which has dynamic attributes
# redraw_without_undo method just is a copy of redraw_with_undo but without operation transaction
$dc_observers.get_latest_class.redraw_without_undo(instance)
puts "definition guid: #{instance.definition.guid}"
# in this case, guid changes, that's what I what.

In this case, the definition’s guid will be changed.
But if wrap those codes into an operation transaction like this:

Sketchup.active_model.start_operation 'test', true
path = 'some/path.skp'
d = Sketchup.active_model.definitions.load(path)
instance = Sketchup.active_model.entities.add_instance(d, IDENTITY)
puts "definition guid: #{d.guid}"
# redraw instance which has dynamic attributes
# redraw_without_undo method just is a copy of redraw_with_undo but without operation transaction
$dc_observers.get_latest_class.redraw_without_undo(instance)
puts "definition guid: #{instance.definition.guid}"
Sketchup.active_model.commit_operation
# in this case, guid doesn't change, that's not what I what.

The definition’s guid won’t change, and after this operation, even import the definition from the path again, the new imported definition is same as instance.definition

But if in the operation transaction use redraw_with_undo instead of redraw_without_undo, the definition’s guid changed, too.

Sketchup.active_model.start_operation 'test', true
path = 'some/path.skp'
d = Sketchup.active_model.definitions.load(path)
instance = Sketchup.active_model.entities.add_instance(d, IDENTITY)
puts "definition guid: #{d.guid}"
# redraw instance which has dynamic attributes
$dc_observers.get_latest_class.redraw_with_undo(instance)
puts "definition guid: #{instance.definition.guid}"
Sketchup.active_model.commit_operation
# in this case, guid changes, that's what I want.

So why does this happens?

Generally, with DCs, if the attribute settings cause the DC code to modify the DC component’s definition, then it must get a new guid. This can occur with material changes, or stretching where extra copies of subcomponents are generated.

So, in your test make sure that you do the exact same attribute changes.

We cannot reproduce the error on our machines without a test component file and the change to the attributes before the redraw calls. Also, whenever reporting an error with the API and/or an extension, it is normal to state the SketchUp/API version and the version of the extension.

FYI, in this second case, because the DC method starts a new undo operation, the operation you had already started is automatically committed.

Thanks. The result of the first case is what I want. I want the definition’ guid changed after redraw since after redraw the entities of the definition would be changed.

Yes, this is why using redraw_without_undo instead of redraw_with_undo, cause I want call redraw method multiple times under single operation.

What I do is define a new mehtod named redraw_without_undo like this:

class DynamicComponentsV1
  def redraw_without_undo(entity)
    determine_movetool_behaviors(entity)
    DCProgressBar::clear()
    redraw(entity)
    DCProgressBar::clear()
    refresh_dialogs()
  end
end

And the original method named redraw_with_undo should be like this:

def redraw_with_undo(entity,progress_bar_visible=true)
    Sketchup.active_model.start_operation translate('Redraw'), true
    determine_movetool_behaviors(entity)
    DCProgressBar::clear()
    redraw(entity)
    DCProgressBar::clear()
    refresh_dialogs()
    Sketchup.active_model.commit_operation
  end

The SketchUp Ruby ObjectSpace is a shared environment.

DO NOT directly modify Ruby Core, SketchUp API, or Trimble extension classes or modules. Doing so affects all other extensions and users.

If you are only doing this on your own machine, then enjoy.

But do not publish extensions that modify the DC code.

If you can … then use Ruby refinements - Documentation for Ruby 3.2 (ruby-lang.org)
They are like your private superclass to the DC class that only your code file can see.

Thanks. This extension won’t be published, so don’t worry.

I wonder if you have any ideas about my question, it confused me for a long time.

I do worry. Do so, would engrain poor coding habits in you. And later on you may forget that you “monkey-patched” classes you should not have. Again, use a refinement.

I already weighed in on this. The DC code is very complex and closed source.
I really don’t have much interest in digging deeper into it. (I have my own projects.)

I see, thanks anyway.

After some test, I think I found the reason:

Based on the document of ComponentDefinition’s guid

The guid method is used to retrieve the unique identifier of this component definition. The guid changes after the component definition is modified and the component edit is exited.

This means definition’s guid will be changed only if the definition is modified.
When using Sketchup.active_model. start_operation(‘test’, true), the second parameter will ignore the modification untill commit_operation

disable_ui (Boolean) (defaults to: false) — if set to true, then SketchUp’s tendency to update the user interface after each geometry change will be suppressed. This can result in much faster Ruby code execution if the operation involves updating the model in any way.

So in the operation block, the definition’s modification will be igonred, and the guid of definition won’t change.

In this case, if we import a definition from a file, and add an instance to the active model, then redraw it, then repeat this operation for several times(import the same definition file), and we wrap those operations into one start_operation / commit_operation block, we’ll find that Sketchup only import definition from file for once, the rest of import will use the definition exist in the active model, even if the definition has been changed(but guid doesn’t change)

So I think Sketchup shoud introduce a new parameter for DefinitionList#import, which if set to true, Sketchup will always import definition from file, even if the definition exists in the active model.

SketchUp 2023.0 had a similar fix for the #load method and added a :allow_newer paramter in SketchUp 2021.0:

So a possible workaround might be to do the import from a non-SKP file, then save it out as a SKP component file, thereafter use Sketchup::DefinitionList#load.

A friend of mine tried with Sketchup 2024, and doesn’t help.

Well post an API feature request in the GitHub tracker.
(SketchUp 2022 will not get any updates.)

Thanks so much!