How to make a selected face be truly flat

I’ve been tracking down an issue, that I should write up about sometime, because the initial symptoms are wild and confusing. I did finally figure out what the real issue was, and I’m trying to find an extension that could take care of the underlying issue. I’m avoiding telling what the original symptoms were, it might send you off into the hours I spent before I saw what was the important part!

So, SketchUp considers something to be coplanar if all of the internal points are on the same plane, but there is a tolerance to that, and imported geometry may have a smaller tolerance. This comes up with importing CAD, and there are extensions such as Eneroth Flatten to Plane to help with that. For the problem I’m solving the selected face may not be on any axis, so the flattening direction is the normal of the face I suppose. That might be as little as a one line change in the flatten to plane code.

But, I think it flattens back to zero, and not just back to the edges of the selected face. Here are manual steps that achieve exactly what I want to do:

  1. Double click down to the geometry.
  2. Use the Axes tool to set the axes so that red is inferred from whatever direction the face goes left, green from the direction it goes down, leaving blue to be coming straight out of the face.
  3. Use Eneroth Flatten to Plane.
  4. Right-click, Reset the axes.

One extension that comes close to that is Move to Plane (TT, inside Architect Tools). It does show up the internal difficult triangles, but in my test case there isn’t a target plane, all possible planes are not really flat.

If Move to Plane could be told to consider what SketchUp sees as one face to be the plane to move to, that might do what I need.

A perfect extension would take the entire model and go down to every face that SketchUp sees, and does a move to plane of the internal geometry that was imported, based on the plane that is the single face SketchUp is seeing.

Is there already an extension, or are any of you intrigued enough to modify existing code?

Just a few questions for this “non-planar scenario” …

Was the “Merge coplanar faces” option selected during import, or does it matter if it is checked or not ?

Does the this send action work for the whole model or just what is selected, and does it work for your case ?

Sketchup.send_action("fixNonPlanarFaces:")

Thank you for the idea. In both cases I get a no problems found message, which makes sense, because SketchUp sees the face as being coplanar. The example I’m working on is a tiny part of a large model, that was started a long time ago. Don’t know what import options were selected.

An easy way to show the problem cases is to export as Collada and bring that back in. Then you can use the tape measure to see the real coordinates. This screenshot shows two parts of the same face, both of which should have been at 0.0", but are at 0.000250" and 0.000318", enough of a difference to cause problems, but not enough to seem non-coplanar.

I’ve been trying other extensions. Came across the @TIG Flatten to Plane extension. It’s the same as Move to Plane, but it uses Work Plane as a way to specify the plane to flatten to. I seem to have to reinstall it each session for some reason, but the bigger problem is that it puts the flattened points into a group. To recreate the original face I need to paint bucket sample the existing face, delete it, explode the flattened group, draw a line, erase the line, then paint bucket fill the new face.

It’s also not quite right as a solution because the points that are now fixed are sometimes in use by other connected faces which would leave some number of line endings not quite touching the flattened points. I read a comment in the SketchUcation forum, that referred to right-clicking and choosing Flatten Faces. I couldn’t find that option, but it does sound close to what I’m trying to do.

Another extension I tried was FredoScale. Unfortunately it doesn’t see the small differences in the coordinates, and so the box scaling option doesn’t give me middle handle to use in order to scale it to zero. If I push pull the face out a little it does then work well, but I still have to do the repair work to get the face and material back.

If I can track down the extension that has the right click, flatten faces option, that might do what I need.

In any case, finding extensions that almost do what I need gives me hope.

Vertex tools, adjust the Gizmo to the angle you want and hit the Make Planar button.

Colin,
I’ve needed this function in the last week or so and decided to make a first try at writing a solution. The code is based around Eneroth’s flatten to plane, a bit of clean up code from Dan Rathbun, with maths from Emil Ernerfeldt. Fitting a plane to many points in 3D

Usage:

  • Select any set of faces and edges
  • Click on the Extensions menu > Flatten to Calculated Plane

The selected geometry will be collapsed onto a plane that represents a ‘best fit’ to the selected entities.

Any edges in the original selection that end up dividing coplanar face will be deleted.

The extension
sw_flatten_to_calculated_plane.rbz (2.4 KB)

A test file that mimics your initial question
start box.skp (74.4 KB)

For the Rubyists among us

####################
# based on Eneroth's Flatten to Plane Extension
# https://extensions.sketchup.com/pl/content/eneroth-flatten-plane

module SW
module FlattenToCalculatedPlane

  def self.purge_invalid_texts(entities)
    entities.grep(Sketchup::Text) { |t| t.erase! if t.point.to_a.any?(&:nan?) }
    nil
  end

  # Constructs a plane from a collection of points -
  # so that the summed squared distance to all points is minimzized,returns a plane ie. [point, vector]
  # Ideas and impplementation from: http://www.ilikebigbits.com/2017_09_25_plane_from_points_2.html
  def self.plane_from_points(points)
    return false if points.size < 3 # At least three points required
    sum = Geom::Point3d.new(0,0,0)
    
    points.each {|pt| sum += pt.to_a } # The + operate doesn't convert to vector from a point
    tr = Geom::Transformation.scaling(1.0/points.size)
    centroid = sum.transform(tr)
    
    # Calc full 3x3 covariance matrix, excluding symmetries:
    xx = xy = xz = yy = yz = zz = 0.0
    points.each {|p|
      r = p - centroid.to_a;
      xx += r.x * r.x;
      xy += r.x * r.y;
      xz += r.x * r.z;
      yy += r.y * r.y;
      yz += r.y * r.z;
      zz += r.z * r.z;
    }
      
    xx /= points.size
    xy /= points.size
    xz /= points.size
    yy /= points.size
    yz /= points.size
    zz /= points.size

    weighted_dir = Geom::Vector3d.new(0,0,0)
   
    det_x = yy*zz - yz*yz
    axis_dir =  [ det_x, xz*yz - xy*zz, xy*yz - xz*yy]
    weight = det_x * det_x
    weight = -weight if weighted_dir.dot(axis_dir) < 0.0
    tr = Geom::Transformation.scaling(weight)
    weighted_dir += axis_dir.transform!(tr)
   
    det_y = xx*zz - xz*xz
    axis_dir = [ xz*yz - xy*zz, det_y, xy*xz - yz*xx]
    weight = det_y * det_y;
    weight = -weight if weighted_dir.dot(axis_dir) < 0.0
    tr = Geom::Transformation.scaling(weight)
    weighted_dir += axis_dir.transform!(tr)
    
    det_z = xx*yy - xy*xy
    axis_dir = [ xy*yz - xz*yy, xy*xz - yz*xx, det_z]
    weight = det_z * det_z;
    weight = -weight if weighted_dir.dot(axis_dir) < 0.0
    tr = Geom::Transformation.scaling(weight)
    weighted_dir += axis_dir.transform!(tr)
    
    normal = weighted_dir.normalize
    
    return false if !normal.valid?
    [centroid, normal]
    
  end

  # remove edges from coplanar face (only from the slected entities)
  # https://forums.sketchup.com/t/deleting-redundant-edges-from-a-solid/104585/3
  # From: Dan Rathbun
  def self.remove_coplanar(ents)
    edge_list = ents.grep(Sketchup::Edge)
    redundant = edge_list.find_all do |e|
      next false unless e.faces.size == 2
      vector = e.faces.first.normal
      e.faces.all? { |f| f.normal.parallel?(vector) }
    end

    ents[0].model.active_entities.erase_entities(redundant) unless redundant.empty?
  end
    
  # Flatten to plane, Code adapted from Eneroth
  def self.flatten_to_plane(ents, remove = false)
    # If curves are not exploded moving one vertex will also move its neighbours,
    # causing a very unpredictable result.
    curves = ents.select { |e| e.respond_to?(:curve) }.flat_map(&:curve).compact.uniq
    curves.each { |c| c.edges.first.explode_curve }

    vertices = ents.select { |e| e.respond_to?(:vertices) }.flat_map(&:vertices).uniq
    original_points = vertices.map(&:position)
    
    plane = plane_from_points(original_points)
    return if !plane
    #p plane ### diagnostics
     
    vectors = original_points.map { |p| p.project_to_plane(plane) - p }
    ents.first.parent.entities.transform_by_vectors(vertices, vectors)
    
    #remove coplanar
    remove_coplanar(ents) if remove == true

    # Using transform_by_vectors on several vertices at once may cause 2D texts
    # from going mad with NAN coordinates and break SketchUp rendering.
    # Aka the "Zoom Extents" bug.
    purge_invalid_texts(ents.first.parent.entities) if ents.size > 0

    nil
  end

  def self.flatten_to__calculated_plane_operation
    model = Sketchup.active_model
    model.start_operation("Flatten to Plane", true)
    flatten_to_plane(model.selection, true) if model.selection.size != 0
    model.commit_operation

    nil
  end

  unless file_loaded?(__FILE__)
    file_loaded(__FILE__)
    menu = UI.menu("Plugins")
    menu.add_item(EXTENSION.name) { flatten_to__calculated_plane_operation }
  end
end
end

1 Like

Thanks Box and sWilliam for those two suggestions. Vertex Tools does fix things too, but not quite as easily as sw_flatten_to_calculated_plane!

Can I use the extension to help solve the big model problem one of our customers is having?

The underlying problem is that SketchUp’s tolerance for drawing new faces is smaller than its tolerance for what is coplanar. This means that an apparently completely flat surface gains extra faces when you try to break the existing face, if the face is made from imported geometry. SketchUp ought to fix the locations of points that are deemed to create a coplanar face, so that mathematically it is coplanar, and not just visually.

Here is a video of me trying to split the single face into two parts, only ending up with two new faces exactly on top of the existing face. The extension quickly fixes that.

I have no objection to my lil’ part helping others. It’s why I posted it. It was basically just converting pseudo code description of the task into concise Ruby code as possible.

But it may suffer from the same “SketchUp has differing behavior” syndrome.
My lil method uses the API’s idea of parallel vectors (within the API tolerance) to decide if faces are coplanar. I seem to recall a topic thread on this that TT weighed in on. It was about the API’s lack of a “coplanar” test method for the API.

I think SW gets around this by first doing what TT suggests, so that by the time he calls my method the faces are truly coplanar, so the normal vector compare can be trusted.

Tried that… trouble is that you run into scenario where the small adjustments to the vertices will trigger autofold on connected faces.

Any any script that tries to move a vertex with 100% accuracy needs to use a scaling transformation instead of a translation transformation. If you use entities.transform_by_vectors then any vectors with magniture less than 1/1000 will be ignored since SU will consider it close enough. The only thing that works to move a vertex a small amount is to use a scaling transformation of magnitude 0 and origin at the location you want to move the vertex.

1 Like

I think that I would be ok with auto fold. You could then adjust the affected face.

The important thing would be that coplanar faces match with coplanar edges/

But autofold would make the new faces not planar. So you still have issues working with the model.

Auto fold wouldn’t affect the face you are asking to have coplanar edges, but it could cause a face that shares one edge to suddenly have two faces. But that might be ok, so long as they are not seen as being coplanar to each other.

Reducing the threshold of what is considered coplanar would help to at least point out the problem cases.

Interesting thread. @colin , I’m designing a climbing gym right now (stepping away from houses for a moment, climbing is a big part of my life so got my chance to do a gym now)… I need something like this because when I’m creating the walls, the faces need to be perfectly planar, conceptually if they are quads that are slightly out of kilter so really polygons, than that’s fine conceptually, but the final product needs to have majority of quads be actually perfectly flat faces and not slightly out of kilter… This has lead me down a rabbit hole tonight. I will say, from my tests, I tested 4 plugins that all make planar faces, vertex tools 2, artisan, quad face tools and the script that the person wrote above the planar by calculated plane. All plugins do exaclty the same thing, they basically take an average of the selected faces and flatten them, I confirmed this by measuring and determing different angles and seeing the results… What sketchup truly needs is a plugin that wraps geometry very nicely (cad mans mesh wrapper is not super great from my experience on this climbing gym, it’s decent though, i actually found a ton of use to simplify my geometry with the skimp plugin @Whaat thanks Dale, amazing plugin. Dale, I hope you’re listening, we need a killer mesh wrapper that allows the option to only create perfectly flat faces not sort of flat faces like mesh wrapper does.

Here’s what I’ve found so far is the best workflow for flattening faces:

I start my gym wall modelling by either sub dividing a rectangle into a grid with split tools or dividing lines on the face, etc, many options there, than I move the face to create the climbing walls with the vertex tools 2 plugin, I like this a lot more than the move tool for this because I can choose a radius that effects nearby geometry, this leaves much better looking and detailed results, however unfortuantely at the end of all this I’m left with a good amount of tri polygons, whereas I need a lot of quads as much as possible for simple construction. So I than use the skimp plugin to simplify and than I will go around and manually choose the faces section by section and use a hotkey to use the Artisan make planar tool, another tool apart of the plugin Artisan by dale and his team, amazing plugin. He has the best make planar plugin I’ve tried because it gives even more super useful options, I can make a face planar instantly in a xyz axis, this is super useful because maybe I want to make an overhanging face instantly vertical and just that face, no rotating or messing about, love that option dale! I love how much better your planar tool is and I’m getting the sense more and more that you guys just do some of the best work on plugins out there the more I use your tools and also I see how much more thought goes into your tools as well, thanks for all your great work.

I don’t know how useful this is for you colin, but I think it’s a great workflow for getting planar objects and my other big point on this all is that we need a better mesh wrapper plugin of which gives truly flat faces only…

My need was to make the selected face have coplanar edges, and sWilliams’ extension in post 5 solved that.

Yea, I saw that lol. Did you read my comment?

Just sharing a detailed view of my experience doing the same thing with in depth explanation and the intention that it helps someone who is interested in the why of it all. There are a lot more things to consider when making a face planar in my experience. Making a face planar that forces geometry to not be flat on the top of an object, side or bottom could be a deal breaker in some cases like on my wall design for example.

Glad you’re all sorted