More interesting problems with text


#1

I’ve already noted that the API cannot set any text font characteristics.
Today I discovered that adding text attached with a leader ignores the preferences text font bold setting. (So called “screen text” does follow the bold setting.) Same situation if user adds the text.

My extension has one function to label a selected face with text at its centroid, and erase any previous text for that face.

To find the previous text I iterate through the active entities, and for each do text.point and face.classify_point to see which text(s) to delete.

However if there are any “screen” texts, they have no point so text.point gives nil, which crashes face.classify_point. So I have to test for nil point first.

The result is this ruby fragment below. First draw a face on XY plane and add some text (some on it and some not), then select the face. Running it deletes some of the text on the face but not all of them. If you rerun It deletes some more. Something is breaking the for loop but I can’t figure out what it is:

mod = Sketchup.active_model # Open model
ent = mod.active_entities # All entities in context
sel = mod.selection # Current selection

selface = sel[0]
for element in ent
	puts element
	if element.class == Sketchup::Text
		textitem = element
		textitem.point
		if textitem.point != nil
			textpoint = textitem.point
			puts textitem.text + "is at " + textpoint.to_s
			result = selface.classify_point(textpoint)
			puts result.to_s + " classify"
			if result == Sketchup::Face::PointInside then textitem.erase! end
		end
	end
end

#2

Never erase or otherwise delete an item from an Entities collection while you are iterating over that collection! This will cause seemingly random “fenceposting” errors in which the iteration quits without covering the entire collection. Instead, get an independent Array of references by Entities.to_a and iterate over that. A flat array will not fencepost.


#3

Also, screen text is fastened to the screen not the model so it does not have any of the attributes that would tie it to model coordinates. That’s why it returns nil for #point. It would be much better if there were separate derived classes in the Ruby API for screen and leader text.


#4

A quicker, simpler, safer iteration would be:

...
    togos=[]
    ent.grep(Sketchup::Text).each{|textitem|
      if textpoint = textitem.point
        puts textitem.text + "is at " + textpoint.to_s
        result = selface.classify_point(textpoint)
        puts result.to_s + " classify"
        togos << textitem if result == Sketchup::Face::PointInside
      end
    }
    ent.erase_entities(togos) if togos[0]
...

#5

Is “fenceposting” a ruby feature or a ruby bug?


#6

“Fenceposting” is a general effect in a great many of the multi-element storage techniques used in computer programming, not just Ruby and not just the SketchUp API. In effect, the issue is like the cliche of sawing off the limb you are sitting on. The data structure loses its place.


#7

That’s really helpful. I’ll give it a try.
Didn’t know about grep outside of Robert Heinlein novels.
Or maybe that was grok.


#8

grok (Stranger in a Strange Land). grep is an old-time UNIX command with a typically cryptic name :slight_smile: Reportedly it comes from the ed editor command to display all occurrences of a regular expression in a file:
g = global
re = regular expression
p = print


#9

Globally search a REgular expression and Print == GREP
Or something like that…
It’s a quick way of extracting ‘types’ from a collection.
It works on Arrays AND Collections like Entities and Selection.
If you pipe the results through a select{} it’s faster that having several steps.
I just used it here to avoid changing the entities AND of course collected the items ‘to-go’ and erased them in one pass, which is also quicker…


#10

I tried your code (even though I don’t really understand how it works) but got:
“undefined method `<<’”
???


#11

Typo! Should be

togos << textitem if result == Sketchup::Face::PointInside

TIG was trying to push the textitem onto the togos Array.


#12

I went with the following because I could understand it;

deltextarr = Array.new
	for textitem in ent
		if textitem.class == Sketchup::Text
			textpoint = textitem.point
			if textpoint != nil
				result = selface.classify_point(textpoint)
				if result == Sketchup::Face::PointInside then ans = deltextarr << textitem end
			end
		end
	end

if deltextarr.length > 0
ent.erase_entities deltextarr
end


#13

Sorry about the typo.
As Steve said I typed that up quickly and got the array and entity references reversed in the << [push] !
I’ve corrected the original code in an Edit…
:blush:

With the GREP method you can erase the items as you go without worry because the GREP has already made an array from a sub-set of the entities-collection.

Erasing one at a time is slightly slower that the collect-into-a-togos-array, then erase all togos ‘in one foul swoop’.
But unless you have a considerable number to erase you are unlikely to perceive the time saved difference in the methods…


#14

You say it crashes face.classify_point - does that mean SketchUp crashes with a BugSplat?


#15

No Bugsplat. It causes a Ruby error that stops the script.
Just by chance, I happened to have a screen text among active elements.
Otherwise I would never have found the problem.