I’m using chat to try to help me write a short script that will evaluate the distances of all the vertices in a selected mesh. Vertices that sit at a distance that is lower than a threshold distance set by the user are automatically merged to a single point at the centroid they form.
This comes in really handy when dealing with complex topo meshes that have draped cad lines greatly increasing complexity. Very often in such cases, the large mesh will have a lot of vertices that are extremely close together. Across acres and acres of land lie hundreds of tiny lines too small to be seen - which show up in Sketchup with <~0" measurements.
Here is the condition of the script when I finally gave up on Chat:
Do you see where I’m going wrong here?
require 'sketchup.rb'
module Example
module CollapsePoints
def self.activate_main_window
Sketchup.send_action("activateMainWindow:")
end
def self.get_user_input
prompts = ["Collapse Distance:"]
defaults = ["1.0"]
input = UI.inputbox(prompts, defaults, "Enter Collapse Distance")
return nil unless input
input.first.to_f
end
def self.create_merged_point(entities, points)
# Find the centroid of the points
centroid = points.reduce(ORIGIN) { |sum, point| sum + point.vector_to(ORIGIN) } / points.size
# Merge the points to the centroid
merged_point = entities.add_cpoint(centroid)
points.each do |point|
entities.add_cline(point, centroid) unless point == centroid
end
merged_point
end
def self.collapse_points(distance)
model = Sketchup.active_model
entities = model.active_entities
selection = model.selection
if selection.empty?
UI.messagebox("Please select some points or edges.")
return nil
end
# Start an operation so this can be undone in one step
model.start_operation('Collapse Points', true)
# Get all the unique vertices from the selected edges
vertices = selection.grep(Sketchup::Edge).map(&:vertices).flatten.uniq
# Create a hash to associate each vertex with a group (initially, itself)
vertex_groups = vertices.map { |v| [v, [v]] }.to_h
# Group vertices within the specified distance
vertices.combination(2) do |v1, v2|
if v1.position.distance(v2.position) <= distance
group1 = vertex_groups[v1]
group2 = vertex_groups[v2]
# Merge groups if they are different
if group1 != group2
merged_group = group1 | group2
merged_group.each { |v| vertex_groups[v] = merged_group }
end
end
end
# Collapse the points of each group to their centroid
vertex_groups.values.uniq.each do |group|
points = group.map(&:position)
create_merged_point(entities, points)
end
# Commit the operation
model.commit_operation
nil
end
unless file_loaded?(__FILE__)
UI.menu("Plugins").add_item("Collapse Points") {
activate_main_window
distance = get_user_input
collapse_points(distance) if distance
}
file_loaded(__FILE__)
end
end
end
This is an unknown (undocumented) action string. It does not work on Windows platform.
It could be an AI “invented” solution that really does not exist.
The code does not actually move the vertices. To do this, vertex objects must be moved with either the Entities#transform_by_vectors or Entities#transform_entities methods. The first method using vectors is likely your need.
There is no real point in drawing clines from the old point to the new centroid. Just collect the vectors into an array whose members match the position of each vertex in the vertices array. See Geom::Point3d#vector_to method.
Ie: vectors = vertices.map { |vertex| vertex.position.vector_to(centroid) }
Thanks - that’s a great solution for problems where I’m just collapsing a single tangle. In this case, I have acres of topology with a lot of little tangles - most of which I can’t even see until I’m trying to manipulate the terrain in some way. So I need something that can evaluate the whole mesh and auto-collapse little constellations of points that are super close to one another.
In a way it’s nice to know how dumb AI still is at this point - but I suppose that will change eventually. At this point, for someone like me with basically no experience thinking about these kinds of problems, I’m unable to tell the difference between what look like good ideas and reasonable syntax (but which is entirely hallucinated) from actual workable code.
Dan - thank you so much for your help. It is very generous of you to suffer along with me. In a way I am glad that AI is still bad enough (for the time being) that our need for fellow human beings is still quite vital. When I told Chat what you said, it readily agreed, and then tried its best to fix the code per your advice. However, being Chat, it still failed to create a workable script. Here is what it created, and following is the error message that Ruby generated.
require 'sketchup.rb'
module Example
module CollapsePoints
def self.get_user_input
prompts = ["Collapse Distance:"]
defaults = ["1.0"]
input = UI.inputbox(prompts, defaults, "Enter Collapse Distance")
return nil unless input
input.first.to_f
end
def self.collapse_points(distance)
model = Sketchup.active_model
entities = model.active_entities
selection = model.selection
Sketchup.focus
if selection.empty?
UI.messagebox("Please select some points or edges.")
return nil
end
model.start_operation('Collapse Points', true)
vertices = selection.grep(Sketchup::Edge).map(&:vertices).flatten.uniq
vertex_groups = vertices.map { |v| [v, [v.position]] }.to_h
vertices.combination(2) do |v1, v2|
if v1.position.distance(v2.position) <= distance
group1 = vertex_groups[v1]
group2 = vertex_groups[v2]
if group1 != group2
group1.concat(group2).uniq!
group2.each { |v| vertex_groups[v] = group1 }
end
end
end
vertex_groups.values.uniq.each do |group|
centroid = Geom::Point3d.new(0, 0, 0)
group.each { |point| centroid = centroid + point.vector_to(ORIGIN) }
centroid = centroid / group.size
vectors = group.map { |vertex| vertex.position.vector_to(centroid) }
model.active_entities.transform_by_vectors(group, vectors)
end
model.commit_operation
nil
end
unless file_loaded?(__FILE__)
UI.menu("Plugins").add_item("Collapse Points") {
distance = get_user_input
collapse_points(distance) if distance
}
file_loaded(__FILE__)
end
end
end
error message following:
Error: #<NoMethodError: undefined method `/' for Point3d(5.83727, -14.9898, 0):Geom::Point3d>
<main>:44:in `block in collapse_points'
<main>:41:in `each'
<main>:41:in `collapse_points'
<main>:57:in `block in <module:CollapsePoints>'
This error message appeared after I ran the script on a very simple piece of geometry - a simple 24" x 24" square with a 1" square at its center. With everything selected and the collapse distance set to 1 and also to other values, it returned variations on this basic message. Looks like Chat didn’t define a method properly.
For what it is worth - here is the latest state of the script, after trying repeatedly to get Chat to think through its logic and pay attention to Dan’s feedback.
require 'sketchup.rb'
module Example
module CollapsePoints
def self.get_user_input
prompts = ["Collapse Distance:"]
defaults = ["1.0"]
input = UI.inputbox(prompts, defaults, "Enter Collapse Distance")
return nil unless input
input.first.to_f
end
def self.collapse_points(distance)
model = Sketchup.active_model
entities = model.active_entities
selection = model.selection
Sketchup.focus # Corrected to use Sketchup.focus
if selection.empty?
UI.messagebox("Please select some points or edges.")
return nil
end
model.start_operation('Collapse Points', true)
vertices = selection.grep(Sketchup::Edge).map(&:vertices).flatten.uniq
vertex_groups = vertices.map { |v| [v, [v]] }.to_h
vertices.combination(2) do |v1, v2|
if v1.position.distance(v2.position) <= distance
group1 = vertex_groups[v1]
group2 = vertex_groups[v2]
if group1 != group2
merged_group = group1 | group2
merged_group.each { |v| vertex_groups[v] = merged_group }
end
end
end
vertex_groups.values.uniq.each do |group|
total_vector = Geom::Vector3d.new(0, 0, 0)
group.each { |vertex| total_vector += vertex.position.vector_to(ORIGIN) }
average_vector = Geom::Vector3d.new(
total_vector.x / group.size,
total_vector.y / group.size,
total_vector.z / group.size
)
centroid = ORIGIN.offset(average_vector)
vectors = group.map { |vertex| vertex.position.vector_to(centroid) }
entities.transform_by_vectors(group, vectors)
end
model.commit_operation
nil
end
unless file_loaded?(__FILE__)
UI.menu("Plugins").add_item("Collapse Points") {
distance = get_user_input
collapse_points(distance) if distance
}
file_loaded(__FILE__)
end
end
end
When I run the script, it deletes the points below the distance threshold - instead of merging them. It also offsets the remaining geometry -1" along the green axis.
The little squares were each 1" x 1". Instead of collapsing them, the script basically removed the lines connecting the points. I think the points are still there - invisible to my eye, but they show up as elements when I select the empty space.
Woah! It kind of worked. The script didn’t like the floating 1x1 square, but when I connected it to other points with lines, it collapsed them down to a single point. Unfortunately, it isn’t really a point. It still has faces and lines - of zero dimension. And it moves the whole mesh down an inch on the green axis. Why?