Adding a slug to imported model

We are building an extension that combines a 3D model catalog with the power of our e-commerce website. The issue we are facing is as follows, when the user adds a light fitting model to the model a few things need to happen. First thing is to store a product slug in an array so we can track the products that have been added this is so we can send these slugs to our api and build a basket in our web application which is working fine. When the user deletes a light fitting model we track this and need to remove the correct slug from the array so the basket will always be correct. How can we add a slug to the model when we import it so we have something to retrieve when we want to update the array if a light model is deleted. We have had no luck adding attributes to the imported model and at the time of import don’t seem to have access to entity info.

    # JavaScript callback to add light
    dialog.add_action_callback("add_light") do |_, light_file, slug|
      puts "Light file requested: #{light_file}"
      @@added_slugs << slug
      puts "Slug added: #{slug}. Current slugs: #{@@added_slugs.inspect}"

      save_slugs_to_model # Save slugs to model after adding
      add_light_to_model(light_file, slug)

      slugs_json = @@added_slugs.to_json
      dialog.execute_script("updateAddedSlugs(#{slugs_json})")
    end
# Method to add the light fitting model to SketchUp
def self.add_light_to_model(light_file, slug)
  model = Sketchup.active_model
  folder_path = File.join(__dir__, 'models', slug) # Path to the folder named after the slug
  file_path = File.join(folder_path, light_file)   # Path to the .dae file inside the folder
  
  puts "Model Folder Path: #{folder_path}"
  puts "Model File Path: #{file_path}"
  puts "Model File exists? #{File.exist?(file_path)}"

  if File.exist?(file_path)
    begin
      # Import the model
      result = model.import(file_path)
      if result
        UI.messagebox("Light fitting added to the model.")
        model.attribute_dictionary("liteworld", true)["last_slug"] = slug
      else
        UI.messagebox("Failed to import the light fitting.")
      end
    rescue => e
      UI.messagebox("Failed to add light fitting: #{e.message}")
      puts "Error: #{e.message}"
    end
  else
    UI.messagebox("Light fitting model not found for slug: #{slug}.")
  end
end

(1) The attributes_dictionaries collection of a model and any of it’s Entity subclass objects is a shared space. So the name of any dictionary should be globally unique. Usually this means a prefix that matches the top-level Ruby module namespace for your extension which also must be unique.

Ex: if you company (or domain) name is “LightWorld” then all your code would be wrapped up in a module of the same name, and within this an extension submodule, say (for argument’s sake this is your “LightingWidget” extension …

module LightWorld
  module LightingWidget

    # Local constant for dictionary name:
    DICT ||= Module.nesting[0].name.gsub('::','_')

  end
end

Then any attribute dictionaries that this extension creates would use this module nesting qualification as its name, resulting in: "LightWorld_LightingWidget"

(2) An attribute dictionary is more like a Ruby Hash than an array, Meaning, each member is a kay/value pair. So, it would make sense to store them using unique keys, say a stock number?

Also, what many new SketchUp coders do not notice is that an AttributeDictionary is itself a subclass of Entity and therefore itself can have an AttributeDictionaries collection. Ie, nested dictionaries.
So, for example, your extension dictionary could have a subdictionary for each light stock number, whose keys could be the object’s persistent_id which allows each instance to be located in a later session and either modified or deleted.

Thank you for your valuable insights. I have now corrected the extension name and set up the local constant for dictionary name. Please could you provide an example of how i could add a stock number and persistant id for the model after importing it so i have something to locate when deleting a model?

# Method to add the light fitting model to SketchUp
def self.add_light_to_model(light_file, slug)
  model = Sketchup.active_model
  folder_path = File.join(__dir__, 'models', slug) # Path to the folder named after the slug
  file_path = File.join(folder_path, light_file)   # Path to the .dae file inside the folder
  
  puts "Model Folder Path: #{folder_path}"
  puts "Model File Path: #{file_path}"
  puts "Model File exists? #{File.exist?(file_path)}"

  if File.exist?(file_path)
    begin
      # Import the model
      result = model.import(file_path)
      if result
        UI.messagebox("Light fitting added to the model.")
        # add attributes at this point
      else
        UI.messagebox("Failed to import the light fitting.")
      end
    rescue => e
      UI.messagebox("Failed to add light fitting: #{e.message}")
      puts "Error: #{e.message}"
    end
  else
    UI.messagebox("Light fitting model not found for slug: #{slug}.")
  end
end

Here’s something i tried and the results i got

  def self.add_light_to_model(light_file, slug)
      model = Sketchup.active_model
      file_path = File.join(__dir__, 'models', slug, light_file)

      if File.exist?(file_path)
        begin
          result = model.import(file_path)
          if result
            new_component = model.active_entities.grep(Sketchup::ComponentInstance).last
            if new_component
              add_slug_attribute(new_component, slug)
            else
              puts "No new component instance found after import."
            end
          else
            UI.messagebox("Failed to import the light fitting.")
          end
        rescue => e
          UI.messagebox("Failed to add light fitting: #{e.message}")
        end
      else
        UI.messagebox("Light fitting model not found: #{file_path}")
      end
    end
   # Add a slug attribute to a component
    def self.add_slug_attribute(component, slug)
      return unless component.is_a?(Sketchup::ComponentInstance)

      # Create or access the dictionary for this slug
      main_dict = component.attribute_dictionary(DICT, true)
      slug_dict = main_dict.attribute_dictionary(slug, true)
      slug_dict["persistent_id"] = component.persistent_id

      puts "Added slug attribute: #{slug} to component #{component.persistent_id}"
    end
 # Globally unique dictionary name
    DICT ||= Module.nesting[0].name.gsub('::', '_')
 class EntitiesChangeObserver < Sketchup::EntitiesObserver
      def initialize(dialog)
        @dialog = dialog
      end

      def onElementRemoved(entities, entity_id)
        puts "Entity removed with ID: #{entity_id}"

        model = Sketchup.active_model
        removed_entity = model.find_entity_by_persistent_id(entity_id)

        if removed_entity && removed_entity.is_a?(Sketchup::ComponentInstance)
          removed_slug = Liteworld::DesignPlugin.get_slug_attribute(removed_entity, slug)
          if removed_slug
            Liteworld::DesignPlugin.remove_slug(removed_slug)
            puts "Removed slug: #{removed_slug}"

            slugs_json = Liteworld::DesignPlugin.get_added_slugs.to_json
            @dialog.execute_script("updateAddedSlugs(#{slugs_json})")
          else
            puts "No slug found for component with ID: #{entity_id}"
          end
        else
          puts "No valid entity found for ID: #{entity_id}."
        end
      end

here is the log i get when import the first model and another model to follow that, the first import we get no component instance found and the second import we do get a component instance found, however this id does now match when we delete a model.

No new component instance found after import.
Added slug attribute: ceiling-lamp-merida-4-white to component 44315
Entity removed with ID: 76245
No valid entity found for ID: 76245.
Entity removed with ID: 76245
No valid entity found for ID: 76245.
Entity removed with ID: 19419
No valid entity found for ID: 19419.
Entity removed with ID: 19419
No valid entity found for ID: 19419.

Sorry, you will need to figure this out yourself as this is commercial project.

Regardless, I do not have a test DAE file and do not know what a slug reference represents. Nor do I have a test SKP model. I cannot debug your extensions from a set of snippets cut out of the whole.


Notes:

When an import finishes successfully, a new component definition will be added to the model’s DefintionList collection. So, the temporary attachment of a DefinitionsObserver may be useful. Once the import is handled the observer can be dettached.
It is common to need to explode the component wrapped instance which then makes the defintion no longer needed. (It can be later purged from the DefintionList collection.)

If you are calling a method in the same scope you should not need to explicitly qualify the method calls as in:

 Liteworld::DesignPlugin.remove_slug(removed_slug)

See calling_methods - Documentation for Ruby 3.2