I have a triangulated data of the model, which records the vertex indices of vertices and planes.
I tried to draw this model in Sketchup.
But the code reported an error.

test.txt (119.7 KB)

``````require 'json'

def self.crate_model_by_shape(file)
# get vertices
points = shape_info['points']
# Avoiding errors
points.map! do |pt_arr|
Geom::Point3d.new(*(pt_arr.map { |i| i * 1000 }))
end

# triangles is vertex index of triangular plane
triangles = shape_info['triangles'].map(&:uniq)
# find more than three points
if triangles.find { |triangle| triangle.length != 3 }
raise 'exist more than three points'
end
# delete three point on one line
delete_count = triangles.length
triangles = triangles.delete_if do |triangle|
p1, p2, p3 = triangle.map { |i| points[i] }
on_line?(p1, p2, p3)
end
puts "delete #{delete_count - triangles.length} triangle"
if triangles.find do |triangle|
p1, p2, p3 = triangle.map { |i| points[i] }
on_line?(p1, p2, p3)
end
raise 'exist three points on line'
end
# Determine the edge line based on the index of the triangle vertex and record the vertex composition of the edge line
triangles.each do |triangle|
triangle.each_with_index { |p2_index, i|
p1_index = triangle[i - 1]
edge_hash[p1_index] ||= []
edge_hash[p2_index] ||= []

next if edge_hash[p1_index][p2_index] #  p1  to  p2   exist

edge_hash[p1_index][p2_index] = edge
edge_hash[p2_index][p1_index] = edge
}
end
triangles.each do |triangle|
edges = []
triangle.each_with_index { |p2_index, i|
p1_index = triangle[i - 1]
edges << edge_hash[p1_index][p2_index]
}
end
end

Sketchup.active_model.start_operation 'crate_model_by_shape', true
begin
crate_model_by_shape('test.txt')
Sketchup.active_model.commit_operation
rescue => e
puts e.message,e.backtrace
Sketchup.active_model.abort_operation
end
``````

Error

``````reference to deleted Group
<main>:35:in `entities'
<main>:35:in `block in crate_model_by_shape'
<main>:29:in `each'
<main>:29:in `crate_model_by_shape'
<main>:41:in `<main>'
SketchUp:in `eval'
``````

``````group = Sketchup.active_model.entities.add_group
triangles.each do |triangle|
pts = triangle.map { |i| points[i] }
v1, v2 = pts[1] - pts[0], pts[2] - pts[1]
normal = v1 * v2
face.reverse! unless face.normal.samedirection?(normal)
end
``````

If you create an empty group early on and then do lots of processing Garbage Collection deletes it.

2 Likes

Execute your method without start_operation â€¦ beginâ€¦rescue

``````crate_model_by_shape('test.txt')
``````

The result for me this without error (SU 2021, Windows)

Edit.
Extending TIGâ€™s suggestion you can add c_point to group just after the group creation and then delete it from the group after the face creationsâ€¦

Yes. Itâ€™s ok without start_operation â€¦ beginâ€¦rescue. (SU 2023, Windows).
But Operation is important.

I donâ€™t think should clean empty group before committing.
And I tried to move it before triangles. each, but it still didnâ€™t work

Iâ€™ve tested your code and found a bug related to adding faces from edges. Here are two methods I wrote to demonstrate the issue and provide a workaround:

Bug Demonstration

``````lines = [[[25.0, 25.0, 0.0], [0.0, 25.0, 0.0]], [[0.0, 25.0, 0.0], [0.0, 0.0, 0.0]], [[0.0, 0.0, 0.0], [25.0, 0.0, 0.0]], [[25.0, 0.0, 0.0], [25.0, 25.0, 0.0]]]

def test01(lines)
m = Sketchup.active_model
m.start_operation("Test", true)
edges = lines.map {|l| gr.entities.add_line l }

# Bug: group will be deleted if call add_face from edges again

m.commit_operation
end
``````

Workaround

``````def test02(lines)
m = Sketchup.active_model
m.start_operation("Test", true)
edges = lines.map {|l| gr.entities.add_line l }

points = lines.flatten(1).uniq

m.commit_operation
end
``````

In test01, adding a face from the same edges twice causes the group to be deleted. However, in test02, I added a face from unique points after creating the face from edges, which avoids the deletion issue.

Iâ€™ve tested this on both SketchUp 2023 and SketchUp 2024 on macOS, and the issue persists.

Hope this helps!

1 Like

Please do not use global variables in released extension code, even if you see code examples that do use them. Instead use `@variables` inside your extension submodule.

Or, better, add the exception to the rescue clause and query it for the relevant information. No need for any @variables unless you want to output more detailed info in the rescue clause than the message and location. Basic Ruby practice!

This is not defensive coding. IO operations should be protected inside a `begin` â€¦ `rescue` â€¦ `end` block. Likewise, `JSON` parsing can raise exceptions.

Ex:

``````  begin
rescue JSON::JSONError => error
# Handle any JSONError exception subclass errors, including:
#  * JSON::CircularDatastructure
#  * JSON::GeneratorError
#  * JSON::MissingUnicodeSupport
#  * JSON::NestingError
#  * JSON::ParserError
#  * JSON::UnparserError
rescue Errno::EACCES => error
# Handle disk storage "permission denied" error ...
rescue Errno::ENOENT => error
rescue Errno::ENOTDIR => error
# Handle disk storage "not a directory" error ...
rescue IOError => error
# Handle other general IO exceptions ...
rescue => error
# Handle unforeseen exceptions ...
end
``````

You could write yourself a `file_read()` method that can be reused throughout your extension(s) that safely handles reading from datafiles. Same thing for a method that parses JSON text data.

2 Likes

Thank you.

I have added inspection code to check for triangular surfaces with non three points and those with three points collinear.