Turning a selection into a face?
-
the code snippet below asks the user to make a selection
model=Sketchup.active_model ents=model.active_entities sel=model.selection if sel.empty? UI.messagebox("No selection !") return nil end
what code do I require to turn that selection into a face?
_____________ == Sketchup::Face
-
There are many ways, e.g.
sel.grep(Sketchup::Edge).each{|e|e.find_faces}
adds all possible faces edges in the selection... -
@tig said:
There are many ways, e.g.
sel.grep(Sketchup::Edge).each{|e|e.find_faces}
adds all possible faces edges in the selection...If I use the following statement:
$newface1=sel.grep(Sketchup::Edge).each{|e|e.find_faces}
the ruby console respond is:
%(#FF0000)[$newface1
[]]
I was expecting something like:
#Sketchup::Face:0xb3cb350 -
That method doesn't return faces, it just makes them !
To find them try...
` edges=sel.grep(Sketchup::Edge)
ofaces=[]
edges.each{|e|ofaces << e.faces}
ofaces.flatten!
ofaces.uniq!edges.each{|e|e.find_faces}
nfaces=[]
edges.each{|e|nfaces << e.faces}
nfaces.flatten!
nfaces.uniq!The array 'nfaces' is all of the faces that are now attached to the edges - old and new. To find just the new faces try...
faces=nfaces-ofaces`
Where 'faces' is the array of those... -
I did not know that this is a 2 stage process, 1: find & 2: return
$newface1=sel.grep(Sketchup;;Face) #finds face only! $newface1.pushpull $inset
So How do I implement a pushpull of $newface1?
-
First off...
Don't use $ variables !
If the vars are spread across different methods in the same class/module use @.
Otherwise why use them at all...
Try
newface1.pushpull(inset)
or
@newface1.pushpull(@inset)
Depending on the +/-ve value of the face it dictates the extrusion relative to the face.normal.
Note that the face.normal can be determined prior to the pushpull and then face.reverse! if it's the 'wrong orientation', for example you want the face to be 'down' so a +ve inset moves the pushpull down too then test face.normal.z<0, and if not you can use face.reverse! before the pushpull is done... -
@tomot said:
I did not know that this is a 2 stage process, 1: find & 2: return
$newface1=sel.grep(Sketchup;;Face) #finds face only! > $newface1.pushpull $inset
So How do I implement a pushpull of $newface1?
sel.grep(Sketchup::Face)
This returns an array of all faces in the colletion (in this case selection)<span class="syntaxdefault"><br />faces </span><span class="syntaxkeyword">= </span><span class="syntaxdefault">sel</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">grep</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">Sketchup</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Face</span><span class="syntaxkeyword">) }<br /></span><span class="syntaxcomment"># Push/pull all faces<br /></span><span class="syntaxdefault">faces</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">each </span><span class="syntaxkeyword">{ |</span><span class="syntaxdefault">face</span><span class="syntaxkeyword">| </span><span class="syntaxdefault">face</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">pushpull</span><span class="syntaxkeyword">( </span><span class="syntaxdefault">distance </span><span class="syntaxkeyword">) }<br /> </span><span class="syntaxdefault"></span>
If you want only one entity, use find():
<span class="syntaxdefault"><br />face </span><span class="syntaxkeyword">= </span><span class="syntaxdefault">sel</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">find </span><span class="syntaxkeyword">{ |</span><span class="syntaxdefault">e</span><span class="syntaxkeyword">| </span><span class="syntaxdefault">e</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">is_a</span><span class="syntaxkeyword">?(</span><span class="syntaxdefault">Sketchup</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Face</span><span class="syntaxkeyword">) }<br /></span><span class="syntaxdefault">face</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">pushpull</span><span class="syntaxkeyword">( </span><span class="syntaxdefault">distance </span><span class="syntaxkeyword">)<br /> </span><span class="syntaxdefault"></span>
-
@tig said:
First off...
Don't use $ variables !
snipI was using $ variables only to check what my face code was actually being selecting in the Ruby Console. I still trying to resolved this issue.
-
@thomthom said:
sel.grep(Sketchup::Face)
This returns an array of all faces in the colletion (in this case selection)<span class="syntaxdefault"><br />faces </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> sel</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">grep</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">Sketchup</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Face</span><span class="syntaxkeyword">)</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">}<br /></span><span class="syntaxcomment"># Push/pull all faces<br /></span><span class="syntaxdefault">faces</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">each </span><span class="syntaxkeyword">{</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">|</span><span class="syntaxdefault">face</span><span class="syntaxkeyword">|</span><span class="syntaxdefault"> face</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">pushpull</span><span class="syntaxkeyword">(</span><span class="syntaxdefault"> distance </span><span class="syntaxkeyword">)</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault"></span>
If you want only one entity, use find():
<span class="syntaxdefault"><br />face </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> sel</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">find </span><span class="syntaxkeyword">{</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">|</span><span class="syntaxdefault">e</span><span class="syntaxkeyword">|</span><span class="syntaxdefault"> e</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">is_a</span><span class="syntaxkeyword">?(</span><span class="syntaxdefault">Sketchup</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Face</span><span class="syntaxkeyword">)</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">face</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">pushpull</span><span class="syntaxkeyword">(</span><span class="syntaxdefault"> distance </span><span class="syntaxkeyword">)<br /></span><span class="syntaxdefault"></span>
That's precisely my problem in a larger nutshell , I'm trying to use RickW's offset.rb which draws a 2nd offset on the initial "sel" selected face. Since there now 2 faces I'm having difficulty selecting the offset face for pushpull that the offset.rb created, and NOT the initial "sel" selected face for pushpull. (I can post a pic if my description falls short)
-
@tomot said:
I'm trying to use RickW's offset.rb which draws a 2nd offset on the initial "sel" selected face.
Please do not use that script AS IS. We need to put it on the Quarantine List.
It overrides the API
Array#offset
method, and adds methods to both API and Ruby Core base classes.You can use ideas from the methods that do not change base classes (by re-writing them and putting them inside your own namespace.)
So... anyway Rick's offset method should have returned a reference to the added face, because it uses
Entities#add_face()
To use that method inside youir OWN namespace, you need to pass a reference to the original face into the method, and replace all
self
references within the method to the reference name of the passed face argument.Example 1 (The internals have not been changed much, but an undo operation has been added):
module Tomot module SomePlugin class << self def offset_a_face( face, dist ) unless face.is_a?(Sketchup;;Face) fail(TypeError,"Sketchup;;Face instance expected for 1st argument",caller) end unless dist.is_a?(Numeric) fail(TypeError,"Numeric subclass expected for 2nd argument",caller) else if dist.zero? unless $VERBOSE.nil? puts("Tomot;;SomePlugin;WARNING; offset_a_face(); Offset distance was zero.") end return nil end end new_face = nil # this method's return value pi = Math;;PI verts = face.outer_loop.vertices pts = [] # CREATE ARRAY pts OF OFFSET POINTS FROM FACE 0.upto(verts.length-1) do |a| vec1 = (verts[a].position-verts[a-(verts.length-1)].position).normalize vec2 = (verts[a].position-verts[a-1].position).normalize vec3 = (vec1+vec2).normalize if vec3.valid? ang = vec1.angle_between(vec2)/2 ang = pi/2 if vec1.parallel?(vec2) vec3.length = dist/Math;;sin(ang) t = Geom;;Transformation.new(vec3) if pts.length > 0 vec4 = pts.last.vector_to(verts[a].position.transform(t)) if vec4.valid? unless (vec2.parallel?(vec4)) t = Geom;;Transformation.new(vec3.reverse) end end end pts.push(verts[a].position.transform(t)) end end # CHECK FOR DUPLICATE POINTS IN pts ARRAY # duplicates = [] pts.each_index do |a| pts.each_index do |b| next if b==a duplicates<<b if pts[a]===pts[b] end break if a==pts.length-1 end duplicates.reverse.each{|a| pts.delete(pts[a])} # CREATE FACE FROM POINTS IN pts ARRAY # if pts.length > 2 begin # if Sketchup.version.to_i > 6 face.model.start_operation('Offset Face',true) else face.model.start_operation('Offset Face') end # new_face = face.parent.entities.add_face(pts) # face.model.commit_operation() # rescue face.model.abort_operation() raise() # to outer rescue clause end # begin end # if rescue TypeError raise() # just re-raise the Exception rescue => e # any other Exceptions; unless $VERBOSE.nil? puts("Tomot;;SomePlugin;WARNING; offset_a_face(); #{face.inspect} did not offset; #{pts}") puts("Error; #<#{e.class.name}; #{e.message}>") puts(e.backtrace) if $VERBOSE end raise() # re-raise to any debugger in use else # when no errors; return new_face end # offset_a_face() end # proxy class end # module SomePlugin end # module Tomot
-
Example 2 : Here is a shorter edition that uses a vector or an array as the 2nd argument, and leverages the API's built-in
Geom::Point3d#offset!
method.You can create a vector to pass as the 2nd argument, or call the method with an array literal, to say, offset the face by 10 units in the positive Z direction:
offset_a_face( @face, [0,0,10.0] )
module Tomot module SomePlugin class << self def offset_a_face( face, vect ) unless face.is_a?(Sketchup;;Face) fail(TypeError,"Sketchup;;Face instance expected for 1st argument",caller) end unless vect.is_a?(Geom;;Vector3d) || vect.is_a?(Array) fail(TypeError,"Geom;;Vector3d or Array expected for 2nd argument",caller) else vect = Geom;;Vector3d.new(*vect) if vect.is_a?(Array) if vect.length.zero? unless $VERBOSE.nil? puts("Tomot;;SomePlugin;WARNING; offset_a_face(); Offset vector length was zero.") end return nil end end new_face = nil # this method's return value verts = face.vertices # CREATE ARRAY pts OF OFFSET POINTS FROM FACE pts = verts.map {|v| v.position.offset!(vect) } # CREATE FACE FROM POINTS IN pts ARRAY # if pts.length > 2 begin # if Sketchup.version.to_i > 6 face.model.start_operation('Offset Face',true) else face.model.start_operation('Offset Face') end # new_face = face.parent.entities.add_face(pts) # face.model.commit_operation() # rescue face.model.abort_operation() raise() # to outer rescue clause end # begin end # if rescue TypeError raise() # just re-raise the Exception rescue => e # any other Exceptions; unless $VERBOSE.nil? puts("Tomot;;SomePlugin;WARNING; offset_a_face(); #{face.inspect} did not offset; #{pts}") puts("Error; #<#{e.class.name}; #{e.message}>") puts(e.backtrace) if $VERBOSE end raise() # re-raise to any debugger in use else # when no errors; return new_face end # offset_a_face() end # proxy class end # module SomePlugin end # module Tomot
-
Example 3 : a one-liner (with no error trapping, etc.)
Assume
@vect
is either anArray
or aGeom::Vector3d
instance:@nface = @oface.parent.entities.add_face(@oface.vertices.map {|v| v.position.offset!(@vect) })
-
@tomot said:
That's precisely my problem in a larger nutshell , I'm trying to use RickW's offset.rb which draws a 2nd offset on the initial "sel" selected face. Since there now 2 faces I'm having difficulty selecting the offset face for pushpull that the offset.rb created, and NOT the initial "sel" selected face for pushpull. (I can post a pic if my description falls short)
<span class="syntaxdefault"><br />model </span><span class="syntaxkeyword">= </span><span class="syntaxdefault">Sketchup</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">active_model<br />entities </span><span class="syntaxkeyword">= </span><span class="syntaxdefault">model</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">active_entities<br />sel </span><span class="syntaxkeyword">= </span><span class="syntaxdefault">model</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">selection<br /><br /></span><span class="syntaxcomment"># Grab a list of existing faces<br /></span><span class="syntaxdefault">original_faces </span><span class="syntaxkeyword">= </span><span class="syntaxdefault">entities</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">grep</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">Sketchup</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Face</span><span class="syntaxkeyword">)<br /><br /></span><span class="syntaxcomment"># First offset face<br /></span><span class="syntaxdefault">face </span><span class="syntaxkeyword">= </span><span class="syntaxdefault">sel</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">find </span><span class="syntaxkeyword">{ |</span><span class="syntaxdefault">e</span><span class="syntaxkeyword">| </span><span class="syntaxdefault">e</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">is_a</span><span class="syntaxkeyword">?(</span><span class="syntaxdefault">Sketchup</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Face</span><span class="syntaxkeyword">) }<br /></span><span class="syntaxdefault">offset</span><span class="syntaxkeyword">( </span><span class="syntaxdefault">face</span><span class="syntaxkeyword">, </span><span class="syntaxdefault">20.mm </span><span class="syntaxkeyword">) </span><span class="syntaxcomment"># I don't know what the real method is there.<br /><br /># Then Push-pull inner face<br /></span><span class="syntaxdefault">faces </span><span class="syntaxkeyword">= </span><span class="syntaxdefault">entities</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">grep</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">Sketchup</span><span class="syntaxkeyword">;;</span><span class="syntaxdefault">Face</span><span class="syntaxkeyword">) - </span><span class="syntaxdefault">original_faces<br />inner_face_from_offset </span><span class="syntaxkeyword">= </span><span class="syntaxdefault">faces</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">first </span><span class="syntaxcomment"># *should* be just one face<br /></span><span class="syntaxdefault">inner_face_from_offset</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">pushpull</span><span class="syntaxkeyword">( </span><span class="syntaxdefault">500.mm </span><span class="syntaxkeyword">)<br /> </span><span class="syntaxdefault"></span>
Untested
-
@thomthom said:
I don't know what the real method is there.
It was my Example 1 (above) but Rick's edition was an instance method added to the
Sketchup::Face
class. (I re-wrote it to pass the face instance into the method.)As shown in my one-liner (Example 3) a method is not really even needed.
@oface
is the old face in the selection.
The new face will be@nface
, and then just do the push-pull on it. -
@dan rathbun said:
Example 3 : a one-liner (with no error trapping, etc.)
Assume
@vect
is either anArray
or aGeom::Vector3d
instance:@nface = @oface.parent.entities.add_face(@oface.vertices.map {|v| v.position.offset!(@vect) })
That just offsets in an direction - we're talking about offsetting in terms of what the Offset tool does.
-
Tig, thomthom and Dan, thanks for your input it will take me a few days to digest your input, and try putting your input into use.
but thomthom is right, I'm looking for the statement that allows me to offset the face the offset.rb creates.I was hoping a simple Boolean expression in the API would have served a very useful purpose, in this particular instance.
-
@tomot said:
... but thomthom is right, I'm looking for the statement that allows me to offset the face the offset.rb creates.
OK... then first of all the Example 1 re-write of Rick's method mimics the native offset tool, and returns the newly created offset face.
(Just ignore Examples 2 & 3, as I did not understand what was needed and may have misunderstood what Rick's code was doing.)
If you wish to AGAIN apply an offset, pass the newly created face and a new offset distance (or whatever distance,) into the method a second time.
If you wish to move it or something else ... do whatever by using the returned reference as needed.
Here is test script wrapped as
Tomot::Offset
, that adds right-click mouse "Offset Face" commands to the popup menu, but differs in result from the native tool by setting the selection to the new face. (The native tool does not change the selection set.) It also uses an inputbox to get the distance, ... but in practice you would bypass that and your code would supply the needed distance value.module Tomot module Offset class << self def offset_a_face( face, dist ) unless face.is_a?(Sketchup;;Face) fail(TypeError,"Sketchup;;Face instance expected for 1st argument",caller) end unless dist.is_a?(Numeric) fail(TypeError,"Numeric subclass expected for 2nd argument",caller) else if dist.zero? unless $VERBOSE.nil? puts("Tomot;;Offset;WARNING; offset_a_face(); Offset distance was zero.") end return nil end end new_face = nil # this method's return value pi = Math;;PI verts = face.outer_loop.vertices pts = [] # CREATE ARRAY pts OF OFFSET POINTS FROM FACE 0.upto(verts.length-1) do |a| vec1 = (verts[a].position-verts[a-(verts.length-1)].position).normalize vec2 = (verts[a].position-verts[a-1].position).normalize vec3 = (vec1+vec2).normalize if vec3.valid? ang = vec1.angle_between(vec2)/2 ang = pi/2 if vec1.parallel?(vec2) vec3.length = dist/Math;;sin(ang) t = Geom;;Transformation.new(vec3) if pts.length > 0 vec4 = pts.last.vector_to(verts[a].position.transform(t)) if vec4.valid? unless (vec2.parallel?(vec4)) t = Geom;;Transformation.new(vec3.reverse) end end end pts.push(verts[a].position.transform(t)) end end # CHECK FOR DUPLICATE POINTS IN pts ARRAY # duplicates = [] pts.each_index do |a| pts.each_index do |b| next if b==a duplicates<<b if pts[a]===pts[b] end break if a==pts.length-1 end duplicates.reverse.each{|a| pts.delete(pts[a])} # CREATE FACE FROM POINTS IN pts ARRAY # if pts.length > 2 begin # if Sketchup.version.to_i > 6 face.model.start_operation('Offset Face',true) else face.model.start_operation('Offset Face') end # new_face = face.parent.entities.add_face(pts) # face.model.commit_operation() # rescue face.model.abort_operation() raise() # to outer rescue clause end # begin end # if rescue TypeError raise() # just re-raise the Exception rescue => e # any other Exceptions; unless $VERBOSE.nil? puts("Tomot;;Offset;WARNING; offset_a_face(); #{face.inspect} did not offset; #{pts}") puts("Error; #<#{e.class.name}; #{e.message}>") puts(e.backtrace) if $VERBOSE end raise() # re-raise to any debugger in use else # when no errors; return new_face end # offset_a_face() #{ get_dist(face) # # Prompts for a distance, then calls offset_a_face() # If a new offset face is created, it will be set as # a single object of the current model's selection. # def get_dist(face) result = UI.inputbox(['Offset distance ; '],[0],'Offset a Face') if result ret = offset_a_face(face,result[0]) if ret sel = Sketchup.active_model.selection sel.clear sel.add(ret) else return nil end else return nil end end #} end # proxy class unless file_loaded?(File.basename(__FILE__)) UI.add_context_menu_handler {|popup| unless Sketchup.active_model.selection.empty? sel = Sketchup.active_model.selection if sel.single_object? && sel[0].is_a?(Sketchup;;Face) face = sel[0] popup.add_item('Offset Face',10) { get_dist(face) } else face = sel.find {|o| o.is_a?(Sketchup;;Face) } if face # (nil if no face was selected) popup.add_item('Offset First Face',11) { get_dist(face) } end end end } file_loaded(File.basename(__FILE__)) end end # module Offset end # module Tomot
Question to the room at large: Regarding line 137, which is faster for large selection sets ?
face = sel.find {|o| o.is_a?(Sketchup::Face) }
OR
face = sel.grep(Sketchup::Face)[0]
Keep in mind both need to be fololowed by a
nil
test conditional, viz:
if face
-
Dan thanks for your code, however regarding your last piece of code.
I'm unclear as to how to use it.If I save your code as as offset.rb and replace it with the original offset.rb then load it in the console, I get an error in the console when I right click on selected face.
Is this the way you intended it to be used? Please explain further!
and just to make sure, please see the attachment regarding the face in question.
-
YES, make a selection, then right-click the selection (or a single face,) and an inputbox should pop up to enter a distance.
IF you get an error, it would help if you paste the error message into a post.
-
@dan rathbun said:
IF you get an error, it would help if you paste the error message into a post.
there is also no "offset" reference in the context menu
Advertisement