SketchUp / api-issue-tracker

Public issue tracker for the SketchUp and LayOut's APIs
https://developer.sketchup.com/
38 stars 10 forks source link

Method to Merge Folders for `Layers` and `LayerFolder` #845

Open DanRathbun opened 2 years ago

DanRathbun commented 2 years ago

SketchUp Ruby and C API Feature Request

Method to Merge Folders for Layers collection and LayerFolder class objects.

The equivalent of this Ruby refinement (for both APIs,) but with localized query text ...

module FolderMerging

  refine ::Sketchup::Layers do

    def merge_folder(folder, prompt = false, op: true)
      model = self.model
      model.start_operation('Merge Folder',true) if op
      begin
        folder.layers.each { |layer| layer.folder= nil }
        folder.folders.each { |folder| self.add_folder(folder) }
      rescue => err
        raise # or handle gracefully ?
      else
        if prompt
          query = "Remove empty folder: \"#{folder.name}\" ?"
          choice = UI.messagebox(query, MB_YESNO)
          folder.parent.remove_folder(folder) if choice == IDYES
        else
          folder.parent.remove_folder(folder)
        end
      end
      model.commit_operation if op
      return self
    end

  end # class refinement

  refine ::Sketchup::LayerFolder do

    def merge_folder(folder, prompt = false, op: true)
      model = self.model
      model.start_operation('Merge Folder',true) if op
      begin
        folder.layers.each { |layer| self.add_layer(layer) }
        folder.folders.each { |folder| self.add_folder(folder) }
      rescue => err
        raise # or handle gracefully ?
      else
        if prompt
          query = "Remove empty folder: \"#{folder.name}\" ?"
          choice = UI.messagebox(query, MB_YESNO)
          folder.parent.remove_folder(folder) if choice == IDYES
        else
          folder.parent.remove_folder(folder)
        end
      end
      model.commit_operation if op
      return self
    end

  end # class refinement

end # refinement module

NOTE: Updated to skip operation if called recursively or repeatedly from within an iterative method that starts the operation.

~

DanRathbun commented 2 years ago

An example of using the above sample refinement.

The merge method in the example extension submodule below starts the undo operation and repeatedly calls the refined methods (shown above). It prevents nested undo operations by passing an op: false argument.

NOTE: After an undo, the merged folder(s) are restored, but not in the original order !

module SomeAuthor
  module SomePlugin

    extend self

    require "the/path/to/FolderMerging.rb"
    using FolderMerging

    # @param folder [Sketchup::Layers, Sketchup::LayerFolder]
    # @return [Array<Sketchup::LayerFolder>] The list of de-duplicated
    #  layer folders. An empty array if none were de-duplicated.
    def merge_duplicate_named_folders(folder = nil)
      model = Sketchup.active_model
      dups = get_duplicate_named_folders(folder)
      return [] if dups.empty?
      model.start_operation('De-Duplicate Folders',true)
      deduped = []
      dups.each { |set|
        first = set.shift
        set.each {|dup| first.merge_folder(dup, op: false) }
        deduped << first
      }
      model.commit_operation
      deduped
    end

    # @param folder [Sketchup::Layers, Sketchup::LayerFolder]
    # @return [Array<Array(Sketchup::LayerFolder)>]
    def get_duplicate_named_folders(folder = nil)
      model = Sketchup.active_model
      if folder.nil?
        folder = model.layers
      elsif !folder.respond_to?(:add_folder)
        fail(TypeError,"Folder or Layers object expected.",caller)
      end
      list = folder.folders.sort_by(&:name)
      dups = list.chunk_while { |i,j| i.name == j.name }
      dups.select { |ary| ary.size > 1 }
    end

  end # extension submodule
end # author top level unique namespace

For testing, temporary folders can be added ...

names = [
  "Five","One","Two","Three","One","Two","Three","Four","One"
]
tags = Sketchup.active_model.layers
names.each {|name| tags.add_folder(name) }