Need some help for writing a simple API to download models from web

Hello guys, a wanna-be programmer here. I’m a woodworker that uses a lot Dynamic Components to create my bespoke cabinets in my shop.
I know that there are thousands plugins like that, but I want to share my own components with my plugin and I’m having some trouble.
I’ve already created a html webpage and some models are up on a server, but I just can’t download them from the website. I can download them if I specify all the links in the *.rb file , but I believe this is not the deal.(this way the website became useless, right?)
I’ve read that I need to create an API to send data from website to Sketchup, but I really don’t know how to do it. Maybe somene could help me with a basic code?
I’m using a free website server by now, models are hosted at Amazon AWS S3 servers.

I just need a code (or the path to write it) that allows download data from amazon to sketchup, without coding it manually at *.rb file. Thank you guys! Sorry for bad english

Update: Here’s my actual .rb file code, as you guys can see, links are “open” to everyone that reads rb file.

# Solicitar API do Sketchup
require 'sketchup.rb'
require 'open-uri'

# Criar botão na barra de ferramentas
toolbar = UI::Toolbar.new 'Orbaneça Blocos Dinamicos'

# Comando para abrir a janela
cmd = UI::Command.new('Orbaneça Blocos Dinamicos') {
  dialog = UI::HtmlDialog.new({
    :dialog_title => "Janela de autenticação",
    :preferences_key => "com.example.plugin",
    :scrollable => true,
    :resizable => true,
    :width => 600,
    :height => 400,
    :left => 100,
    :top => 100,
    :min_width => 50,
    :min_height => 50,
    :max_width => 1000,
    :max_height => 1000
  })
  
  # Defina a URL da janela HTML
  dialog.set_url("https://orbaneca.000webhostapp.com/app.php")

   # Adicionar um callback para receber a ação dos botões
  dialog.add_action_callback('button_clicked') { |dialog, button_id|
    # Aqui você pode verificar qual botão foi clicado e adicionar a lógica para carregar o componente dinâmico correspondente.
    if button_id == 'button1'
      # Carregar o componente dinâmico "gaveta.skp"
      model = Sketchup.active_model
      url = 'https://orbaneca-blocos.s3.sa-east-1.amazonaws.com/Gaveta.skp' # Substitua este URL pelo endereço da prateleira.skp em seu servidor
      temp_file = Tempfile.new('temp.skp')
      temp_file.binmode
      open(url, "rb") { |read_file| temp_file.write(read_file.read) }
      temp_file.close
      definition = model.definitions.load(temp_file.path)
      instance = model.entities.add_instance(definition, Geom::Point3d.new(0,0,0))
    elsif button_id == 'button2'
      # Carregar o componente dinâmico "painel.skp"
      model = Sketchup.active_model
      url = 'https://orbaneca-blocos.s3.sa-east-1.amazonaws.com/Painel+engrossado+tamburato.skp' # Substitua este URL pelo endereço da prateleira.skp em seu servidor
      temp_file = Tempfile.new('temp.skp')
      temp_file.binmode
      open(url, "rb") { |read_file| temp_file.write(read_file.read) }
      temp_file.close
      definition = model.definitions.load(temp_file.path)
      instance = model.entities.add_instance(definition, Geom::Point3d.new(0,0,0))
    elsif button_id == 'button3'
      # Carregar o componente dinâmico "prateleira.skp", este vem da internet
      model = Sketchup.active_model
      url = 'https://orbaneca-blocos.s3.sa-east-1.amazonaws.com/Prateleira+engrossada+tamburato.skp' # Substitua este URL pelo endereço da prateleira.skp em seu servidor
      temp_file = Tempfile.new('temp.skp')
      temp_file.binmode
      open(url, "rb") { |read_file| temp_file.write(read_file.read) }
      temp_file.close
      definition = model.definitions.load(temp_file.path)
      instance = model.entities.add_instance(definition, Geom::Point3d.new(0,0,0))
    end
  }
  dialog.show
}


icon = File.join(__dir__, 'orbaneca', 'icon.png')
cmd.small_icon = icon
cmd.large_icon = icon
cmd.tooltip = 'Orbaneça Blocos Dinamicos'
cmd.status_bar_text = 'Este é meu primeiro app, boa sorte'
toolbar.add_item cmd
toolbar.show

# Adicionar o nome do plugin no menu de plugins do Sketchup
UI.menu('Plugins').add_item('Orbaneça') {
  # Ao clicar na janela, o seguinte conteudo acontecerá:
  UI.messagebox('Deu certo mesmo!')
}

# Mostrar o console ao abrir o Sketchup para verificar erros
SKETCHUP_CONSOLE.show

ALL of your code must be within a unique top level namespace module …
… and each of your extension should be within a submodule whose name will be unique within your namespace module.

module OrbaneçaFelipe
  module BlocosDinamicos

    extend self

    # Your extension methods and custom classes here ...

    # Your run once UI code block here ...

  end
end

You need to use a unique preferences key for a custom dialog such as this. Example:

    :preferences_key => "Orbaneça.Blocos.Dinamicos",

The code for a command should be within a method …

cmd = UI::Command.new('Orbaneça Blocos Dinamicos') { my_command_name() }

… this way you edit the command method (or the methods it calls) and then reload the file so that the method(s) are redefined. It makes development much easier as you do not need to reload SketchUp to see changes take effect.


All menu, toolbar and UI::Command code should be protected within a conditional block that only runs once per session. This avoids duplicate submenus, menu items or toolbars. Ie:

    unless defined?(@gui_loaded)
      #
      # Define UI objects here ...
      #
      @gui_loaded = true
    end

You should organize your extension files properly:


Re, downloading components … the API has a built-in method and abstract downloader class:

1 Like

Basically, you do not need to pass the button id back to ruby.

You need to pass the model filename back to Ruby so you can insert it onto the end of the URL path.

At the top of your extension submodule …

URL ||= 'https://orbaneca-blocos.s3.sa-east-1.amazonaws.com'

Elsewhere …

def download(model_file)
  model = Sketchup.active_model
  definitions = model.definitions
  definitions.load_from_url("#{URL}/#{model_file}", LoadHandler.new)
end

And your dialog code …

  dialog.add_action_callback('button_clicked') { |dialog, skp_file|
    new_definition = download(skp_file)
  }

Thank you for you support, Dan.

But I believe this is over my skills lol

Nothing works here, I’m completely lost…

Code ended like this, but nothing happens when I click the button:

require 'sketchup.rb'
require 'open-uri'

module OrbanecaFelipe
  module BlocosDinamicos

    #Caminho do servidor sem os arquivos
    URL ||= 'https://orbaneca-blocos.s3.sa-east-1.amazonaws.com'


    # Criar botão na barra de ferramentas
    toolbar = UI::Toolbar.new 'Orbaneça Blocos Dinamicos'
    
  # Comando para abrir a janela
    cmd = UI::Command.new('Orbaneça Blocos Dinamicos') {
      dialog = UI::HtmlDialog.new({
      :dialog_title => "Janela de autenticação",
      :preferences_key => "com.example.plugin",
      :scrollable => true,
      :resizable => true,
      :width => 600,
      :height => 400,
      :left => 100,
      :top => 100,
      :min_width => 50,
      :min_height => 50,
      :max_width => 1000,
      :max_height => 1000
      })
  
      # Defina a URL da janela HTML
      dialog.set_url("https://orbaneca.000webhostapp.com/")

     

      def download(model_file)
        model = Sketchup.active_model
        definitions = model.definitions
        definitions.load_from_url("#{URL}/#{skp_file}", LoadHandler.new)
      end

      dialog.add_action_callback('buttonClicked') { |dialog, skp_file|
      new_definition = download(skp_file)
      }
      dialog.show
    }

    icon = File.join(__dir__, 'orbaneca', 'icon.png')
    cmd.small_icon = icon
    cmd.large_icon = icon
    cmd.tooltip = 'Orbaneça Blocos Dinamicos'
    cmd.status_bar_text = 'Este é meu primeiro app, boa sorte'
    toolbar.add_item cmd
    toolbar.show

    # Adicionar o nome do plugin no menu de plugins do Sketchup
    UI.menu('Plugins').add_item('Orbaneça') {
    # Ao clicar na janela, o seguinte conteudo acontecerá:
    UI.messagebox('Deu certo mesmo!')
    }

  # Mostrar o console ao abrir o Sketchup para verificar erros
  SKETCHUP_CONSOLE.show

  end
end

Maybe the problem it’s on the web side, I really don’t know. This is my page code.

Button 1 changed to “gaveta.skp” to complete models server link, but I’m really not sure if its correct.

I bet problem it’s here, but I don’t know:

 function buttonClicked() {
      window.location.href = "skp:button_clicked@" + buttonId;
    }

Here’s complete page coding.

<!DOCTYPE html>
<html>
<head>
  <title>Orbaneça Blocos Dinamicos</title>
  <meta charset="UTF-8">
  <script>
    // Função para enviar a ação do botão para o Ruby
    function buttonClicked() {
      window.location.href = "skp:button_clicked@" + buttonId;
    }
  </script>
</head>
<body>
  <p>Selecione um componente dinâmico para baixar:</p>
  <button onclick="buttonClicked('gaveta.skp')">Gaveta Avulsa</button><br>
  <button onclick="buttonClicked('button2')">Painel engrossado</button><br>
  <button onclick="buttonClicked('button3')">Prateleira engrossada</button>

</body>
</html>

Maybe I’m asking too much, IDK, but I appreciate your help so far.

One problem is that your JS function has no parameter. Within the function you are using the var buttonId (which is undefined,) but as said previously it’s the skp file name you should send back to Ruby.

The other issue is that you should be using the JS sketchup object that the newer UI::HtmlDialog class defines for you.

The old skp: location protocol was for the deprecated UI::WebDialog class.

So the function should look like:

    function buttonClicked(skp_file) {
      sketchup.button_clicked(skp_file);
    }

But you have an additional Ruby-side error in the naming of your Ruby callback. The names must match between the JS and Ruby but yours do not. The Ruby-side callback name should be "button_clicked" not "buttonClicked". It does not matter whether using the skp: protocol or the newer sketchup object, the callback name used to fire the Ruby side callback must be correct, or yes nothing will seem to happen.

Programming is about getting all the nitty details correct. Otherwise things will not happen or act as you wish.


This statement should not be needed inside an extension. Besides you would want the console open before loading any plugins to see any errors.

SketchUp versions from 2021.1 now remember if the console is open and will reopen it next session. So just manually open it, and leave it open when you close SketchUp. It should reopen in the next session before extension load.


Regarding the Ruby … you did not follow the instructions I gave.

  • Especially including the extend self statement so methods can call each other within your submodule without extra qualification.
  • If you want the size and position of your HTML dialog to be remembered correctly and restored automatically, then you must use a unique preferences key as shown.
  • Again your GUI object creation should be within a conditional block so it’s loaded only once during development. (As you wrote it, you’ll need to close and reopen SketchUp after each edit.)
  • Most important is that the main command proc has all the dialog code within it.
    • GUI command objects can not be redefined later. This is why I told you that the command code should actually be defined in a method, and the command proc only call that method.
    • The download() method does not go within the command proc, not within the other method. They each are defined at the same level. (They are sibling methods. The command method will can the download method when it needs to.)

Also, if using the API’s load_from_url method, your code need not load the open-uri library.

In addition, you don’t really need to require "sketchup.rb" as SketchUp loads this file before ANY extensions are processed. (It could be used if you want to load API stubs into an IDE later on when you are more comfy with coding.)

Lastly, the dialog reference needs to be persistent so that it does not get garbage collected. Use a @dialog variable instead of a dialog local variable.


Listen, I have arthritis in my hands and I’m really done with explaining this any further in print.

So there is a load of Ruby Resources to learn from. It will take time. More if your new to coding.

Really really really thank you, DanRathbun. I’ll read everything carefully and do my best! Later I’ll came back to say that everything worked! (I hope, so)

Another thing you forgot in the Ruby submodule is to define your custom LoadHandler class.

Again, refer to the documentation for: Sketchup::DefinitionList#load_from_url()

This is an abstract class (meaning there is no superclass from which to inherit functionality.) You can name it whatever you wish: ie: DownloadHandler, SkpDownloader, whatever you like.
Just be sure to:

  1. Define it within your extension submodule.

  2. Use whatever class identifier you’ve chosen when making the constructor call for the 2nd argument to the #load_from_url() method.

It worked! I really appreciate your time and help, it was essential in resolving the issues with the program. Now I’m going to study how to make the code more pleasant following the material you sent. Thank you very much.

1 Like