Add SketchupModel entity to LayOut

@DanRathbun here is my current challenge. I am trying to add a Layout::SketchUpModel entity into a Layout::Document with the following code. But what I am getting is a blank page, so nothing is inserted. Pls assume that the global var for filename are correct.

      def add_model
        doc = Layout::Document.open($filename)

        model_file = $filename.split('.layout')[0]

        bounds = Geom::Bounds2d.new(1,1,5,5)
        model = Layout::SketchUpModel.new("#{model_file}.skp", bounds)
        model.current_scene = 0
        model.view = Layout::SketchUpModel::FRONT_VIEW
        model.render_mode = Layout::SketchUpModel::VECTOR_RENDER
        model.render if model.render_needed?

        all_layers = doc.layers
        this_page = doc.pages.first

        doc.add_entity(model, all_layers.first, this_page) 
        doc.add_entity(model.entities, all_layers.first, this_page)       

        doc.save
      end

From the doc here I understood that the SketchUpModel entity, is an instance of a SketchUp Model that is inserted into a .layout file. In other part of the doc here suggest that we can add entities to the Layout. But nothing happens if I add the model itself or model.entities as a group representation of the model in exploded form. So how to add an SU model to the Layout successfully?

@DanRathbun I am attaching my current file for better overview. I also tried to add some simple geometries, like a rectangle but no success. In my code you may see that I am creating some Pages, so called Scenes in SU, and that part works ok. Also my set_camera method works fine, but to add eventually anything to the LO.
layout_drawings.rb (3.8 KB)

BTW, once I get this done I’ll move to implement it with a Layout template file. I’m not sure if this info is important for the functionality of how to add SU models to LO, but I just wanted to mention.

$filename

You shouldn’t be using global variables.

@Neil_Burkholder, thanks for this. I’ll refactor my code accordingly.

Incorrect… a Layout::SketchUpModel entity is a viewport object that “looks” at a Sketchup::Model object. So you would be better off using a viewport reference name (rather than a model reference,) so as not to confuse yourself.

Secondly you do the render after adding the objects to the Layout::Document and before saving.

Also make sure that your coordinates for the viewport bounds are inside the page margins.

@DanRathbun, so far I am unable to add anything to the LO based on the API doc. Can you advise how to add a simple rectangle to LO?

Stay on topic please, with adding a Layout::SketchUpModel entity.

There needs to be some rules here.
Do you add a page for the new viewport or always put it on the first page ?
What size is the page and it’s margins ?

@DanRathbun, I am trying to add the entities into the first page. What is a viewport in API terms? This terms doesn’t exist in the API doc. Could you elaborate it?
I couldn’t find an instance method which could check the paper size but I can open the layout file as that is generated correctly from my code. There I can see the paper size is US letter and 1/2" margins all around. In my file, the bounds are bounds = Geom::Bounds2d.new(1,1,5,5) which should be in the upper left quarter of the page.

Get and set paper properties via …

As I said earlier … (perhaps in the other topic) … the API authors chose a misleading name for the Layout::SketchupModel class. It should have been named Layout::ModelViewport.

As I said above … in responce to your incorrect assumption (that a Layout::SketchupModel object was the same as a Sketchup::Model object) …

In English, a viewport is a window. It is an opening that you look through to view some object, an interior space, or the outside space if you the viewer are inside looking outward.

In 3D CAD applications, paper space viewports are 2D windows that look into the 3D model space from a specific camera orientation in the model space.

Okay, this is a condensed edition of what I got to work.
It should serve as a simple example to add a viewport page to a LayOut document object:

      # Add a model viewport page to a LayOut document.
      # @param document [Layout::Document] A LayOut document object.
      # @param model_path [String] The filepath to the SKP file.
      # @param title [String] The name for the viewport page and layer.
      # @param scene [Integer] The zero-based model scene page index.
      # @param offset [Float,nil] (1") The offset padding inside the page
      #   margins. Pass nil for "Last Saved SketchUp View".
      def add_model_viewport_page(doc, model_path, title, scene, offset = 1.0)
        # Set the 2D bounds for the new viewport (in inches):
        info = doc.page_info
        view_bounds = Geom::Bounds2d.new(
          offset + info.left_margin,  offset + info.top_margin,
          info.width - ((2*offset) + info.left_margin + info.right_margin),
          info.height - ((2*offset) + info.top_margin + info.bottom_margin)
        )
        # Create the viewport object:
        viewport = Layout::SketchUpModel.new(model_path, view_bounds)
        # Set the viewport scene index to 1 more than the model scene:
        viewport.current_scene = scene.nil? ? 0 : scene + 1
        # Add a LayOut document page for the new model viewport:
        this_page = doc.pages.add(title)
        # Create an unshared layer for this new page only:
        layer = doc.layers.add(title)
        layer.set_nonshared(this_page, Layout::Layer::UNSHARELAYERACTION_CLEAR)
        this_page.set_layer_visibility(layer, true)
        # Add the viewport object to the document page on the unshared layer:
        doc.add_entity( viewport, layer, this_page )
        # Render the viewport:
        viewport.render_mode= Layout::SketchUpModel::HYBRID_RENDER
        viewport.render if viewport.render_needed?
      end ### add_model_viewport_page()

Also, …

  • When doing an LayOut export, the document page indices are zero based, so if there is only 1 page in the document, then using “from: 1, to: 1” will raise a RangeError as the indices will be out of range.

  • The API docs do not explain well (if at all,) that the 2D geometric origin for each page is the top left corner of the paper, not the intersection of the top and left margins. So the origin is actually outside the margins we would want to keep our objects within.
    The sets up a scenario that we could call the “canvas” which will be the area within the page margins. It will be advantageous to write little methods that convert canvas space coordinates and bounds to paper space for use with LayOut entity factory methods, so that you can think in terms of “canvas” space and coordinates, and ensure that your objects are always within the margins.
    Or … you can create a 2D transformation that performs a translation down and to the right by the amount of the top and left margins, and apply this transform to each entity after you add it to a page.

And more so…

  • The information for the Layout::PageInfo class is incorrect. There is no such thing as “document units”. All methods return Float values in inches.
    (So I edited the above example to remove the “hack” of temporarily setting the document units to inches, as it is unnecessary, as page properties are always in inches.)

:bulb:

1 Like

@DanRathbun, thanks for the workaround. This was helpful and now I have got it to work :wink: …I still have a lot to do around it but I got the concept. (camera settings, update page, hidden layers, etc., I’ll leave them for other posts if needed)

Furthermore, I had one major error which contributed to my lot’s of questions. That was a wrong Layout::Document as I was grabbing an empty LO doc with this: doc = Layout::Document.new rather than the one which included the entities already added by doc.add_entity.

One thing is still connected here. How to grab the .skp file to create the Viewport? It seems that it matters if I save the file before sending to LO. If I do model.save it does exactly the same as I hit the “save button” in the UI of SU? Otherwise it creates the viewport from the last saved version, if I got it right.

The bounds are also ok now, and I keep all units in inches. I’ll need several “utility” methods to create a proper bounds with scaled object. They will need to be in 1:50 or 1:100 or something similar, based on the building size.

I paste my code below which works ok, except the hidden layers.

module Kisfali
  module Scaffolding
     module LayoutDrawings
      
      extend self

      def create_layout_doc
        model_path = Sketchup.active_model.path
        if model_path.empty?
          UI.messagebox("File not saved. Please save your file and try again.", MB_OK)
          return nil
        else
          name = model_path.split('/')[-1].split('.skp')[0]
          path = model_path.split('/')[0..-2].join('/')
          @filename = File.join(path, "#{name}.layout")
        end
        template = "#{PLUGIN_ROOT}/20-009 Trebex template.layout"
        doc = Layout::Document.new(template)
        status = doc.save(@filename)
        puts status
      end

      def prep_model
        model = Sketchup.active_model
        entities = model.active_entities
        pages = model.pages
        arr_of_pages = pages.to_a
        if pages.length != 0
          arr_of_pages.each{|page| pages.erase(page)}
        end
        views = ["Front", "Right", "Back","Left"]
        views.each {|view|
          pages.add view
        }
        pages.selected_page = model.pages[0]
      end

      def turn_off_spaces #layer in model
        model = Sketchup.active_model
        layers = model.layers
        layers.each {|layer|
          if layer.name.include? "Space"
            layer.visible = false
          end
        }
      end

      def set_camera
        turn_off_spaces
        model = Sketchup.active_model
        pages = model.pages
        puts "scene length: #{pages.length}"
        page = pages["Front"]
        page.use_hidden_layers = true
        puts "page layers count: #{page.layers.length}"
        status = page.update
        puts "Front page status: #{status}"
        model_center = model.bounds.center
        puts "center x: #{model_center.x.to_m}"
        # Create a camera from scratch with an "eye" position in
        # x, y, z coordinates, a "target" position that
        # defines what to look at, and an "up" vector.
        eye = [model_center.x, -30.m, model_center.z]
        target = [model_center.x,model_center.y.to_m,model_center.z]
        up = [0,0,1]
        my_camera = Sketchup::Camera.new eye, target, up
        my_camera.perspective = false
        # Get a handle to the current view and change its camera.
        view = Sketchup.active_model.active_view
        view.camera = my_camera
        status = page.update(33) #camera + visible layers
      end

      def add_model
        doc = Layout::Document.open(@filename)
        layers = doc.layers
        new_layer = layers.add("Scaffold", shared=false)
        layers.active = new_layer
        first_layer = layers.first
        this_page = doc.pages.first
        page_info = doc.page_info
        # Get the paper height and width and set the output resolution
        width = page_info.width
        height = page_info.height 
        bounds = Geom::Bounds2d.new(1,1,24,15)
        model_file = @filename.split('.layout')[0]
        viewport = Layout::SketchUpModel.new("#{model_file}.skp", bounds)
        # Set the viewport scene index to 1 more than the model scene:
        # 0 is the auto-added scene by LO as Last Saved SU View
        viewport.current_scene = 1 # that's the Front scene
        viewport.view = Layout::SketchUpModel::FRONT_VIEW     
        doc.add_entity(viewport, new_layer, this_page) 
        viewport.render_mode = Layout::SketchUpModel::VECTOR_RENDER      
        viewport.render if viewport.render_needed?
        doc.save
        return doc
      end

      def export_layout(doc)
        # Export pages one through three at high quality, compressing jpeg images
        # at 0.75 compression quality (valid range is 0.0 - 1.0). Note that the
        # first page of a {Layout::Document} is index 0.
        options = { 
          start_page: 0,
          end_page: 1,
          output_resolution: Layout::PageInfo::RESOLUTION_HIGH,
          compress_images: true,
          compress_quality: 0.75 
        }
        export_path = @filename.split('.layout')[0] + '.pdf'
        puts export_path
        status = doc.export(export_path, options)
      end

      def generate_drawings
        model = Sketchup.active_model
        model.start_operation("Generating 2D drawings", true)
        create_layout_doc
        prep_model
        set_camera
        doc = add_model
        export_layout(doc)
        model.commit_operation
      end
    end
  end
end

And here is how it looks:

At the top of your submodule you may need to initialize module variables.

@filename ||= ""

The reason is that newer Ruby versions are no longer allowing use of @vars that have not been initialized before they are referenced in a statement.

If however, the very first statement that uses @filename is the assignment statement in create_layout_doc() then that statement can be the “initializer”.

Yes, of course because Layout::SketchUpModel.new reads in from an SKP file.

In SketchUp you can test if the model has been modified like …

  prompt_to_save(model) if model.modified?

See: Sketchup::Model#modified?

Can also be …

name = model.title

See: Sketchup::Model#title

… or …

name = File.basename(model_path,'.*')

See: File::basename

Can also be …

path = File.dirname(model_path)

See: File::dirname

The main reason yours could be fragile is the .split('/') as there are still API methods that on Windows platform return path strings with backslashes ('\' or '\\').

You can use .gsub(/\\\\|\\/,'/') to convert them to Ruby Unix-style path strings which Windows NT has always understood.

def slashify(path)
  path.gsub(/\\\\|\\/,'/')
end

See: String#gsub

~

@DanRathbun, thanks for the answer and the tips :wink:

yes, actually that assignment is the very first statement for @filename

This is cool.

I’ll rid off .split"(/") and go for the Win-safer solution :slight_smile: Thx

FYI, … prompt_to_save(model) is referring to one of your extension’s methods, not something in the API. You may name it whatever you wish however.

1 Like