How to determine "From Point" and "Constrained on Line" in Ruby?


#1

I would like to know if possible to determine the “From Point” position (Point3d), and “Constrained on Line” (Point3d, Vector3d) in Ruby? I’m almost get a workaround, but now I’m stucked…
I’m talkig about this:
from_point

constrained_on_line

So far I discovered:
Looking at the degrees of freedom of inputpoint (@ip0.degrees_of_freedom == 1) can tell me the cursor is moving a direction of edge (or axis), but nothing about points or vector…(dof can be 0, if inference locked)
The .inference_locked? method tell me that also, the cursor is moving a direction of edge (or axis), but nothing about points or vector… But at least I can switch it on or off, and can do some code to cacht the line, somthing like this:

def onMouseMove(flags, x, y, view)
#...
if( view.inference_locked? || @ip0.degrees_of_freedom == 1) )
  unless( @ippos_o == @ip0.position )
    vec_Constrained_on_Line = @ippos_o.vector_to( @ip0.position )
    line_Constrained_on_Line1 = [ @ip0.position, vec_Constrained_on_Line ]
    line_Constrained_on_Line2 = [ @ip0.position, vec_Constrained_on_Line.reverse ]
    point_From_Point_1 = Sketchup.active_model.raytest( line_Constrained_on_Line1 )[0]
    point_From_Point_2 = Sketchup.active_model.raytest( line_Constrained_on_Line2 )[0]
    @ippos_o = @ip0.position
  end
end
#...
end

But which “point_From_Point_x” is the right one?

Some other things in my mind…
The officially “unsupported” method:
Sketchup.send_action(numeric)
Have some “promising” numbers like:
20027 : From Point
20436 : Constrained on %1
20439 : from Point
But I don’t know if (and how) I can use it? (They are returns “true” only…)

Do you have any idea?
Thanks.


#2

Use …Sketchup::InputPoint#tooltip()

if @ip0.tooltip == "From Point"
  # do something
end

… or …

if @ip0.degrees_of_freedom == 1
  #
  case @ip0.tooltip
  when "From Point"
    # do this
  when "Constrained on Line"
    # do that
  else
    # do other
  end
  #
end

Keep in mind that the tooltips may be localized for each language that SketchUp supports.


Those are not send actions. They are simply ID numbers into SketchUp’s text resource lookup table.


#3

Thanks Dan for the (tool)tip. :slight_smile: I have been get that in my mind too…
@eneroth3 was mentioned about it somewhere here in the forum ( I hope she can have some idea too.)
… but this one does not really help me…

I would like to get the position (Point3d) of the point what is refereed in the “From Point”. When Sketchup inferring, telling me: “Hi man, I’m getting inference From Point”. Then I’m asking: “What point are you talking about? Can you please give me the coordinates?”
When I’m locking the inference, the tooltip is turning from “From Point” to “Constrained on Line” (mostly ). For sure, Sketchup knows already about this point and line, but not giving enough info.
As you see my code above I’m already able to catch the line and two possible points. But I don’t know, how to determine which of the two is the “From Point” ?
Do you think if there is a method to get this (Point3d), directly, or these are deeply hidden internally? Or any other idea to get the right coordinates?


I was afraid about it… :frowning:


#4

I don’t think there is a way to know from what point an inference was made. I don’t think any of the internal tools rely on this information either. What are you planning to use it for?

Also I’d strongly advise against comparing the tooltip string. This is meant for the UI layer, not as an internal identifier, and will be different in localized SU versions. While it is unlikely it is also possible that the text changes even in the English version, e.g. to “From Endpoint”, which would also break this plugin.


#5

Woow, that was really fast reaction… :slight_smile: Thanks Julia!

Yes, I agree… I have been read your opinion already about it somewhere here in the forum.


I’m “playing” by making a “ProLine” tool…
https://sketchucation.com/forums/viewtopic.php?f=323&t=69965&sid=019b7da344cd2b281e4be4d2eddc96ad

… and I would like to implement some special inferring into it. In this case I will force the length snapping to the “Constrained on Line”, so you do not have to use a circle for intersecting to draw a defined length line to another line. E.g. hypotenuse of right triangle. (I hope you will understand what I mean…). The refereed link above does not containing this feature yet… I just working on it, and almost get it to properly work, even without knowing, the “From Point” position. :slight_smile:
All in all, even if I don’t really need this now, just for fun I “want” to know how can I determine this point… :slight_smile:


#6

Ok, so you kind of want to snap to the inference line? I don’t think that is possible. However, you could get around it by letting the tool copy the input point as reference when you have hovered it for half a second or so (using a timer). Then you could try to find these inference liens yourself and draw them to the view.


#7

I hope I can do it somehow… Currently I do not use a timer. What I’m doing now: I’m intentionally, manually locking the inference, so I know the input point is only moving on this line. While the ip is moving, I’m using his previous position (stored temporally) and current position of ip to get vector. So I have a “Constrained on Line” .
Then I can project my line start point to “Constrained on Line”, and using a simple Pythagorean theorem to calculable the desired point distance from projected point, then I use the offset along “Constrained on Line” to get it to right position of my point to draw.
This one is currently more or less working, but not that smooth as I can satisfied with. Still playing with it to get it better. It may be better if I already have a fix point to start with… I’ll let you know when I’m ready, if you are interested…


#8

Sounds like a workaround. When doing this I’d recommend to clearly document in the code that you are doing this to get a reference to the inference line, even though it isn’t exposed in the API. This is typically something that can confuse the socks off of anyone trying to work on that code in the future.

Btw, perhaps it helps to create “hidden” InputPoints that are never drawn to the view, just for the sake of locking inference and getting the line.


#9

Well noted! I hope I can use this idea somehow…
(I promise, I’ll heavily commenting that part of my code :slight_smile: )


#10

Couldn’t resist trying this :stuck_out_tongue: .

The super ugly 3 px line is just for testing the code.

Here’s my code.

class Tool
  def initialize
    @ip = Sketchup::InputPoint.new
  end

  def onMouseMove(flags, x, y, view)
    @ip.pick(view, x, y)
    @line = inference_line(@ip, view)
    view.invalidate

    p @line
  end

  def draw(view)
    draw_line(@line, view)
    @ip.draw(view)
    view.tooltip = @ip.tooltip
  end

  private

  def inference_line(ip, view)
    return nil unless ip.degrees_of_freedom == 1

    view.lock_inference(ip)
    ip2 = ip.clone
    # Pick from arbitrary point. Screen coordinates should not matter as we only
    # want any point on the inference line.
    ip2.pick(view, 0, 0)

    # TODO: Reset inference lock to what it was before, if it was locked,
    # rather than just removing it.
    view.lock_inference

    [ip.position, (ip2.position - ip.position).normalize]
  end

  def draw_line(line, view)
    return unless line
    
    view.line_width = 3
    view.draw(GL_LINES, line[0], line[0].offset(line[1], 1.m))

    view.line_width = 1
  end
end

Sketchup.active_model.select_tool(Tool.new)

#11

Btw, if relying on tooltip string, make sure to extract it as a constant rather than hard code it, and document what the constant is for. This helps for the future if you ever want it to support localized SU versions. Then the constant can be defined to match the running SU language, e.g. by locking up the values from a hash indexed by the lang codes. It is important to keep this string separate from any stings used in the UI of your extension, as the extension may run in another language than SU itself.

(I’m currently in the process of cleaning up a plugin where the same constants are used both for printing to the UI, and as internal identifiers. It’s a pain!)


#12

Wow! My feeling now is same ! :slight_smile: (But unfortunately I have to wait when my working time expiring… what a long 4 hours!!)
Your code for sure more elegant than mine, but in principle very similar. One time I may can do similar. (BTW. it is very interesting to see and learn a small tricks e.g. creating a vector you just point-point2 instead of. point.vector_to(point2)…)
(BTW2: I could not understand what “private” means in your code… I have to learn more heavily)
Your help is Very much appreciated!
Thanks!

Okay, I just staring to get familiar with this and I’m trying to use it my code (there are some of it already… hope I’m using correctly! :slight_smile: )


#13

Private is to prevent code outside the class from calling it. In larger projects it is very important to make the distinction between internal methods, and methods other classes are allowed to use, to avoid having everything interconnected with everything else.


#14

Filed API request … (which references this thread.)


#15

(Finally I’ve got some time to play with it a little…
The forced length snapping is working nicely now without extra input points. Previously I constantly checked and changed the line vector, then this caused some glitches (shaking cursor), but now when I get it, store it and working okay. I will update my plugin in a coming days (or weeks :smile: -) )

But now you have aroused my interest about the input points and I got some idea…, but again it is half way working.

class Tool
  def initialize
    @ip = Sketchup::InputPoint.new
  end

  def onMouseMove(flags, x, y, view)
    @ip.pick(view, x, y)
    @line = inference_line(@ip, view) 
    view.invalidate

    p @line
  end

  def draw(view)
    draw_line(@line, view)
    @ip.draw(view)
    view.tooltip = @ip.tooltip
  end

  private

  def inference_line(ip, view)
    model = Sketchup.active_model
    if ip.face
      #Calculate the average of vertices's coordinates
      vn =  ip.face.vertices.length
      all = ORIGIN
      ip.face.vertices.each{|v| all += ORIGIN.vector_to(v.position)}
      #this will be the center point of face
      av = Geom::Point3d.new( all.x/vn, all.y/vn, all.z/vn )  
      # get a point on the normal vector of face
      av2 = av.offset(ip.face.normal)
      # Create "hidden" ip's
      iav=Sketchup::InputPoint.new(av)
      iav2=Sketchup::InputPoint.new(av2)

      # and lock to it
      view.lock_inference(iav2,iav) 
      # cline = model.active_entities.add_cline(av, (av - av2)) 

      [av, (av - av2).normalize]
    end
  end

  def draw_line(line, view)
    return unless line
    
    view.line_width = 3
    view.draw(GL_LINES, line[0], line[0].offset(line[1], 1.m))

    view.line_width = 1
  end
end

Sketchup.active_model.select_tool(Tool.new)

If I’ve got a face normal parallel to x,y or z the code is working nicely to automatically lock the inference. But as soon as the face is turned the lock will fail…


I can put the temporally cline there, and than manually lock, but would be more elegant without it.
Any Idea?



BTW. Thanks for @DanRathbun for request!
… and if I remember my readings well: calculate the face center idea, come from his “pen” too… :wink: (Thanks again!)


#16

You can get the plane directly from the input point by querying the face it’s on. The hidden ip hack was just for getting a line that isn’t directly represented by an entity.

Btw, when writing code, try to use descriptive names. av, all, av2 etc makes the code very hard to read, which makes it more likely to contain bugs and makes it harder to fix them.


#17

That’s okay, I do not need the plane. I think you misunderstood… Sorry if my explanation was not clear . My problem is to lock the inference not only on X_ Y_ Z_AXIS, but any direction. Is that possible?
In the code above "view.lock_inference(iav2,iav) " locks the inference when they are on line of axis, but not when they are any other direction. Why?


Edit:

I assume you can determine what kind of interface lock was it before. Are you? I’m specially interested how can you “remember”, and how to go back to parallel or perpendicular lock?

Sorry I was too lazy here, and when I have idea quickly make it to code, so any name is okay for temporally… normally I’m trying to use “average_of_points” ; ip_average… and so on.
Thanks for your help!


#18

But likely old (Ruby 1.8) code.

Now that we are using Ruby 2+ …

      #Calculate the average of vertices's coordinates
      vn =  ip.face.vertices.length
      all = ORIGIN
      ip.face.vertices.each{|v| all += ORIGIN.vector_to(v.position)}
      #this will be the center point of face
      av = Geom::Point3d.new( all.x/vn, all.y/vn, all.z/vn )

… might be better written as …

      # Find the centroid of the face
      pts = ip.face.vertices.map(&:position)
      centroid = Geom::linear_combination(
        pts.size**-1, ORIGIN, 1-pts.size**-1, pts.reduce(:+) 
      )

#19

Okay. I think I’ve got something…
The code below may not that clear as professional’s made, but at least it is doing what I wanted… and again sorry about the my ugly variable names, and missing comments… :slight_smile:
Any suggestion, mistake correction or improvement recommendation are welcome!
The complete operations are wrapped into one start_operation / commit_operation, just for now, but real implementation most likely will be different…

Some explanation shortly: It is more or less clear now that SU native inference will catch the axes even there are no edge drawn there, but could not catch the line determined only by two Point3d or InputPoint. Therefor if I want to catch inference line which is not on axes, I have to “turn” one of the axis “under his arm”. From SU version 2016 it is possible, but in older versions I must use an other workaround: put ConstructionLine and give some extra job to user. :wink:

class Tool_lock_to_face_normal
SU_VER_OK = ( Sketchup.version.to_i >= 16 )? true : false unless defined? SU_VER_OK
  def initialize
    @ip = Sketchup::InputPoint.new
    # Remember for original axes !! Note: SU version !! 
    if SU_VER_OK
      @orig_origin, @orig_xaxis, @orig_yaxis, @orig_zaxis = Sketchup.active_model.axes.to_a
    else
      puts "I'm  too old to do this on sloped face :-)" 
    end
    Sketchup.active_model.start_operation('Lock_test', true)
  end
  
  def reset(view)
    view.lock_inference
    @cline.erase! if @cline && @cline.valid?
    view.invalidate
    Sketchup.active_model.axes.set(@orig_origin, @orig_xaxis, @orig_yaxis, @orig_zaxis) if SU_VER_OK 
  end
  def onCancel(reason, view)
    reset(view)
  end
  def deactivate(view)
    reset(view)
    Sketchup.active_model.commit_operation
  end  
  def getExtents
    bb = Geom::BoundingBox.new
    bb.add(@ip.position)
    bb      
  end  
  
  def onMouseMove(flags, x, y, view)
    @ip.pick(view, x, y)
    @line = inference_line(@ip, view) if @ip.face
    view.invalidate
    p "inf_locked: #{view.inference_locked?}"
  end

  def onKeyDown(key, repeat, flags, view)
  puts "k"
    if( key == CONSTRAIN_MODIFIER_KEY )
      if( view.inference_locked? )
        view.lock_inference
      else
        view.lock_inference(@ip)
      end
    end 
  end
  
  def draw(view)
    draw_line(@line, view)
    @ip.draw(view)
    view.tooltip = @ip.tooltip
    #just to see where are we...
    view.draw_points( @ip.position, 5, 4, "blue" ) if @ip
    view.draw_points( @ip_av.position, 5, 2, "orange" ) if @ip_av
    view.draw_points( @ip_avo.position, 5, 1, "red" ) if @ip_avo
    if !SU_VER_OK && !view.inference_locked? && @line
      view.draw_text(view.screen_coords(@ip.position), "\n\nI told you, I can't infer to this sloped face!\nDo it yourself with Shift key!\nYou've got a helpline." ) 
    end
  end

  def inference_line(ip, view)
      vn =  ip.face.vertices.length
      all = ORIGIN
      ip.face.vertices.each{|v| all += ORIGIN.vector_to(v.position)}
      av = Geom::Point3d.new( all.x/vn, all.y/vn, all.z/vn )  
      avo = av.offset(ip.face.normal)
      inf_line = [av, (av - avo).normalize]
      @ip_av=Sketchup::InputPoint.new(av)
      @ip_avo=Sketchup::InputPoint.new(avo)
      para = ( ip.face.normal.parallel?(X_AXIS) || ip.face.normal.parallel?(Y_AXIS) || ip.face.normal.parallel?(Z_AXIS) )? true : false
      Sketchup.active_model.axes.set(@orig_origin, @orig_xaxis, @orig_yaxis, @orig_zaxis) if para && SU_VER_OK
      if SU_VER_OK 
        unless para
          new_origin = av
          new_zaxis = ip.face.normal
          new_xaxis = new_zaxis.axes[0]
          new_yaxis = new_zaxis.axes[1]       
          Sketchup.active_model.axes.set(new_origin, new_xaxis, new_yaxis, new_zaxis) 
        end
      else
        @cline.erase! if @cline && @cline.valid?
        @cline = Sketchup.active_model.active_entities.add_cline(av, (av - avo)) unless para
      end
      view.lock_inference(@ip_av,@ip_avo)
      return inf_line
  end

  def draw_line(line, view)
    return unless line
    view.line_width = 3
    view.set_color_from_line(line[0], @ip.position )
    view.draw(GL_LINES, line[0], @ip.position)
  end
end
Sketchup.active_model.select_tool(Tool_lock_to_face_normal.new)

SU2018:
lock_test_SU2018
SU2015:
lock_test_SU2015s


I’m still interested how can you “remember”, and how to go back to e.g. parallel or perpendicular lock?


Sketchup::InputPoint #transformation vs. #face inconsistency(?)