class RuboCop::Cop::SketchupBugs::MaterialName

Prior to SketchUp 2018 it was possible for the Ruby API to cause materials to have duplicate names. This is not a valid SketchUp model as SketchUp expects material names to be unique identifiers.

`model.materials.add('Example')` have always made materials unique by appending a numeric post-fix to the name.

However, `material.name = 'Example'` did not perform such check. It would blindly set the new name.

As of SketchUp 2018 the API behavior was changed to prevent this. `material.name = 'Example'` will now raise an `ArgumentError` is the name is not unique.

A new method was added to allow a unique material name to be generated: `model.material.unique_name('Example')`.

Changing the name of materials can now follow the same pattern as layers and component definitions.

Note that in SketchUp 2018 there was also a second bug introduced. A name cache was introduced to speed up the lookup and generation of unique names. Unfortunately this got out of sync between changing name via the UI versus via the API. This has been fixed in SketchUp 2019.

@example Pattern for setting material name from SketchUp 2018

material.name = model.materials.unique_name('Example')

@example Pattern for setting name prior to SketchUp 2018

# Works with SketchUp 2014 or newer:
require 'set'

module Example

  def self.rename_material(material, name)
    materials = material.model.materials
    material.name = self.unique_name(materials, name)
  end

  def self.unique_material_name(materials, name)
    if materials.respond_to?(:unique_name)
      # Use fast native implementation if possible.
      materials.unique_name(name)
    else
      # Cache names in a Set for fast lookup.
      names = Set.new(materials.map(&:name))
      unique_name = name
      # Extract the base name and post-fix.
      match = unique_name.match(/^\D.*?(\d*)$/)
      base, postfix = match ? match.captures : [unique_name, 0]
      # Ensure basename has length and postfix is an integer.
      base = unique_name if base.empty?
      postfix = postfix.to_i
      # Iteratively find a unique name.
      until !names.include?(unique_name)
        postfix = postfix.next
        unique_name = "#{base}#{postfix}"
      end
      unique_name
    end
  end

end

Constants

MATERIAL_VARIABLES
MSG_SET_NAME

Public Instance Methods

on_send(node) click to toggle source
# File lib/rubocop/sketchup/cop/bugs/material_name.rb, line 84
def on_send(node)
  return unless valid_for_target_sketchup_version?
  return unless material_set_name?(node)

  add_offense(node, message: MSG_SET_NAME)
end

Private Instance Methods

material?(node) click to toggle source
# File lib/rubocop/sketchup/cop/bugs/material_name.rb, line 93
def material?(node)
  return false unless node && (node.send_type? || node.variable?)

  name = variable_name(node.children.last)
  MATERIAL_VARIABLES.include?(name)
end
variable_name(symbol) click to toggle source

Returns the name without the @ or $ prefix.

# File lib/rubocop/sketchup/cop/bugs/material_name.rb, line 101
def variable_name(symbol)
  symbol.to_s.tr('$@', '').to_sym
end