Request for help in developing a new Tool
-
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.
-
I think view.screen_coords needs the actual position so
view.draw_text view.screen_coords(@ip1.position), "Test0"
should make it work
-
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!)
-
@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
-
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?
-
@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. -
Use
vectorC = vectorA.cross(vectorB)
Substituting thevec3
[the 'flat-vector' you've just made] and theX_AXIS
forvectorA
&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... -
Thanks again, TIG.
-
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.
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.
-
can you utilise
face.bounds.center.normal
where face is the 'new' face???
thinking out loud...
john -
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!
-
@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
Advertisement