Class: RuboCop::Cop::SketchupBugs::MaterialName

Inherits:
SketchUp::Cop show all
Includes:
SketchUp::SketchUpTargetRange
Defined in:
lib/rubocop/sketchup/cop/bugs/material_name.rb

Overview

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.

Examples:

Pattern for setting material name from SketchUp 2018

material.name = model.materials.unique_name('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

Constant Summary collapse

MATERIAL_VARIABLES =
%i[material mat].freeze
MSG_SET_NAME =
'`material.name=` might add duplicate materials in '\
'SU2017 and older versions.'

Constants inherited from SketchUp::Cop

SketchUp::Cop::SKETCHUP_DEPARTMENT_SEVERITY

Constants included from SketchUp::Config

SketchUp::Config::DEFAULT_CONFIGURATION

Instance Method Summary collapse

Methods included from SketchUp::SketchUpTargetRange

included, #sketchup_target_max_version, #sketchup_target_min_version, #valid_for_target_sketchup_version?

Methods inherited from SketchUp::Cop

inherited, #relevant_file?

Instance Method Details

#on_send(node) ⇒ Object



84
85
86
87
88
89
# 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