User Tools

Site Tools


snapping_in_pcb

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
snapping_in_pcb [2017/11/05 16:10]
cparker [Related Pages]
snapping_in_pcb [2018/07/11 21:46] (current)
cparker [Discussion]
Line 16: Line 16:
  
 ===== Proposed System ===== ===== Proposed System =====
 +Implemented in the home/​cparker/​snapping_overhaul branch.
 +
 +====Overview====
 +Snapping is initiated by moving the mouse. The paths for each HID are different, but they ultimately end up calling crosshair.c:​MoveCrosshairAbsolute. This function computes a new location for the crosshair, and updates the X, Y location in the global crosshair structure. The computing a new location part is where the snapping happens. MoveCrosshairAbsolute is triggered by mouse events, so, it can potentially be called very frequently. Consequently,​ this function should execute very quickly.
 +
 +MoveCrosshairAbsolute calculates the new crosshair position by checking for snaps, checking that the crosshair is inside of it's boundaries, and then enforcing DRC. Checking for snaps occurs by iterating through a list of functions, each of which looks for a particular type of object: lines, arcs, elements, pins, etc. The first function to find an object provides the coordinates that the crosshair snaps to. By changing the order of functions in the list, different types of objects can be given different priorities. Both the function that does the iterating and the snapping list can be changed dynamically so that different snapping policies can be implemented depending on the software context.
 +
 +====Details====
 +The snapping code is located in two places: snap.[c,h] and crosshair.c. snap.[c,h] defines several general types that are used as infrastructure for the snapping. crosshair.c is where the snapping actually occurs and is where all of the snapping functions are implemented.
 +
 +There are three new types in snap.[c,​h]: ​
 +  * SnapSpecType:​ This type is an index type for things that can be snapped to. 
 +  * SnapListType:​ A list of SnapSpecType objects
 +  * SnapType: The result of a snapping test
 +
 +Snapping occurs by iterating through SnapSpecType objects in a SnapListType. Each SnapSpecType object has a "​search"​ function pointer that calls a function to look for the the object type associated with that particular SnapSpecType. These functions are presently defined in crosshair.c. So, for example, there is a SnapSpecType object associated with pins and pads called pin_pad_snap. It has a function associated with it called snap_to_pins_pads that calls SearchObjectByLocation to look for and pins or pads under the cursor, and decide if there is a valid snapping target. If there is, it passes that back to the calling function (as an object, not a pointer).
 +
 +The crosshair structure has two related fields:
 +  * (SnapType*) (*snap)(SnapListType*,​ Coord, Coord): a function pointer to a function that does the snapping
 +  * (SnapListType*) snaps: a pointer to a list of things to try snapping to.
 +
 +Presently, snapping occurs whenever the crosshair is repositioned using MoveCrosshairAbsolute (crosshair.c). This function calls Crosshair.snap(Crosshair.snaps,​ X, Y). If this returns non-null, we found something to snap to, and we reposition the crosshair to those coordinates.
 +
 +Both of the crosshair items are implemented as pointers so that they can be changed dynamically. You could, for example, change the snap list when you go into line drawing mode so that you don't snap to elements or other lines, or do so in a different order. Similarly, you could change the snapping function to one that snaps based on which item is closest as opposed to which has the highest priority.
 +
 +====Data Structures====
 +In a nod to the gobject/​glib folks, I've tried to adopt some similar naming conventions for functions.
 +
 +===SnapType===
 +A SnapType contains information about the result of a snapping test. This is basically a messenger type. It's mostly used to convey info from one place to another, and doesn'​t necessarily persist for very long. These are produced by snapping functions to indicate that a snappable object was or wasn't found. They contain a SnapSpecType pointer (that as I write this I realize is never used), an object type indicate, a pointer to a found object, a flag that it was valid for snapping, it's location, and the square of the distance to it.
 +
 +===SnapSpecType===
 +This type contains information about snapping to something. They are identified by a name string, however, they are sorted in a ''​SnapListType''​ by priority. There is an enable flag that can be used to turn off the snap, and there is a radius of effect. Finally, there is a function pointer to a function that looks for the subject object type.
 +
 +Presently, there are ''​SnapSpecType''​s defined for:
 +  * grid points ​
 +  * pins and pads
 +  * elements
 +  * vias
 +  * lines
 +  * arcs, and
 +  * polygons
 +
 +These are all defined in crosshair.c,​ although eventually if we're ever successful at migrating to a more object oriented structure, these should be defined in their respective type files.
 +
 +''​SnapSpecTypes''​ can be created using the ''​snap_spec_new''​ function which takes the name and priority of the spec, or by the ''​snap_spec_copy''​ function which takes a pointer to another ''​SnapSpecType''​.
 +
 +===SnapListType===
 +''​SnapListType''​ is a simple container for organizing ''​SnapSpecTypes''​. It allocates an array of ''​SnapSpecTypes''​ and inserts, removes, or changes the order in that array based on the ''​priority''​ of the ''​SnapSpecType''​. This priority was perhaps a silly way of organizing them since the actual number is irrelevant, however, it does make it fairly easy to change their order, or insert new ones at a given place in the list. 
 +
 +Lists can be created using ''​snap_list_new''​ and destroyed using ''​snap_list_delete''​. You can add a SnapSpec to a SnapList using the ''​snap_list_add_snap''​ function. Snaps are referenced by name, so, to get a pointer to a particular snap you can use ''​snap_list_find_snap_by_name''​. To remove a snap, use ''​snap_list_remove_snap_by_name''​.
 +
 +There are two additional functions associated with SnapLists. ''​snap_list_list_snaps''​ will list all of the available snaps in the log window. ​
 +
 +''​snap_list_search_snaps''​ deserves a little more attention. This is the function that will iterate through the SnapSpecs in a SnapList trying to find an object on the board to snap to. Presently, this is the function called by the crosshair to search for a snappable object. This function implements a snap-to-the-highest-priority-object-under-the-cursor policy, but looping through the SnapList until a valid snap is returned. Another possible implementation of this function would be to look for object candidates of all types and then snap to the nearest one.
 +
 +===Crosshair Integration===
 +All of the ''​SnapSpecs''​ are currently defined manually in crosshair.c,​ as are the snapping functions that search for those types of objects. ​
 +
 +The ''​Crosshair''​ structure is a global structure, formerly defined in global.h but now moved to Crosshair.h,​ that contains crosshair data such as location, and attached objects. Two fields have been added to the Crosshair structure, ''​snap''​ and ''​snaps''​. The first is a function pointer to a snap search function (such as ''​snap_list_search_snaps''​) and the second is a SnapList to be searched. Implementing them in this way allows them to be changed out as needed. So, if you want to change snapping functions to one that searches for the closes object instead of the highest priority one, you can do that easily. If you want to change the SnapList, because perhaps you went into LineMode and don't want to snap to other lines, you can do that easily (of course you could always just search the list and disable those snaps too).
 +
 +These things are all tied together in ''​InitCrosshair''​ which creates the default snap list and adds all of the built in snaps to it, and then links ''​Crosshair.snap''​ to ''​snap_list_search_snaps''​. The actual snapping is done in ''​MoveCrosshairAbsolute''​
 +
 +One thing to note: there is a feature, I think it's called 'Auto Enforce DRC Clearance'​ in the settings menu. It used to be implemented at the end of the ''​FitCrosshairIntoGrid''​ function that was previously responsible for the snapping. This has been moved into ''​MoveCrosshairAbsolute''​ until a better home can be found for it. I don't think this really belongs as part of the crosshair subsystem.
 +
 +====Adding Snaps====
 +TODO: TEST THIS!
 +
 +Under this configuration,​ it is fairly easy to add snaps or change the snapping behavior using plugins. Let's examine how that could work with an example. Let's change how the element snapping works. Presently, it snaps to the location of the element mark. Let's snap to the center of the bounding box instead.
 +
 +First, get the plugin template from [[geda:​pcb_developer_introduction_2]]. In a real plugin, you would probably want to write some actions to enable and disable your features, but, for the sake of this example, we'll skip that. So, delete that stuff. Then, let's grab the existing code for snapping to elements out of crosshair.c,​ and copy it into the plugin file. There are two parts, the snapping function, and the ''​SnapSpecType''​ definition. Here they are (more or less). The snapping function should be fairly easy to read:
 +
 +<code C>
 +/* Snap to elements */
 +static SnapType
 +snap_to_elements(Coord x, Coord y, Coord r)
 +{
 +  SnapType snap;
 +  void *p1, *p2, *p3;
 +  snap.valid = false;
 +    ​
 +  snap.obj_type = SearchObjectByLocation (ELEMENT_TYPE,​ &p1, &p2, &p3,
 +                                          x, y, r);
 +  ​
 +  if (snap.obj_type & ELEMENT_TYPE)
 +  {
 +    /* if we found an element, check to see if we should snap to it */
 +    ElementType *el = (ElementType *) p1;
 +    snap.loc.X = el->​MarkX;​ snap.loc.Y = el->​MarkY;​
 +    snap.distsq = square(snap.loc.X - x) + square(snap.loc.Y - y);
 +    snap.valid = true;
 +  }
 +  return snap;
 +}
 +</​code>​
 +
 +Break it down! (In my head that sounded like M.C. Hammer...) First, search the specified location for an element. If one was found (obj_type is a bit vector in which only one bit should be set for the corresponding type, and ELEMENT_TYPE is a mask with that bit), grab the coordinates of the mark, update the data in the snap, mark the snap as valid, and return the snap.
 +
 +The snap coordinates are where there crosshair will snap if this snap is selected. It's clear what we need to change to achieve our new objective. Instead of populating the snap location with the location of the element mark, we need to populate it with the center of the bounding box. Simple enough. The second part is the new ''​SnapSpecType''​ definition. This is a littler harder to read, but fortunately it template definition was nicely commented for you (your welcome), so all you have to do is update a few things. You need to change the name to something unique, the function pointer to the updated function (which you should rename), and the priority. You'll also probably want to enable it (although that can be done in the GTK HID if you'd rather), and you can change the default radius if you want.
 +
 +<code C>
 +static SnapSpecType element_snap = {
 +  "Snap to elements", ​      // Name
 +  &​snap_to_elements, ​       // Function pointer
 +  false, ​                   // enabled
 +  10,                       // priority
 +  1000000, ​                 // radius (nm)
 +  0                         // object type
 +};
 +</​code>​
 +
 +Finally, all that's left to do is register our new snap in the snap list. For that we'll want to copy a line out of InitCrosshair:​
 +
 +<code C>
 +snap_list_add_snap(Crosshair.snaps,​ &​element_snap);​
 +</​code>​
 +
 +Change that to point to your new snap definition. Your resulting code should look something like this:
 +<code C snap_plugin.c>​
 +//
 +//  snap_plugin.c
 +//  ​
 +//  Created by Parker, Charles W. on 2018-06-03.
 +//
 +
 +#include <​stdio.h>​
 +
 +#include "​globalconst.h"​
 +#include "​global.h"​ // types
 +
 +#include "​crosshair.h"​
 +#include "​snap.h"​
 +
 +/* Snap to elements */
 +static SnapType
 +snap_to_element_centers(Coord x, Coord y, Coord r)
 +{
 +  SnapType snap;
 +  void *p1, *p2, *p3;
 +  snap.valid = false;
 +  ​
 +  /* if we're not drawing rats, check for elements first */
 +  if (PCB->​RatDraw) return snap;
 +  ​
 +  snap.obj_type = SearchObjectByLocation (ELEMENT_TYPE,​ &p1, &p2, &p3,
 +                                          x, y, r);
 +  ​
 +  if (snap.obj_type & ELEMENT_TYPE)
 +  {
 +    /* if we found an element, check to see if we should snap to it */
 +    ElementType *el = (ElementType *) p1;
 +    snap.loc.X = (el->​BoundingBox.X1 - el->​BoundingBox.X2) / 2; 
 +    snap.loc.Y = (el->​BoundingBox.Y1 - el->​BoundingBox.Y2) / 2;
 +    snap.distsq = square(snap.loc.X - x) + square(snap.loc.Y - y);
 +    snap.valid = true;
 +  }
 +  return snap;
 +}
 +
 +static SnapSpecType element_center_snap = {
 +  "Snap to element centers",//​ Name
 +  &​snap_to_element_centers,​ // Function pointer
 +  true,                     // enabled
 +  15,                       // priority
 +  1000000, ​                 // radius (nm)
 +  0                         // object type
 +};
 +
 +
 +void
 +pcb_plugin_init()
 +{
 +  printf("​Loading plugin: snap to element centers\n"​);​
 +  snap_list_add_snap(Crosshair.snaps,​ &​element_center_snap);​
 +}
 +</​code>​
 +
 +Compile that, and put the result in your plugins folder. Run pcb from the command line so that you can see the message to verify that your plugin loaded. Then open up the preferences dialog and go to the snapping page. You should see your new snap in there! Now disable all the other snaps and open up a layout with some elements in it. When you mouse over an element, the crosshair should snap to the center of the element! (note that some bounding boxes could be drawn strangely).
 +
 +Congrats! You've extended pcb with a new snap. You could do something similar to replace the snapping function.
 +
 +=====Discussion=====
 +This section is for ideas and discussion regarding things related to the snapping implementation.
 +
 +==Who Does the Snapping==
 +Regarding the crosshair integration,​ there is a question in my mind about algorithm. Should the crosshair be responsible for the snapping? Presently the procedure is...
 +  * the mouse is moved 
 +  * a function is called to move the crosshair to the mouse location
 +  * the crosshair checks underneath it for something to snap to
 +  * if it finds something, it adjust the coordinates to the location of the object
 +  * it moves the crosshair to the new location.
 +Perhaps is should be that the HID calls the snapping, and the crosshair just goes where it's told:
 +  * the mouse is moved
 +  * the a snap is searched for underneath the mouse location
 +  * if a snappable object is found, the coordinates are updated
 +  * the crosshair is told to move to the new location
 +
 +==Need to Know==
 +One thing I'm on the fence about is if the snapping functions should get a pointer to the SnapSpecType they were called from. Presently, the functions answer the question: "is there something of this type under the cursor that can be snapped to?". To answer that question, the SnapSpecType data isn't required, and so presently it's not provided. However, if this were to become more sophisticated in the future, it might become necessary.
 +
 +==Snapping Modifiers==
 +A very nice feature would be if the user could press a mod key to change the snapping behavior. Perhaps the cursor only snaps to grid points normally, and then starts snapping to everything else when "​alt"​ is held.
 +
 +There are several places where this kind of behavior could be implemented. The basic idea behind them all is that you would have a second ''​SnapList''​ that was applied to the other condition.
 +  1. In the search_snaps function. It could check in with the gui to see if the modifier is active, and then select a ''​SnapList''​ to search.
 +  2. The crosshair could do the same thing before it calls the search_snaps,​ and pass the appropriate list to the search function.
 +  3. The gui could control it, watch the modifier key and change the active SnapList when the state of the key changes.
 +  4. A plugin can actually be implemented to do this. Since plugins can access the gtk structures, a plugin could install a listener function to listen for modifier key strokes, and update the crosshair snap list accordingly.
 +
 +Option 4 is the most modular, and I did write this infrastructure so that plugins could be used to modify the behavior. I think I'm going to try that method first. ​
 +
 +How does it work?
 +The plugin is loaded and creates it's alternate ''​SnapList''​. It can populate it from the structures that already exist in crosshair.c,​ I think, by declaring them as externs.
 +=====TODO List=====
 +  * snap based on screen distance instead of linear distance
 +  * don't snap to off-screen objects
 +  * snap-to-the-closest-object function
 +  * snapping preferences saving
 +  * sub-snaps? (lines, line endpoints, midpoints... )
 +  * lesstif config page
 +  * tests
  
 ===== Related Pages ===== ===== Related Pages =====
   * [[PCB Crosshair|PCB Crosshair]]   * [[PCB Crosshair|PCB Crosshair]]
  
snapping_in_pcb.1509916240.txt.gz ยท Last modified: 2017/11/05 16:10 by cparker