How to "align view" in ruby

Hi,

For each scene, I need to align the view with the biggest face (programmatically).
How could I code that?

Thanks for your suggestions!

Collect all of the faces:

model = Sketchup.active_model
faces = model.active_entities.grep(Sketchup::Face)

Find the largest one:

area = 0
face = nil
faces.each{|f| (face = f; area = f.area) if f.area > area }

Find its center and normal

center = face.bounds.center
normal = face.normal

Make a new camera with its target=center, eye=center.offset(normal) and up=Z_AXIS

cam = Sketchup::Camera.new(eye, target, up)

Get a handle to the current view and change its camera.

view = model.active_view
view.camera = cam

Zoom extents and save scene.

new_view = view.zoom_extents
page = model.pages.add('Scene 1') # Choose name ?
# Also choose the page settings too ?? line use_camera = true
1 Like

Thank you TIG for the advices.

For the moment, I am stuck at the very first step: how to go from a ‘page’ to its ‘faces’?
A Page has no ‘entities’. Model does.
I’ve tried to assign the page to the model, but without any succes: I always get the same set of entities (while each of my scenes shows different parts of the whole model)…

@mod.pages.each {|page|
@mod.pages.selected_page = page
puts “‘#{page.name}’”
@mod.active_entities.each{|e| puts “\t#{e.class}”}
}

Any idea about what I am doing wrong?

Thx.

You are right, pages do not own collections of Entities, they just remember various settings such as camera, style, etc. The model itself and ComponentDefinitions (associated with Groups and Components) are the only things with Entities collections. So, you need to choose some way to find the face you want to align with. For example, have the user select it first. Or code some logic to find it.

Thank you slbaumgartner for your reaction.

In each scene, I need to select (programmatically) the biggest face.
My starting point is then something like this:

@mod.pages.each {|page|
# find the biggest face of the scene
… ??? …
… ??? …
}

@TIG or another of the code gurus may have ideas of how to proceed efficiently, but I fear this is going to be very complicated! I can’t quite get my head around the needed explicit code, so I’ll just discuss concepts. Others may correct me if they know better - I like to learn :slight_smile:.

By “biggest face” I assume you mean “biggest visible face”? Otherwise the goal doesn’t make sense to me. Why would you want to align to something that can’t be seen? In the same vein, I can’t imagine you mean “biggest face in the model” because then all your scenes would be aligned the same way.

Some aspects of “visible” are easy to test in Ruby, e.g. the hidden flag or the visible setting of the associated layer. Others are much harder, such as whether the face is off-screen or is obscured by something nearer to the camera in a particular View.

A Page (aka scene) object does not have an associated View per-se. Rather, it has a collection of parameters that tell SketchUp how to position the camera and adjust other settings to re-create a View. So far as I know, the only practical way to steer SU to the desired View is to make the desired Page “selected” via the Pages#selected_page= method. And, of course, this will actually cause that Page to display to the user.

Once a desired View is active, the Ruby API doesn’t contain any method along the lines of “all visible”. Rather, it provides a few methods by which to probe the model in the View to see what is there. Some of these apply only within a Tool when the user clicks on a desired point on the screen. Since you don’t want user control, I won’t discuss those. Model#raytest will probe to see what is found on a specific ray through the active View in the active model. You can get the ray through a screen pixel from the current camera via View#pickray. But to test the whole model for visibility it seems to me you would have to iterate these tests over the entire screen, which would be very slow!

@Arbiorix, Some snippets that work for the top level model entities ONLY:

def viewable?(obj)
  view = @mod.active_view
  obj.vertices.all? {|v|
    coords = view.screen_coords(v.position)
    coords.x.round >= 0 && coords.y.round >= 0
  }
end

def find_biggest_face(faces)
  faces.max_by {|f| f.area }
end

def find_viewable_faces(ents)
  allfaces = ents.grep(Sketchup::Face)
  allfaces.select {|face| viewable?(face) }
end

def task()
  @mod = Sketchup::active_model
  ents = @mod.entities
  faces  = find_viewable_faces(ents)
  biggie = find_biggest_face(faces)
end

Sketchup.active_model.selection.add(task)

It is much more complex if you need to test faces nested inside group or components.

@slbaumgartner, thanks again for your implication.

In order to clarify the context, I’ll describe here the situation.
I’ve completed the ‘drawing’ of my project (something to hold music partitions).
For the moment, notice that there is no scene, nor (extra) layer.

Before this becomes a real object, I need to dimension each piece.
My current strategy is:

  • Each piece is a Group
  • For each Group, I create a scene and a layer. By adjusting the layers visibilities of each scene, I get one piece per scene. As this process is repetitive, I do it programmatically.
  • Manually, for each scene, I will draw the dimensions
  • Finally, I can send everything to Layout to bundel a nice report

Up to now, with the following script, I am satisfied.

@mod = Sketchup.active_model # Open model

def groupToScene(group,prefix)
    if group.visible?
        # Create the name
        @name = if prefix.empty? 
                    group.name
                else
                    (prefix.dup << group.name).join(" - ")
                end
        # Create a layer and assign the group to it
        group.layer = @mod.layers.add(@name)
        # Create a scene
        @mod.pages.add(@name)
    end
end

def groupsToScenes(root,prefix)
    root.entities.grep(Sketchup::Group)
    .sort { |a, b| a.name <=> b.name }
    .each { |parent|
        if parent.entities.grep(Sketchup::Group).count > 0
            groupsToScenes(parent,prefix << parent.name)            
        else
            groupToScene(parent,prefix)
        end
    }
end


@mod.options["PageOptions"]["ShowTransition"]= false

# Create an 'All' scene
@mod.pages.add("All")

# Create a Scene for each (leaf) group
groupsToScenes(@mod,[])

# Format all Scene
@mod.pages.each {|page| 
    if page.name != "All"
        # Set Layer visibility for each scene
        @mod.layers.each {|layer|
        page.set_visibility(layer,page.name == layer.name)
      }
    end

    # Zoom on the biggest face
    # TBC

    @mod.pages.selected_page = page
    @mod.active_view.zoom_extents
    page.update(1)
}

After running the code, the layers and scenes have been created.
Ouuups, new users can only post 1 image… (see image 2 in next post!)

But I want more! I would like each piece to be “aligned” by the script, so I can directly start to dimension it. As you said, by convention, if the script align the piece with the biggest face area, it’s Ok for me.

To summaryse, how to complete my code with the equivalent of this manual operation?
Ouuups, new users can only post 1 image… (see image 3 in next post!)

Thanks for reading - and thinking !

And here is “Image 2” …

And “Image 3” !

Please edit your code post (above) to wrap code in triple backtick lines:

```ruby
# code here
```

It will lexed like the snippets I posted above:
How to "align view" in ruby - #7 by DanRathbun

I’ve “blockquoted” the code part, but I am not sure that it is exactly what you want (I don’t really see the difference!)

It isn’t. I explained how to do it properly.

We all see the difference. Your “blockquoted text” does not show proper indents, nor correct color lexing.

Yes, of course, I see it now !
I’ll try again …

Now all that aside. Your going about this the “hard way”. What we do is, make the objects components.

The assembly would be associated with an assembly layer. Then switch off that layer when making the scenes.

Then insert new instances off to the side somewhere aligned with the global axes. (Actually they can be inserted at the ORIGIN if they’ll all be associated with a unique layer that is only on for one scene page.)

Then just use the standard Top, Side, Front, etc. views to set up scenes. You can associate the second instances each with a layer of it’s own.

See the Sketchup::send_action() method for using the standard views:
Module: Sketchup — SketchUp Ruby API Documentation

If doing this by code, you’ll need to temporarily set scene transitions off, and use a FrameChange or View observer to know when the view is ready to be updated, ie, calling view.update().

It’s a bit better now. But I still can’t get the colors (so stupid!)…

The strings are lexing in color now.

The first line of triple backticks needs a language name (ruby, text, javascript, C, html, css, xml, … etc.) immediately following the three backticks, in order to be lexed correctly.

BTW, Ruby uses 2 space indents. (Tabs do not transfer well to the forum.)

Thank you @DanRathbun for your energy!

For the formating of the code on the forum, I think I finally got it :slight_smile:!

About making each piece a component, [quote=“DanRathbun, post:15, topic:41621”]
What we do is, make the objects components.
[/quote]

this goes against my current understanding of what a Component is. I though a Component is something that has multiple instances. But Ok, let’s discover a new usage of the Component!

So, instead of splitting the whole object in groups, I should split it in components.
Then, to dimension the various pieces, I do it on a new instance of each component. Right?
Can you confirm this strategy? Do you know any good tutorial about this process?

Jumping back in…

Yes, a primary purpose of components is to allow multiple instances that all continue to match if you edit any one of them. But there is nothing that says there have to be multiple instances. Groups and Components are the two ways you isolate edges and faces so that they don’t interact with ones from other groups or components or loose ones in the model.

For creating “exploded” diagrams, views of individual parts, doors in open vs closed position, etc. it is very common practice to make each part a component and to place a copy aside from the location where it fits into the completed assembly. You can rotate the copy in whatever way makes it show as you desire. By associating different layers with these copies, you can create scenes that each show any required set of component pieces and their dimensions (also associate the dimensions with layers so they aren’t clutter on scenes where they aren’t appropriate).

I do that all the time in my furniture designs.

Here’s an example that has annotations rather than dimensions, but you can see the idea

Ok, thank you @slbaumgartner.
You put me back on the right way!