Anyone know of a plugin or plugins that will do Search and Replace on layer and/or component names?

searchandreplace
componentnames
layernames

#1

Does anyone know of a plugin that will search and replace in either or both of components and layers, to enable one to change layer names ‘en bloc’?

Working with Scott Baker @NewThinking2, we have a large and complex model. Components and floor names start with an F for floor, and we started numbering from F0 to F9, and corresponding layer names. We have now got to floor 10, and have realised that we should have used F00 - F09 for lower floors, so that things will list in proper sequence in the Layer Window and Component Browser.

It is of course possible to do manually, but tedious - change F0 to F00, F1 to F01, etc everywhere it occurs in the layers window, or the component browser.

And check and change name capitalization for consistency.

What we need is an equivalent of a case-sensitive Search and Replace as in a word processor, but for the component definitions and layer names.

Any thoughts? Either on where to find one, or how difficult it would be to create one by adapting someone’s existing plugin?


#2

Unless I misunderstand, what you describe does not sound terribly difficult. You don’t have to search over the model Entities themselves or anything in the layers or components browser windows.

You can access all of the Layer names in the model via

Sketchup.active_model.layers.each {|layer| 
  # do a pattern match of layer.name and revise the required names
}

The second part varies a little depending on whether you are talking about ComponentDefinition names or ComponentInstance names, but the general idea is

Sketchup.active_model.defintions.each {|defn|
  # if you want the definition names, pattern match and reassign each defn.name as required
  # if you want the ComponentInstance names:
  defn.instances.each {|inst|
    # do the match and replace test for inst.name
  }
}

The exact test and replace logic will of course depend on the variety of names you have actually used, but shouldn’t be hard to implement.


#3

Thanks, Steve. I’ll see if my Ruby skills are up to using what you suggest, but not tonight - too late (after midnight here).

It’s mainly the component definition names we want to change as well as the layer names, but a few components have got non-null instance names like ‘m’ or ‘o’ or ‘t’ for reasons you as a Mac user will know!


#4

it’s a fairly trivial script to find and replace from Ruby Console

adding to Steve’s reply when you find the names you can use

.sub(/\AF/, 'F0')

the \A is for start of string, and F is capital F…

john


#5

Many thanks to you both.


#6

A tweak I forgot in the stub code: in the each block for ComponentDefinitions, skip Images using

next if defn.is_a?(Sketchup::Image)

Images don’t have names, so you would otherwise get a missing method error if there are any Images in the model!


#7

Pls. keep in mind that we can just change the layer names - which ought to take less than 1/2 hour - and leave the component names as is. There are too many exceptions as it is and doing a replace on them wojuld be more trouble than it’s worth.


#8

http://ruby-doc.org/core-2.2.4/String.html#method-i-gsub

if layer.name.start_with?('F','f')
  # Replace starting F or f with F0:
  layer.name= layer.name.gsub(/\A(F|f)/,'F0')
end

OR

if instance.name.start_with?('F','f')
  # Replace starting F or f with F0:
  instance.name= instance.name.gsub(/\A(F|f)/,'F0')
end

However, regular expressions also have a case-insensitive “i” switch
(switches are placed just following the closing / of the expression.)
This can shorten the expression from: /\A(F|f)/, to: /\Af/i,
which is read as "case-insensitive match any f character at start of string"
Example:

if layer.name.start_with?('F','f')
  # Replace starting F or f with F0:
  layer.name= layer.name.gsub(/\Af/i,'F0')
end

Ruby versions prior to 2.x (SketchUp 2014 and earlier,) did not have the String#start_with?() method
so (for older SketchUp support) you may need to also use a regular expression match in the if:

if layer.name =~ /\Af/i
  # Replace starting F or f with F0:
  layer.name= layer.name.gsub(/\Af/i,'F0')
end

http://ruby-doc.org/core-2.2.4/doc/regexp_rdoc.html
http://ruby-doc.com/docs/ProgrammingRuby/html/intro.html#S5
http://ruby-doc.com/docs/ProgrammingRuby/html/language.html#UJ


# Split the string at word boundaries, iterate the array capitalizing 
# members that can be recognized as words with ASCII characters,
# and stitch the array back together assigning it back to the object:
lname= lname.split(/\b/).each{|s|s.capitalize!}.join

http://ruby-doc.org/core-2.2.4/String.html#method-i-split
http://ruby-doc.org/core-2.2.4/Array.html#method-i-each
http://ruby-doc.org/core-2.2.4/String.html#method-i-capitalize-21
http://ruby-doc.org/core-2.2.4/Array.html#method-i-join

Ruby’s String capitalize methods will only cap lowercase characters within the ASCII set. So it’s just easier to let Ruby’s C side code handle this. (Much faster than Ruby-side string comparison in a conditional expression.)


I posted several code examples in a “Find components by name” thread, one for components, the other for groups:


#9

Dan, thanks to you as well as Steve and John.

I’ll see what I can do to put these ideas into practice over the next few days.


#10

The layer name replacement worked a treat. Used the following copied into the Ruby console. All layer names that start with an F are as it happens followed by a single digit and a space, and are the ones I want to change, so it works fine.

Sketchup.active_model.layers.each {|layer|
  # do a pattern match of layer.name and revise the required names
if layer.name.start_with?('F','f')
  # Replace starting F or f with F0:
  layer.name= layer.name.gsub(/\Af/i,'F0')
end
}

Trying to do the same with component definitions.

Some non-floor-specific component names also start with F or f and I don’t want to change them.

So realise I need to use a regular expression to test for starting text to change:
start_with? ... the letter F or f followed by a single digit and a space.

Which I think would be a regexp:
/F\d /

If I just put the regexp instead of the single letters 'F' ,'f' in the sample code, Ruby gives an error: no implicit conversion of Regexp to String.

Here’s the test code (not trying to change anything yet, just seeing if I can select the right components) just looking for start_with? ‘F’.

  i = 1
  puts "Starting"
  Sketchup.active_model.definitions.each {|defn|
  # if you want the definition names, pattern match and reassign each defn.name as required
  # if you want the ComponentInstance names: use defn.instance.name instead
    next if defn.is_a?(Sketchup::Image)
    # do the match and replace text for defn.name
    if defn.name.start_with?('F','f')
      #defn.name = defn.name.gsub(/\Af/i,'F0')
 #     unless defn.name.start_with?('F10','F11')
       puts defn.name
      i = i + 1
      break if i > 10
    end
  }

This works to list the first eleven component names starting with F or f.

How do I change it to match the pattern instead?

I’m feeling tired and rather dense tonight, and haven’t opened the Ruby console for several months, so I’ve forgotten most of what I ever knew!


#11

You’ll need to use a regular expression for the conditional, that checks if the “f” character is followed by a digit character (\d) and a whitespace character (\s),… ie:

starts_f_digit_space = /\Af\d\s/i
starts_f_insen = /\Af/i
Sketchup.active_model.definitions.each {|cdef|
  # do a pattern match of layer.name and revise the required names
  if cdef.name =~ starts_f_digit_space
    # Replace starting F or f with F0:
    cdef.name= cdef.name.gsub(starts_f_insen,'F0')
  end
}

Notice, how the regular expression objects are created outside the loop,
so they are not being re-created with each iteration.


#12

Ah, can’t use start_with? with a regex, then.

And I’ve just looked up: \A means 'starts with` in a regex.

Thank you.


#13

Although the docs say nothing about it, I tried yesterday and attempting to pass a RegExp as an argument to start_with?() raises a TypeError exception with a message similar to “no implicit conversion of String to RegExp”.

You could do this however it is very clunky:

if cdef.name.start_with?(
'f0 ','f1 ','f2 ','f3 ','f4 ','f5 ','f6 ','f7 ','f8 ','f9 ',
'F0 ','F1 ','F2 ','F3 ','F4 ','F5 ','F6 ','F7 ','F8 ','F9 '
)

#14

Or here’s another multiple expression conditional:

if cdef.name.start_with?('f','F') &&
cdef.name[2] == ' ' &&
cdef.name[1].ord.between?(48,57)
 # replace here
end

#15

have a play at rubular, it’s a quick and dirty ruby regular expression test site…

also, to search for definitions by name, I would use

matches = defs.find_all{|d| d.name =~ /(fF)/ }

john


#16

I’d think

matches = defs.find_all{|d| d.name =~ /\Af\d\s/i }

will work better, as he only wants those that start with F or F, followed by a digit and a space.
(Your example will match f characters anywhere in the name, not just the start.)


#17

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.