Transforming entities using the API does not merge overlapping edges and vertices?

For some reason, I have always thought that transforming vertices and edges using the API would behave the same as the SketchUp move tool in that overlapping edges and vertices would get automatically merged.

Today I learned this does not appear to be the case after all. If I use entities.transform_entities or entities.transform_by_vectors, it does not result in overlapping geometry getting merged.

Is there a relatively simple way to trigger this merging behavior using the API? The hacky way would seem to be to perform the transformation, then put all the entities affected by the transform in a temporary group, and then explode the group. Is there a better way?

1 Like

Delete one of the coincident edges ?

Call edge.find_faces on one of the edges ?

Call edge.all_connected on one of the faces ?

:thinking: :bulb:

The #intersect_with method is used to intersect an entities, component instance, or group object with a entities object.

def tr_merge( merge = true)
  model = Sketchup.active_model
  entities = model.entities
  edge = model.selection.first

  vec =,, 0)
  tr = Geom::Transformation.translation(vec)

  txt = merge ? 'Tr&Merge' : 'Tr'
  model.start_operation( txt )
    entities.transform_entities(tr, edge)
    if merge
      recurse = false
      transformation1 = IDENTITY
      entities1 = entities 
      transformation2 = IDENTITY
      hidden = false
      entities2 = edge.all_connected

tr_merge( false )

EDIT: Added to the model on which it works.
test_v2019.skp (17.5 KB)

Thanks dezmo - I was experimenting with using that same approach yesterday but it wasn’t working for me. In fact, I tried your exact code in both SU2021 and SU2019 and the merging does not work for me. Very confused…

Hmmm…Could be several reason… Some tipp:
(Sorry if it too obvious…Hard to tell without seeing your code and model.)

  • Maybe your model geometry is different than in my example and the translation vector not “long” enough for you to reach the lying poligon? This should be adapted to your situation:
  • Your tried, but not exactly as my code…(Typo?)
  • Your geometries in a group?
  • You executed only the tr_merge( false ) command, (which will not merge) and not the tr_merge or tr_merge( true )

It is good if those having issues post a test model along with their code. (hint hint)

1 Like

I added the model on which my code works to my post above.

However, I think your first idea to add affected entities to temp group and explode maybe more easy to write “one line of code” and you will get a desired result…
Like, if you select the edge, which you want to translate - similar as my above animation - and run this:

def tr_merge_two
  model = Sketchup.active_model
  entities = model.active_entities
  sel = model.selection
  edge = sel.first
  vec =,, 0)
  tr = Geom::Transformation.translation(vec)
  model.start_operation( "Tr & Merge by add_group&explode")
    entities.transform_entities(tr, edge)

(Honestly, the intersec_ with method is poorly documented and not easy to understand, I’m not even sure I fully get it… :blush: )

The documentation for intersect_with is annoyingly unhelpful :slight_smile: . I think (after some trial and error) that the correct interpretation of the documentation is like in the wrapper method below

  def cut_external(cutter_group, mesh_group, result_group)
    # the inverse transformation from the cutter group into the mesh group
    tr0 = cutter_group.transformation.inverse * mesh_group.transformation
    # The inverse transformation from the cutter group into the result group
    tr1 = cutter_group.transformation.inverse * result_group.transformation
    mesh_group.entities.intersect_with(false, tr0, result_group.entities, tr1, true, cutter_group.entities.to_a)
1 Like

Regarding intersect_with and merging geometry. I have a distinct memory that this does not work when you collapse an edge, that is when you want to remove an edge by moving the start vertex to the end vertex. In this case I found no better way than to simply rebuild the geometry.

On a general note, I think it’s a feature that the geometry is not merging in transform_by_vectors simply because it allows for interactive tools on large meshes. This would not be possible if Sketchup applied its magic to every operation.

Yes, after testing multiple ways, I am convinced that Edge#find_faces is bugged.

The only way I found that is simple is to group the coincident edges and immediately explode the group. We would think that the grouping would remove the faces but it doesn’t.

Using dezmo’s test model …

module MyTest

  extend self

  def any_coincident?
    find_coincident_edges(get_edges()).size > 0

  def coincident?(edge1,edge2)
    (edge1.start.position == edge2.start.position &&
    edge1.end.position == edge2.end.position) ||
    (edge1.start.position == edge2.end.position &&
    edge1.end.position == edge2.start.position)

  def find_coincident_edges(edges)
    edges = edges.dup
    coincidents = []
    while !edges.empty?
      e = edges.shift
      break if edges.empty?
      matches = edges.find_all {|edge| coincident?(e,edge) }
      edges = edges - matches
      coincidents << matches.unshift(e) unless matches.empty?
    return coincidents

  def get_edges
    model = Sketchup.active_model
    entities = model.active_entities
    edges = entities.grep(Sketchup::Edge)

  def tr
    model = Sketchup.active_model
    entities = model.entities
    edge = model.selection.first
    if edge.nil?
      UI.messagebox("Must select an edge!")
    vec =,, 0)
    tr = Geom::Transformation.translation(vec)
    model.start_operation('Move Face')
      entities.transform_entities(tr, edge)

  def merge
    model = Sketchup.active_model
    entities = model.active_entities
    edges = get_edges()
    overlapping = find_coincident_edges(edges)
    unless overlapping.empty?
      selset = model.selection
      overlapping.flatten.each { |e| selset.add(e) }
      UI.messagebox("Ready to merge...")
        overlapping.each do |set|
          #set.each(&:find_faces) # DOES NOT WORK !

  def num_edges
    puts get_edges().size


There’s a gap in the API here unfortunatly.

They way I handle this in my extensions is to not group existing geom, but to create a new temp group with a set of edges that origin in each position where I want vertices to merge. I scale the edge to zero length and explode. Also a kludge, but grouping existing geom can sometimes be slower.

So if an issue would be opened in the tracker, would it be against Sketchup::Edge#find_faces or a new Entities#merge_coincident_geometry method ?

I think maybe it’s be some way to invoke merge. But no need to propose API implementation, descripting the scenario where improvement is needed is enough.

Let me ask it another way (as this is a thread for discussion) …

So, developers, how would you think it most simple or like to be able to cause geometry to be merged and in what situations do you need to do this merging ?

In my case, I know the vertices that I am transforming. So for me, it makes sense to have some sort of new API method where I can invoke the merge and pass an array of vertices (or faces, or edges), as a parameter.

How about Entities#merge_coincident(array_of_entities_to_check_and_merge))

The same could be used if geometry was created using the API and you want to ensure it gets merged with the other geometry. After creating the new faces / edges, you can already get a reference to them which you could then use to pass to the above method. IIRC, creating faces and edges using the API does not always result in proper merging so having a method to call to guarantee clean merging could be useful.

1 Like

I just realized this behavior might be a feature. If there is no merging, you could maybe replicate Move and have the geometry move in each mouse move event, without sticking to things you move it over. An explicit call to merging/sticking together the geometry would be needed for when you make the second click though.

1 Like

Yes, it definitely is a feature but I was surprised that the merging behavior was not triggered when the operation gets committed