sketchucation logo sketchucation
    • Login
    🤑 SketchPlus 1.3 | 44 Tools for $15 until June 20th Buy Now

    Request for help in developing a new Tool

    Scheduled Pinned Locked Moved Plugins
    12 Posts 4 Posters 571 Views 4 Watching
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • J Offline
      johnwmcc
      last edited by

      I'm trying to redevelop from scratch a tool I had working (more or less), but in which the coding had got too tangled to take much further.

      It is based very heavily on the sample CLineTool by Jim Foltz. I've appended my working copy of this, since I can't find a definitive newer version either in the Extension Warehouse or the Plugin Store on this site.

      I want to start again to build the tool from scratch, this time trying to understand how the whole Tool class works, whereas last time I just fiddled with the existing code to adapt it, without really understanding quite a bit of how it works.

      I've put together a 'skeleton' ruby module and class, called DrawFraming, which I will eventually use to draw wood/timber framing in standard UK softwood sizes. In its former mostly working version, it allows me to

      • choose a standard cross-section of wood (by R-click from a menu), or define a custom size
      • place one corner in the drawing by mouse click at first pick point
      • choose what is to become the component's long axis, either normal to the face at the pick point, or using an arrow cursor key to toggle X, Y, or Z_axis lock on or off
      • orient the cross section by mouse movement around the first pick point, drawing the outline as feedback

      A second mouse click then creates the geometry for the cross-section in the chosen location, and then switches to the built-in PushPull Tool to pull it to the desired length, by inferencing or VCB input.

      I had most of that working, but decided I wanted it to be cleaner code, and also want to understand how to get working properly some features that I couldn't get to work quite the way I want - like suspend and resume the DrawFraming tool during Zoom operations, for example, or resume DrawFraming after the PushPull tool finishes.

      But I'm stuck at a couple of rather basic things which don't error, but don't work either, though as far as I can see they copy the relevant parts of Jim's code.

      I attach the current draft skeleton. I've only implemented parts of initialize, activate, reset, onMouseMove, onSetCursorand onLButtonDown. The other Tool methods are just stubs.

      I've got the tool to set the cursor image, but can't get it to display text at the cursor, nor to inference or display tooltips when hovering over existing drawing elements.

      In lines 94-98, the code

      
      # set the tooltip that should be displayed to this point
      view.tooltip = @ip1.tooltip
      ...
      # Display cursor text to give feedback at cursor
      view.draw_text view.screen_coords(@ip1), "Test0" 
      
      

      does nothing.

      In my older more complete version it DID work, and it does in the CLineTool too, but not here.

      I'd like to understand why. Perhaps I haven't initialized something properly? Missed a few lines of the clinetool_main.rb code that matters?

      Any clues would be most welcome.

      Another small point puzzles me. The onSetCursor method where my cursor is displayed seems to get called on every mouse move. I tried putting

      UI.set_cursor @cursor_id 
      

      in the activatemethod, but it does nothing there. Is this just the way it has to work? The Ruby API shows this code as an example, and it works, but it seems unnecessary to call it so often when I only have one cursor.


      jwm_draw_framing.rbz


      clinetool.rbz

      1 Reply Last reply Reply Quote 0
      • sdmitchS Offline
        sdmitch
        last edited by

        I think view.screen_coords needs the actual position so

        view.draw_text view.screen_coords(@ip1.position), "Test0"

        should make it work

        Nothing is worthless, it can always be used as a bad example.

        http://sdmitch.blogspot.com/

        1 Reply Last reply Reply Quote 0
        • J Offline
          johnwmcc
          last edited by

          Many thanks for responding.

          The Ruby API docs say there should be two parameters to the view.draw_text method - point and text:
          point: A Point3d object.
          text: The text string to draw.

          Unfortunately, your suggestion doesn't work, though I can see why it should.

          Re-read the API docs for view.draw_text. Finally spotted that it says:

          View.draw_text
          This method is used to draw text on the screen.
          This method is usually invoked within the draw method of a tool.

          In my previous almost-working tool, I had the code in both places - the onMouseMove, AND in the draw_geometry method. When I put it in the draw method here, and drawgets called, it works. DOH!

          (Note to self: RTFM more carefully!)

          1 Reply Last reply Reply Quote 0
          • D Offline
            driven
            last edited by

            @johnwmcc said:

            ...This method is usually invoked within the draw method of a tool.

            It should say This method is ONLY WORKS when invoked within the draw method of a tool.

            I have many a doh! moments with it...

            john

            learn from the mistakes of others, you may not live long enough to make them all yourself...

            1 Reply Last reply Reply Quote 0
            • J Offline
              johnwmcc
              last edited by

              I'm making progress on this, but I've run into another question.

              At one point, I need to find the angle between a vector in the X-Y (red/green) plane and the X_AXIS.

              I calculate

              
              # Calculate a vector (vec3) which is the projection of a face.normal onto the red/green plane
                 vec3 = Geom;;Vector3d.new [face.normal.x, face.normal.y, 0]
                 rotate3 = X_AXIS.angle_between vec3 
              
              

              Perhaps naively, I had expected the value of rotate3 to change sign if the direction of vec3 is rotated past the X_AXIS from (say) [1,-1,0] (-45 degrees) through [1,0,0] (0 degrees) to [1,1,0] +45 degrees.

              But it doesn't. rotate 3 is always positive.

              Is this because in the general case, the angle between two arbitrary vectors doesn't have a 'sense of direction'?

              So do I have to distinguish cases where face.normal.y may be positive or negative?

              Doing that with an IF statement works.

              But is there a more elegant way of calculating this angle, WITH a sign attached?

              1 Reply Last reply Reply Quote 0
              • sdmitchS Offline
                sdmitch
                last edited by

                @johnwmcc said:

                I'm making progress on this, but I've run into another question.

                At one point, I need to find the angle between a vector in the X-Y (red/green) plane and the X_AXIS.

                I calculate

                
                > # Calculate a vector (vec3) which is the projection of a face.normal onto the red/green plane
                >    vec3 = Geom;;Vector3d.new [face.normal.x, face.normal.y, 0]
                >    rotate3 = X_AXIS.angle_between vec3 
                > 
                

                Perhaps naively, I had expected the value of rotate3 to change sign if the direction of vec3 is rotated past the X_AXIS from (say) [1,-1,0] (-45 degrees) through [1,0,0] (0 degrees) to [1,1,0] +45 degrees.
                But it doesn't. rotate 3 is always positive.

                the angle will always be positive and between 0 and 180 degrees

                Is this because in the general case, the angle between two arbitrary vectors doesn't have a 'sense of direction'?

                So do I have to distinguish cases where face.normal.y may be positive or negative?
                Yes
                Doing that with an IF statement works.

                But is there a more elegant way of calculating this angle, WITH a sign attached?
                No I don't think so.

                Nothing is worthless, it can always be used as a bad example.

                http://sdmitch.blogspot.com/

                1 Reply Last reply Reply Quote 0
                • TIGT Offline
                  TIG Moderator
                  last edited by

                  Use vectorC = vectorA.cross(vectorB)
                  Substituting the vec3 [the 'flat-vector' you've just made] and the X_AXIS for vectorA & vectorB as appropriate***.
                  if vectorC == face.normal then the angle is positive; if it is not - i.e. it's ==face.normal.reverse then the angle is negative. I think you should just get a +/-ve Z value difference ?

                  ***The order you substitute the two vectors in the 'cross' affects the resultant vectorC...
                  Do some tests to work out which way is which...

                  TIG

                  1 Reply Last reply Reply Quote 0
                  • J Offline
                    johnwmcc
                    last edited by

                    Thanks again, TIG.

                    1 Reply Last reply Reply Quote 0
                    • J Offline
                      johnwmcc
                      last edited by

                      I thought I'd just got to a working version of this tool, when I discovered a bug in my code that I don't understand, and wonder if anyone can help me figure it out.

                      I have drawn (using my Draw Framing tool) a component representing a rectangular piece of wood, at an arbitrary angle (though it can also happen at orthogonal angles).

                      I pick a point on its face from which to start drawing another component, but I find that the reported face.normal vector is along the green axis, not normal to the face.

                      The image may help to visualize it. The magenta rectangle is generated by the draw_geometry method of my tool, which also draws the face normal along what will become the @long_axis of the about-to-be-created component.

                      Shows calculated face.normal visibly NOT normal to the top face of the component

                      But as you can see in the drawing, instead of being normal to the face, the @long_axis is parallel to the green axis. (By the way, the 3x2 cursor text is the nominal size in inches of the cross section of the component about to be drawn, in case you were wondering.)

                      The code which finds the face normal is derived from the sample ruby CLineTool:

                      
                      @ip1.pick view, x, y
                      if( @ip1.valid? )
                      ... other code...
                      ## Detect if pick point is on a face, and if so, orient long axis normal to it
                      #   unless axis is locked
                      if @ip.face 
                         f = @ip.face
                         puts  "Face picked; normal is \n"
                         puts f.normal.inspect
                         if @@axis_lock == NO_LOCK # axis not locked
                           @long_axis = f.normal
                           puts "@long_axis = " + @long_axis.inspect
                      
                      

                      This outputs in the Ruby console :

                      
                      Face picked; normal is 
                      Vector3d(0, 1, 0)
                      @long_axis = Vector3d(0, 1, 0)
                      
                      

                      The code which draws the feedback geometry is:

                      
                      # Display long axis as visual feedback
                        view.line_width = 2; view.line_stipple = ""
                        view.set_color_from_line(pt1 - @long_axis, pt1 + @long_axis)
                        view.draw_line(@first_pick.position, @first_pick.position + @long_axis) 
                        ...
                        view.drawing_color = "magenta" 
                        view.draw_polyline(@profile_points)
                      
                      

                      I find that if I open the component for editing, and copy and paste its top face into the World coordinate system, the face.normal IS normal to this face, which is in the red-blue plane, not at the angle it appears as the top surface of the component.

                      I'm sure this is because I've originally drawn the far end face of the visible component in the r-g plane (as @profile_points0, which got transformed into @profile_points in the feedback view.draw.polyline above), then did the same transforms on the component face -- rotated it about two different axes and translated it -- then push-pulled it along (its) long_axis to generate the component shown.

                      Yet (as you can see from the blue highlighted bounding box) the previous component axes are in line with its faces.

                      In case it helps, I also attach the complete tool draw_framing.rb file.

                      Any clues as to how to fix this would be most welcome. Until I discovered this 'bug' in my code, I thought I was nearing v1.0 of my code!

                      So how do I reset the axes of the original face and the component which it becomes, to get a component drawn whose faces AREN'T weirdly distorted internally so as to throw off the apparent face.normal?

                      I found (before I recoded it this way) that if I first made a group, then a component instance, it didn't have this problem, but its component axes were along world axes, not tightly around the shape of a non-orthogonal component. That is easy to fix manually with a Change Axes command, but there isn't an API function to do the same, afaik.

                      If anyone knows how to do that, I could revert to that method of creating the component instance.

                      For example, would it help to explode the component in code, then re-group it and make component?

                      Or can anyone suggest an alternative approach altogether?

                      Thanks in advance for any advice I could follow, to work round this.


                      Code that generates this odd component

                      1 Reply Last reply Reply Quote 0
                      • D Offline
                        driven
                        last edited by

                        can you utilise

                        face.bounds.center.normal
                        

                        where face is the 'new' face???

                        thinking out loud...
                        john

                        learn from the mistakes of others, you may not live long enough to make them all yourself...

                        1 Reply Last reply Reply Quote 0
                        • J Offline
                          johnwmcc
                          last edited by

                          Thanks for the suggestion. Unfortunately, it reports just the same normal as face.normal - off at an angle from the face, but along one axis to which the face is NOT normal geometrically!

                          I shall look again at the sequence of inserting and transforming the face into the component definition - I'm now pretty sure that the problem lies in the create_geometry method, where I draw the face, insert it into a component definition, then transform it to the required position. Doing that is somehow seriously distorting where the component thinks its face normals point - not normal to the physical face!

                          1 Reply Last reply Reply Quote 0
                          • sdmitchS Offline
                            sdmitch
                            last edited by

                            @johnwmcc said:

                            Thanks for the suggestion. Unfortunately, it reports just the same normal as face.normal - off at an angle from the face, but along one axis to which the face is NOT normal geometrically!

                            I shall look again at the sequence of inserting and transforming the face into the component definition - I'm now pretty sure that the problem lies in the create_geometry method, where I draw the face, insert it into a component definition, then transform it to the required position. Doing that is somehow seriously distorting where the component thinks its face normals point - not normal to the physical face!

                            If you are picking a face in a component or group, the normal will need to be transformed.

                            @ip1.pick view, x, y
                            if( @ip1.valid? )
                            ... other code...
                            ## Detect if pick point is on a face, and if so, orient long axis normal to it
                            #   unless axis is locked
                            if @ip.face 
                               f = @ip.face
                               puts  "Face picked; normal is \n"
                            #######
                               n = @ip.face.normal; t = @ip.transformation; n.transform! t
                            #######
                               puts f.normal.inspect; n.inspect
                               
                               if @@axis_lock == NO_LOCK # axis not locked
                                 @long_axis = f.normal
                                 puts "@long_axis = " + @long_axis.inspect
                            

                            Nothing is worthless, it can always be used as a bad example.

                            http://sdmitch.blogspot.com/

                            1 Reply Last reply Reply Quote 0
                            • 1 / 1
                            • First post
                              Last post
                            Buy SketchPlus
                            Buy SUbD
                            Buy WrapR
                            Buy eBook
                            Buy Modelur
                            Buy Vertex Tools
                            Buy SketchCuisine
                            Buy FormFonts

                            Advertisement