DC color in face

I’m trying to change the color of the component, filtering so that the selected face does not change color:

```
def self.apply_material_recursivo(component_instance, material, filtro_tipo = "TODOS")
    return unless component_instance.is_a?(Sketchup::ComponentInstance)
    definition = component_instance.definition
    return unless definition && definition.valid?

    # item_type da peça (PORTA, LATERAL, etc.)
    tipo   = definition.get_attribute("dynamic_attributes", "item_type", "").to_s.upcase.strip
    filtro = filtro_tipo.to_s.upcase.strip

    # Só aplica se:
    #  - o tipo bate com o filtro (ou filtro = TODOS)
    #  - E o componente tiver marker us_material
    aplicar_aqui = (filtro == "TODOS" || tipo == filtro)
    aplicar_aqui &&= componente_com_us_material?(component_instance)

    if aplicar_aqui
      # pinta o instance (borda/aresta do componente)
      component_instance.material = material

      # pinta faces da definição, respeitando faces “Sem material”
      definition.entities.grep(Sketchup::Face).each do |face|
        next if face_sem_material?(face)

        begin
          # Frente
          if face.material
            nome = face.material.display_name.to_s.downcase rescue face.material.name.to_s.downcase
            face.material = material unless nome.include?('sem material')
          else
            face.material = material
          end

          # Verso
          if face.back_material
            nome_b = face.back_material.display_name.to_s.downcase rescue face.back_material.name.to_s.downcase
            face.back_material = material unless nome_b.include?('sem material')
          else
            face.back_material = material
          end
        rescue => e
          puts "⚠️ apply_material_recursivo(face): #{e.message}"
        end
      end

      # grava us_material com o nome do material aplicado
      nome_mat = material.name.to_s
      definition.set_attribute("dynamic_attributes", "us_material", nome_mat)
      component_instance.set_attribute("dynamic_attributes", "us_material", nome_mat)
    end

    # recursão nos subcomponentes (mantendo o mesmo filtro)
    definition.entities.grep(Sketchup::ComponentInstance).each do |sub_inst|
      apply_material_recursivo(sub_inst, material, filtro_tipo)
    end

    # se aplicou material aqui, ajusta o veio dessa definição
    orientar_veio_na_definicao(definition, material) if aplicar_aqui
  end
```

You gave no instructions as to how the method is used !

I assume this is not the whole code set, since there’s no enclosing module(s) and missing methods [def…]

Did you get this via AI ? If so it’s full of issues.

I tried it passing

(component_instance=Sketchup.active_model.selection[0], material='red', filtro_tipo = "TODOS")

I just passed a color name ‘red’ rather that making a material/color and ### out later calls to that material.name to make it work.

aplicar_aqui &&= componente_com_us_material?(component_instance) ### IS UNDEFINED NONSENSE !

aplicar_aqui &&= componente_com_us_material?(component_instance) ### IS UNDEFINED NONSENSE !

You are not helping us to help you… Much more context / info is needed…

1 Like

Yes, I used AI, I achieved many advancements with the plugin as a whole, it is very robust and has several tools, it frustrates me a little not knowing how to fix potential errors.

`#  material.rb
module GCut
  require 'json'

  # =========================================================
  # Helpers para filtro de componentes / faces
  # =========================================================

  # Componente é “válido” para receber material do GCut?
  # → precisa ter algum atributo us_material (no instance ou definition)
  def self.componente_com_us_material?(inst)
    return false unless inst.is_a?(Sketchup::ComponentInstance)
    defn = inst.definition
    return false unless defn

    fontes = [
      inst.get_attribute('dynamic_attributes', 'us_material'),
      inst.get_attribute('gcut',              'us_material'),
      defn.get_attribute('dynamic_attributes','us_material'),
      defn.get_attribute('gcut',              'us_material')
    ]

    fontes.compact.any? { |v| !v.to_s.strip.empty? }
  end

  # Face marcada como “Sem material” em qualquer lado?
  def self.face_sem_material?(face)
    return false unless face.is_a?(Sketchup::Face)

    mats = []
    mats << face.material      if face.material
    mats << face.back_material if face.back_material

    mats.any? do |m|
      nome =
        if m.respond_to?(:display_name)
          m.display_name.to_s
        else
          m.name.to_s
        end
      nome.downcase.include?('sem material')
    end
  end

  # =========================================================
  # Aplicar material recursivamente
  # =========================================================
  def self.apply_material_recursivo(component_instance, material, filtro_tipo = "TODOS")
    return unless component_instance.is_a?(Sketchup::ComponentInstance)
    definition = component_instance.definition
    return unless definition && definition.valid?

    # item_type da peça (PORTA, LATERAL, etc.)
    tipo   = definition.get_attribute("dynamic_attributes", "item_type", "").to_s.upcase.strip
    filtro = filtro_tipo.to_s.upcase.strip

    # Só aplica se:
    #  - o tipo bate com o filtro (ou filtro = TODOS)
    #  - E o componente tiver marker us_material
    aplicar_aqui = (filtro == "TODOS" || tipo == filtro)
    aplicar_aqui &&= componente_com_us_material?(component_instance)

    if aplicar_aqui
      # pinta o instance (borda/aresta do componente)
      component_instance.material = material

      # pinta faces da definição, respeitando faces “Sem material”
      definition.entities.grep(Sketchup::Face).each do |face|
        next if face_sem_material?(face)

        begin
          # Frente
          if face.material
            nome = face.material.display_name.to_s.downcase rescue face.material.name.to_s.downcase
            face.material = material unless nome.include?('sem material')
          else
            face.material = material
          end

          # Verso
          if face.back_material
            nome_b = face.back_material.display_name.to_s.downcase rescue face.back_material.name.to_s.downcase
            face.back_material = material unless nome_b.include?('sem material')
          else
            face.back_material = material
          end
        rescue => e
          puts "⚠️ apply_material_recursivo(face): #{e.message}"
        end
      end

      # grava us_material com o nome do material aplicado
      nome_mat = material.name.to_s
      definition.set_attribute("dynamic_attributes", "us_material", nome_mat)
      component_instance.set_attribute("dynamic_attributes", "us_material", nome_mat)
    end

    # recursão nos subcomponentes (mantendo o mesmo filtro)
    definition.entities.grep(Sketchup::ComponentInstance).each do |sub_inst|
      apply_material_recursivo(sub_inst, material, filtro_tipo)
    end

    # se aplicou material aqui, ajusta o veio dessa definição
    orientar_veio_na_definicao(definition, material) if aplicar_aqui
  end

  # =========================================================
  # Orientar veio da textura na definição
  # =========================================================
  def self.orientar_veio_na_definicao(definition, material)
    return unless definition&.valid? && material
    model = Sketchup.active_model
    ro    = model.rendering_options

    definition.entities.grep(Sketchup::Face).each do |face|
      # só ajusta faces que estejam usando esse material (frente ou verso)
      next unless face.material == material || face.back_material == material

      begin
        uvh = face.get_UVHelper(true, true, ro) # pode ser usado depois se precisar

        verts = face.outer_loop.vertices
        next if verts.length < 3

        e_long = face.edges.max_by { |e| e.length }
        a      = e_long.start.position
        b      = e_long.end.position

        restante = (face.vertices.map(&:position) - [a, b])
        c        = restante.first || verts[2].position

        u_len = 1000.mm
        v_len = 1000.mm

        uv_a = Geom::Point3d.new(0,     0,    0)
        uv_b = Geom::Point3d.new(u_len, 0,    0)
        uv_c = Geom::Point3d.new(0,     v_len,0)

        face.position_material(material, [a, uv_a, b, uv_b, c, uv_c], true)

        if face.back_material == material
          face.position_material(material, [a, uv_a, b, uv_b, c, uv_c], false)
        end
      rescue => ex
        puts "⚠️ orientar_veio_na_definicao: #{ex.message}"
      end
    end
  end

  # =========================================================
  # Diálogo de Materiais
  # =========================================================
  def self.abrir_materiais
    dlg = UI::HtmlDialog.new({
      dialog_title:     "Materiais Disponíveis",
      preferences_key:  "GCut.materiais",
      scrollable:       true,
      resizable:        true,
      width:            500,
      height:           750,
      style:            UI::HtmlDialog::STYLE_DIALOG
    })

    html = <<-HTML
    <!DOCTYPE html>
    <html lang="pt-BR">
    <head>
      <meta charset="UTF-8">
      <style>
        body {
          font-family: Arial, sans-serif;
          background-color: #f4f4f4;
          margin: 0;
          padding: 20px;
        }
        h2 {
          text-align: center;
          color: #007ACC;
          margin-bottom: 20px;
        }
        #tipoFiltro, #search {
          width: 100%;
          padding: 8px;
          margin-bottom: 10px;
          border: 1px solid #ccc;
          border-radius: 5px;
          font-size: 14px;
        }
        .grid {
          display: grid;
          grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
          gap: 15px;
          margin-top: 15px;
        }
        .item {
          background: white;
          border-radius: 8px;
          box-shadow: 1px 1px 4px rgba(0,0,0,0.1);
          text-align: center;
          padding: 15px;
          transition: transform 0.2s;
          cursor: pointer;
        }
        .item:hover {
          background-color: #e6f4ff;
          transform: scale(1.02);
        }
        .thumbnail {
          width: 80px;
          height: 80px;
          margin: 0 auto 10px;
          background-color: #ddd;
          background-size: cover;
          background-position: center;
          border-radius: 5px;
          border: 1px solid #ccc;
        }
        .name {
          margin-top: 8px;
          font-size: 12px;
          font-weight: bold;
          color: #333;
          white-space: nowrap;
          overflow: hidden;
          text-overflow: ellipsis;
        }
        .rotate, .price {
          display: none;
          font-size: 11px;
          margin-top: 8px;
          text-align: left;
        }
        .price input {
          width: 100%;
          font-size: 11px;
          padding: 4px;
          box-sizing: border-box;
          margin-top: 4px;
        }
        #togglePriceBtn {
          width: 100%;
          padding: 10px;
          background: #009933;
          color: white;
          border: none;
          border-radius: 5px;
          font-size: 14px;
          margin-bottom: 15px;
          cursor: pointer;
          font-weight: bold;
        }
        #togglePriceBtn:hover {
          background: #007a29;
        }
        .config-label {
          display: flex;
          align-items: center;
          gap: 5px;
          margin-bottom: 5px;
        }
        .empty-state {
          grid-column: 1 / -1;
          text-align: center;
          padding: 40px 20px;
          color: #666;
        }
      </style>
    </head>
    <body>
      <h2>Materiais</h2>
      <select id="tipoFiltro">
        <option value="TODOS">Todos</option>
        <option value="PORTA">Porta</option>
        <option value="LATERAL">Lateral</option>
        <option value="FRENTE">Frente</option>
        <option value="FUNDO">Fundo</option>
      </select>
      <input type="text" id="search" placeholder="Buscar material...">
      <button id="togglePriceBtn">Parâmetros</button>
      <div class="grid" id="material-list">Carregando materiais...</div>

      <script>
        let allMaterials = [];
        let filtroSelecionado = "TODOS";
        let priceMode = false;

        document.getElementById("tipoFiltro").addEventListener("change", function () {
          filtroSelecionado = this.value;
          if (window.sketchup && sketchup.setFilter) {
            sketchup.setFilter(filtroSelecionado);
          }
        });

        document.getElementById('togglePriceBtn').addEventListener('click', function () {
          priceMode = !priceMode;
          this.innerText = priceMode ? "✅ Salvar" : "Parâmetros";
          if (window.sketchup && sketchup.getMaterials) {
            sketchup.getMaterials();
          }
        });

        function loadMaterials(materials) {
          allMaterials = materials;
          renderMaterials(materials);
        }

        function renderMaterials(materials) {
          const list = document.getElementById('material-list');
          list.innerHTML = '';
          if (materials.length === 0) {
            list.innerHTML = '<div class="empty-state"><p>Nenhum material encontrado.</p></div>';
            return;
          }

          materials.forEach(mat => {
            const div = document.createElement('div');
            div.className = 'item';

            const thumb = document.createElement('div');
            thumb.className = 'thumbnail';
            thumb.style.backgroundImage = "url('file:///" + mat.thumbnail + "')";

            const label = document.createElement('div');
            label.className = 'name';
            label.innerText = mat.name;

            const rotateBox = document.createElement('div');
            rotateBox.className = 'rotate';
            rotateBox.innerHTML = `
              <div class="config-label">
                <input type="checkbox" ${mat.rotate ? 'checked' : ''} onchange="setRotation('${mat.name}', this.checked)">
                Pode rotacionar
              </div>`;

            const priceBox = document.createElement('div');
            priceBox.className = 'price';
            priceBox.innerHTML = `
              <div>Preço:</div>
              <input type="text" placeholder="R$ 0,00" value="${mat.price || ''}" 
                     onchange="setPrice('${mat.name}', this.value)">
            `;

            if (priceMode) {
              rotateBox.style.display = 'block';
              priceBox.style.display = 'block';
            }

            div.appendChild(thumb);
            div.appendChild(label);
            div.appendChild(rotateBox);
            div.appendChild(priceBox);

            if (!priceMode) {
              div.onclick = () => sketchup.applyMaterial(mat.path);
            }

            list.appendChild(div);
          });
        }

        function setRotation(materialName, canRotate) {
          if (window.sketchup && sketchup.updateRotation) {
            sketchup.updateRotation(materialName, canRotate ? 'true' : 'false');
          }
        }

        function setPrice(materialName, priceValue) {
          if (window.sketchup && sketchup.updatePrice) {
            sketchup.updatePrice(materialName, priceValue.toString());
          }
        }

        document.getElementById('search').addEventListener('input', function () {
          const term = this.value.toLowerCase();
          const filtered = allMaterials.filter(m => m.name.toLowerCase().includes(term));
          renderMaterials(filtered);
        });

        window.onload = function () {
          if (window.sketchup && sketchup.ready) {
            sketchup.ready();
          }
        };
      </script>
    </body>
    </html>
    HTML

    dlg.set_html(html)

    @selected_type = "TODOS"

    dlg.add_action_callback("ready") do |_|
      carregar_materiais(dlg)
    end

    dlg.add_action_callback("getMaterials") do |_|
      carregar_materiais(dlg)
    end

    dlg.add_action_callback("setFilter") do |_context, tipo|
      @selected_type = tipo
    end

    dlg.add_action_callback("applyMaterial") do |_context, skm_path|
      begin
        model     = Sketchup.active_model
        material  = model.materials.load(skm_path)
        filtro    = @selected_type || "TODOS"
        selection = model.selection.grep(Sketchup::ComponentInstance)

        model.start_operation("Aplicar Material", true)

        if selection.empty?
          model.entities.grep(Sketchup::ComponentInstance).each do |ent|
            GCut.apply_material_recursivo(ent, material, filtro)
          end
        else
          selection.each do |ent|
            GCut.apply_material_recursivo(ent, material, filtro)
          end
        end

        model.commit_operation
        UI.messagebox("✅ Material '#{material.name}' aplicado com sucesso!")
      rescue => e
        model.abort_operation
        UI.messagebox("❌ Erro: #{e.message}")
      end
    end

    dlg.add_action_callback("updateRotation") do |_context, material_name, can_rotate|
      update_material_config(material_name, "rotate", can_rotate == 'true')
    end

    dlg.add_action_callback("updatePrice") do |_context, material_name, price_value|
      update_material_config(material_name, "price", price_value.to_s)
    end

    dlg.show
  end

  # =========================================================
  # JSON de configuração dos materiais (rotate / price)
  # =========================================================
  def self.update_material_config(material_name, key, value)
    material_folder = "C:/LibraryManager/Components/Materiais"
    json_path       = File.join(material_folder, "materials.json")
    config          = File.exist?(json_path) ? JSON.parse(File.read(json_path)) : {}

    config[material_name] ||= {}
    config[material_name][key] = value

    File.write(json_path, JSON.pretty_generate(config))
    puts "🔁 Atualizado #{key}: #{material_name} → #{value}"
  end

  def self.carregar_materiais(dlg)
    material_folder = "C:/LibraryManager/Components/Materiais"
    json_path       = File.join(material_folder, "materials.json")
    config          = File.exist?(json_path) ? JSON.parse(File.read(json_path)) : {}

    materiais = []

    if Dir.exist?(material_folder)
      Dir.entries(material_folder).each do |file|
        next unless file.downcase.end_with?(".skm")

        path          = File.join(material_folder, file)
        material_name = File.basename(file, ".skm")

        thumb_dir  = File.join(material_folder, material_name)
        thumb_path = File.join(thumb_dir, "doc_thumbnail.png")

        unless File.exist?(thumb_path)
          candidatos = Dir.glob(File.join(thumb_dir, "**", "*.{png,jpg,jpeg}"), File::FNM_CASEFOLD)
          thumb_path  = candidatos.first if candidatos.any?
        end

        pode_rotacionar = (config.dig(material_name, "rotate") == true)
        preco           = config.dig(material_name, "price").to_s

        materiais << {
          name:       material_name,
          path:       path.tr("\\", "/"),
          thumbnail:  File.exist?(thumb_path) ? thumb_path.tr("\\", "/") : "",
          rotate:     pode_rotacionar,
          price:      preco
        }
      end
    end

    dlg.execute_script("loadMaterials(#{materiais.to_json})")
  end
end
``
```

Can you please ask that very smart AI how to post code to the forum to make it look the way it should. :winking_face_with_tongue:
Search term:
"[How to] Post correctly formatted and colored code on the forum? "

3 Likes

You haven’t explained exactly how it doesn’t work. e.g. it doesn’t change color, it makes error messages in the Ruby Console…

Learn how to debug - a simple way is to run it with the Ruby Console open and read the error messages, which give line numbers etc… Also use Notepad++ to add in some temporary extra ‘puts’ - e.g. early on in liine#22, puts fontes to get the array of those attributes for the instance and its definition…

Another big problem is that Ruby is an English programming language and this is an English Language forum. Your code has Portuguese comments and method names. There is no way that I (for one) will even attempt to decipher what the code does. The browser cannot translate these comments or method names into English.

1 Like