To calculate the volume of a solid I used to group the faces of the solid together temporarily, store the volume and explode it immediate after that. In SU2016 the faces don’t change, in SU2017 all my faces are replaced by new faces. Since I store the references of those faces, they now point to deleted entities. Both entityId and persistent_id are new too.
To see the difference, create a cube and run the following in the console:
before = Sketchup.active_model.entities.grep(Sketchup::Face)
puts before
group = Sketchup.active_model.entities.add_group(before)
volume = group.volume / (1.m * 1.m * 1.m)
group.explode
after = Sketchup.active_model.entities.grep(Sketchup::Face)
puts after
puts before == after
Is this the intended behaviour? Is there another way compatible with both versions?
Okay, so how do I get the volume of this solid without creating a temporary group that destroys my faces? Sure, I probably can “find” the faces again, but this feels quite dirty to me.
My brain is slow today. Could you please explain why persistent ID’s make it necessary to delete and recreate entities when you explode a group? I’m struggling with the following thought analogy: I have a box of marbles. If I dump them out of the box, aren’t they still the same marbles?
The preservation of entityId when exploding in previous versions was an implementation detail. The API never guarantied this. When we implemented persistent ID’s this implementation detail changed for various reasons in order to ensure consistent persistent IDs. The regular entityIDs has always been a very transient property.
I want to compute the volume of a manifold not stored in a group. To do so, I create a temporary group, compute the volume, store the result in a variable and then explode it. My geometry hasn’t changed and it’s okay, nervertheless all the entities (faces, …) change their reference so all my variable that previously pointed to them now point to some “Deleted Entity” and I get an error if I work on them : <TypeError: reference to deleted Face> .
My questions are :
can create a groupe with the copy of my entities to compute the volume and thus not deleting the original entities ?
is there an other way to compute the volume without grouping ?
# get_volume(geometry)
# Create a temporary group, get the volume and undo.
#
# @param geometry [Array<Drawingelement>] Array of collected geometry references.
# @return [Float] the computed volume if successful, 0.0 if not.
def get_volume(geometry)
volume = 0.0
model = Sketchup::active_model
model.start_operation('Temp Group',true)
#
ents = model.active_entities
grp = ents.add_group(geometry)
volume = grp.volume
#
model.commit_operation
rescue => e
puts e.inspect
else
Sketchup::undo
ensure
puts "get_volume() returned: #{volume} in³"
return volume
end
However, if some other extension creates a undo operation and attaches it to this one (from behind or next) their’s will also be undone. (IMO, that is their fault.)
EDIT: The example assumes that the geometry objects are in the current active entities collection.
If they are not, then you’ll need to modify the code to get the parent entities collection of the geometry:
# perhaps just before the undo operation ...
sire = geometry.first.parent
ents = sire.entities
return 0.0 unless geometry.all? {|e| e.parent == sire }
… and remove the line inside the operation that assigns ents.
Let’s us assume the geometry in question is temporarily placed inside a ‘group’
The explode returns an array of objects.
ex = group.explode
This includes all resulting edges and faces, and more besides - like curves…
You can filter it - see below
So to make your code suitable for all supported versions you could consider adding a ‘marker’ attribute to say the faces [or edges if desired].
This will be passed to the replacement entities after the explode, so before grouping we ‘mark’ the faces…
hashed_faces = {}
ex.grep(Sketchup::Face).each{|face|
next unless i = face.get_attribute('adrigon', 'marker', nil)
hashed_faces[i] = face
}
sorted_ex_faces = []
hashed_faces.keys.sort.each{|k|
sorted_ex_faces << hashed_faces[k]
}
You now have an array of those newly exploded faces, ordered as they were in the original geometry [and ‘faces’ array]…
If desirable you can also finally reset your ‘marker’ attribute associated with these replacement faces to ‘nil’…
This will work across all SketchUp versions.
BUT if you only want >= v2017 compatibility, then just use the newer persistent ids and trap for `Sketchup.version` to suit.......
Mine does not modify API class Geom::Point3d, but converts to an Array which already has a #dot method.
Mine also uses #grep which is very fast when comparing class identity, … instead of calling #is_a? on each and every object in the geometry collection on the Ruby-side, during each iteration.
# calculate_volume(geometry)
# Calculate volume on a set of faces without a temporary group.
#
# Based on an old SCF topic thread post by AdamB:
# http://sketchucation.com/forums/viewtopic.php?p=14598#p14598
#
# @param geometry [Array<Drawingelement>] Array of collected geometry references.
# @return [Float] the computed volume (in inches) if successful, 0.0 if not.
def calculate_volume(geometry)
geometry.grep(Sketchup::Face).map {|f|
(2 * f.area * (f.vertices[0].position.to_a.dot(f.normal))) / 6
}.reduce(:+)
rescue => e
puts e.inspect
return 0.0
end
That’s because 1m^3 = 61023.744in^3. If you treat that as inches and apply format_length, it works out to 1550.0m. The format_volume_in_model_units method given is wrong!
you are right…and surly it isn’t a beautiful advice… copy the raw geometry to a temporarily group - measure the volume - then delete the temporarily group again.