Sketcher: Ellipse support

Here's the place for discussion related to coding in FreeCAD, C++ or Python. Design, interfaces and structures.
Forum rules
Be nice to others! Respect the FreeCAD code of conduct!
abdullah
Veteran
Posts: 4935
Joined: Sun May 04, 2014 3:16 pm
Contact:

Re: Sketcher: Ellipse support

Post by abdullah »

Sebastian, should I take any action regarding this?
User avatar
shoogen
Veteran
Posts: 2823
Joined: Thu Dec 01, 2011 5:24 pm

Re: Sketcher: Ellipse support

Post by shoogen »

It would be nice if you could integrate it.
abdullah
Veteran
Posts: 4935
Joined: Sun May 04, 2014 3:16 pm
Contact:

Re: Sketcher: Ellipse support

Post by abdullah »

shoogen wrote:It would be nice if you could integrate it.
My pleasure.

Done in:

https://github.com/abdullahtahiriyo/Fre ... master.git
3ec6114..c44c634 skt2_rebase_DeepSOIC_attempt_rebased_to_master_for_Draft_And_Drawing_Fixes

https://github.com/abdullahtahiriyo/Fre ... wing_Fixes
User avatar
marktaff
Posts: 47
Joined: Sun Sep 21, 2014 3:25 pm
Location: Seattle, Washington, USA

Re: Sketcher: Ellipse support

Post by marktaff »

Just wanted to touch base. I have both ellipse construction methods implemented in the handler class, I just need to integrate it into the UI similar to the circle. I'll probably be done later today or Sunday, depending on coding time available.
Available on chat.freenode.net#freecad while working on FreeCad
abdullah
Veteran
Posts: 4935
Joined: Sun May 04, 2014 3:16 pm
Contact:

Re: Sketcher: Ellipse support

Post by abdullah »

marktaff wrote:Just wanted to touch base. I have both ellipse construction methods implemented in the handler class, I just need to integrate it into the UI similar to the circle. I'll probably be done later today or Sunday, depending on coding time available.
Great!! :) Anyway, do not get stressed. I still have some cleaning up to do anyway. ;)
abdullah
Veteran
Posts: 4935
Joined: Sun May 04, 2014 3:16 pm
Contact:

Re: Sketcher: Ellipse support

Post by abdullah »

More goodies to Ellipse support in:

https://github.com/abdullahtahiriyo/Fre ... master.git
c44c634..b91443e skt2_rebase_DeepSOIC_attempt_rebased_to_master_for_Draft_And_Drawing_Fixes

Code: Select all

commit b91443e9f741993f67fdef8b676c6cea1855ea04
Author: Abdullah Tahiri <abdullah.tahiri.yo@gmail.com>
Date:   Sun Oct 19 07:51:36 2014 +0200

    Added code to convert an Part.Arc having a base shape of an ellipse to the sketcher.
    
    However I do not know what to do in the UI to trigger this in the UI (other than using Draft WB, which does not use this code)

commit 4a841706a4425f8f2ea963d018265f671bccf1a9
Author: Abdullah Tahiri <abdullah.tahiri.yo@gmail.com>
Date:   Sun Oct 19 07:30:27 2014 +0200

    Fix for the SeekAutoConstraint of Ellipse
Now, when drawing a line in a position near to being tangent to an existing ellipse, the tangency is automatically detected and created line and tangent constraint.

The last commit... I detected this code when coding hyperbola, I thought this would be related to Jim's post about Part Arc of ellipse being converted to a full ellipse. I have coded the conversion for an arc of ellipse, however, surprisingly, when using the Draft WB blue-to-sketcher (icon), this code block is not executed (neither for an arc of circle). So:
1) either there is another way to put an arc created with Part WB into an sketch that uses this code
2) there is some other coding to do in the python written Draft WB...
abdullah
Veteran
Posts: 4935
Joined: Sun May 04, 2014 3:16 pm
Contact:

Re: Sketcher: Ellipse support

Post by abdullah »

In Draft.py, I tracked down the problem with the Part arcofellipse being converted to an ellipse to this function:

def makeSketch(objectslist,autoconstraints=False,addTo=None,delete=False,name="Sketch"):

In the code I can not identify where an Ellipse (for the case of converting an Ellipse) is being handled. Well, I even fail to understand the first line listed below (what is "list" there?)...

Code: Select all

    if not isinstance(objectslist,list):
        objectslist = [objectslist]
    if addTo:
        nobj = addTo
    else:
        nobj = FreeCAD.ActiveDocument.addObject("Sketcher::SketchObject",name)
        nobj.ViewObject.Autoconstraints = False
    for obj in objectslist:
        ok = False
        tp = getType(obj)
        if tp == "BSpline":
            print("makeSketch: BSplines not supported")
        elif tp == "Circle":
            g = (DraftGeomUtils.geom(obj.Shape.Edges[0],nobj.Placement))
            nobj.addGeometry(g)
            # TODO add Radius constraits
            ok = True
        elif tp == "Rectangle":
Maybe some more proficient python coder can take a look to it... ;)

EDIT: BTW the code goes to:
PyObject* SketchObjectPy::addGeometry(PyObject *args) in SketchObjectPyImp.cpp
but not as a trimmedcurve!!! (case of Arc Of Ellipse). This is why I was not detecting it before. It has to come as a trimmedcurve type having an ellipse as inside curve. Then the current code in SketchObjectPyImp.cpp will handle it...
User avatar
shoogen
Veteran
Posts: 2823
Joined: Thu Dec 01, 2011 5:24 pm

Re: Sketcher: Ellipse support

Post by shoogen »

abdullah wrote:Well, I even fail to understand the first line listed below (what is "list" there?)...

Code: Select all

    if not isinstance(objectslist,list):
        objectslist = [objectslist]
If a single object was provided, a list gets created. The usage of isinstance to check if objectlist "is a" "list" is not really pythonic. It is necessary to ensure that objectlist is iterable* for the upcomming for loop a few lines later.

*) as the list is not muted. objectlist does not need to be a list. it would suffice to be iterable. http://stackoverflow.com/questions/1952 ... 55#1952655
abdullah
Veteran
Posts: 4935
Joined: Sun May 04, 2014 3:16 pm
Contact:

Re: Sketcher: Ellipse support

Post by abdullah »

shoogen wrote:
abdullah wrote:Well, I even fail to understand the first line listed below (what is "list" there?)...

Code: Select all

    if not isinstance(objectslist,list):
        objectslist = [objectslist]
If a single object was provided, a list gets created. The usage of isinstance to check if objectlist "is a" "list" is not really pythonic. It is necessary to ensure that objectlist is iterable* for the upcomming for loop a few lines later.

*) as the list is not muted. objectlist does not need to be a list. it would suffice to be iterable. http://stackoverflow.com/questions/1952 ... 55#1952655
Thanks Sebastian. Now with your explanation my problem is much more basic. I was unaware that list was naming a builtin type.

Do you know how does a Type "Ellipse" manage to get added with "addGeometry" in that loop??

Code: Select all

def makeSketch(objectslist,autoconstraints=False,addTo=None,delete=False,name="Sketch"):
    '''makeSketch(objectslist,[autoconstraints],[addTo],[delete],[name]): makes a Sketch
    objectslist with the given Draft objects. If autoconstraints is True,
    constraints will be automatically added to wire nodes, rectangles
    and circles. If addTo is an existing sketch, geometry will be added to it instead of
    creating a new one. If delete is True, the original object will be deleted'''
    import Part, DraftGeomUtils
    from Sketcher import Constraint

    StartPoint = 1
    EndPoint = 2
    MiddlePoint = 3
    
    if not isinstance(objectslist,list):
        objectslist = [objectslist]
    if addTo:
        nobj = addTo
    else:
        nobj = FreeCAD.ActiveDocument.addObject("Sketcher::SketchObject",name)
        nobj.ViewObject.Autoconstraints = False
    for obj in objectslist:
        ok = False
        tp = getType(obj)
        if tp == "BSpline":
            print("makeSketch: BSplines not supported")
        elif tp == "Circle":
            g = (DraftGeomUtils.geom(obj.Shape.Edges[0],nobj.Placement))
            nobj.addGeometry(g)
            # TODO add Radius constraits
            ok = True
        elif tp == "Rectangle":
            if obj.FilletRadius.Value == 0:
                for edge in obj.Shape.Edges:
                    nobj.addGeometry(edge.Curve)
                if autoconstraints:
                    last = nobj.GeometryCount - 1
                    segs = [last-3,last-2,last-1,last]
                    if obj.Placement.Rotation.Q == (0,0,0,1):
                        nobj.addConstraint(Constraint("Coincident",last-3,EndPoint,last-2,StartPoint))
                        nobj.addConstraint(Constraint("Coincident",last-2,EndPoint,last-1,StartPoint))
                        nobj.addConstraint(Constraint("Coincident",last-1,EndPoint,last,StartPoint))
                        nobj.addConstraint(Constraint("Coincident",last,EndPoint,last-3,StartPoint))
                    nobj.addConstraint(Constraint("Horizontal",last-3))
                    nobj.addConstraint(Constraint("Vertical",last-2))
                    nobj.addConstraint(Constraint("Horizontal",last-1))
                    nobj.addConstraint(Constraint("Vertical",last))
                ok = True
        elif tp in ["Wire","Polygon"]:
            if obj.FilletRadius.Value == 0:
                closed = False
                if tp == "Polygon":
                    closed = True
                elif hasattr(obj,"Closed"):
                    closed = obj.Closed
                for edge in obj.Shape.Edges:
                    nobj.addGeometry(edge.Curve)
                if autoconstraints:
                    last = nobj.GeometryCount
                    segs = list(range(last-len(obj.Shape.Edges),last-1))
                    for seg in segs:
                        nobj.addConstraint(Constraint("Coincident",seg,EndPoint,seg+1,StartPoint))
                        if DraftGeomUtils.isAligned(nobj.Geometry[seg],"x"):
                            nobj.addConstraint(Constraint("Vertical",seg))
                        elif DraftGeomUtils.isAligned(nobj.Geometry[seg],"y"):
                            nobj.addConstraint(Constraint("Horizontal",seg))
                    if closed:
                        nobj.addConstraint(Constraint("Coincident",last-1,EndPoint,segs[0],StartPoint))
                ok = True
        if (not ok) and obj.isDerivedFrom("Part::Feature"):
            if not DraftGeomUtils.isPlanar(obj.Shape):
                print("Error: The given object is not planar and cannot be converted into a sketch.")
                return None
            for e in obj.Shape.Edges:
                if DraftGeomUtils.geomType(e) == "BSplineCurve":
                    print("Error: One of the selected object contains BSplines, unable to convert")
                    return None
            if not addTo:
                nobj.Placement.Rotation = DraftGeomUtils.calculatePlacement(obj.Shape).Rotation
            edges = []
            for e in obj.Shape.Edges:
                g = (DraftGeomUtils.geom(e,nobj.Placement))
                if g:
                    nobj.addGeometry(g)
            ok = True
        formatObject(nobj,obj)
        if ok and delete:
            FreeCAD.ActiveDocument.removeObject(obj.Name)
    FreeCAD.ActiveDocument.recompute()
    return nobj
User avatar
marktaff
Posts: 47
Joined: Sun Sep 21, 2014 3:25 pm
Location: Seattle, Washington, USA

Re: Sketcher: Ellipse support

Post by marktaff »

Well, I've got my code done, but of course my old nemesis git is rearing its ugly head. :?

I cloned abdullah's code: https://github.com/marktaff/FreeCAD_sf_ ... IC_attempt

ETA: Don't bury the lede, Mark! The above link now points to the public code, thanks to lame_r on #freecad. :-)

Code: Select all

git branch
  master
* skt2_rebase_DeepSOIC_attempt
I made my changes and committed them:

Code: Select all

git log origin/master..f0a4339621b0bf901754af14c3cd36c95ca55966
commit f0a4339621b0bf901754af14c3cd36c95ca55966
Author: Mark A. Taff <mark@marktaff.com>
Date:   Sun Oct 19 13:21:41 2014 -0700

    Implement two construction methods for ellipses:
    
      --Center, major radius, minor radius
      --Periapsis, apoapsis, minor radius
    
    Artist: We need an icon for periapsis, apoapsis, minor radius method.
But when I try to push my local commit, it fails. It seems it is trying to push the changes to abdullah's repo instead of mine:

Code: Select all

git push
<snip>
remote: Permission to abdullahtahiriyo/FreeCAD_sf_master.git denied to marktaff.
fatal: unable to access 'https://github.com/abdullahtahiriyo/FreeCAD_sf_master.git/': The requested URL returned error: 403
If someone wants to ping me on #freecad to help me out, that'd be great. In the meantime, here is the diff for your perusal:

Code: Select all

diff --git a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp
index c0a74b5..c497bfe 100644
--- a/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp
+++ b/src/Mod/Sketcher/Gui/CommandCreateGeo.cpp
@@ -27,6 +27,10 @@
 # include <QApplication>
 #endif
 
+#include <qdebug.h>
+#include <QString>
+#include <GC_MakeEllipse.hxx>
+
 #include <boost/math/special_functions/fpclassify.hpp>
 #include <Base/Console.h>
 
@@ -1779,7 +1783,9 @@ bool CmdSketcherCreateCircle::isActive(void)
 }
 // ======================================================================================
 
-/* XPM */
+/**
+ * @brief Creates a 32x32 pixel XPM image for the mouse cursor when making an ellipse
+ */
 static const char *cursor_createellipse[]={
 "32 32 3 1",
 "+ c white",
@@ -1818,210 +1824,803 @@ static const char *cursor_createellipse[]={
 "................................",
 "................................"};
 
+/**
+ * @brief This class handles user interaction to draw and save the ellipse
+ *
+ * Two construction methods are implemented:
+ *   -Periapsis, apoapsis, and b; and
+ *   -Center, periapsis, and b.
+ *
+ * The first method limits the ellipse to a circle, while the second method allows for
+ * swapping of the semi-major and semi-minor axes.
+ *
+ * We use three reference frames in this class.  The first (and primary), is the cartesian
+ * frame of the sketcher; all our work begins and ends in this frame.  The second is the
+ * perifocal frame of the ellipse using polar coordinates.  We use this frame for naming
+ * conventions and working with the ellipse.  The last is a rotated right-handed cartesian
+ * frame centered at the ellipse center with the +X direction towards periapsis, +Z out of
+ * screen.
+ *
+ * When working with an ellipse in the perifocal frame, the following equations are useful:
+ *
+ *    \f{eqnarray*}{
+ *        r &\equiv& \textrm{ radial distance from the focus to a point on the ellipse}\\
+ *        r_a &\equiv& \textrm{ radial distance from the focus to apopasis}\\
+ *        r_p &\equiv& \textrm{ radial distance from the focus to periapsis}\\
+ *        a &\equiv& \textrm{ length of the semi-major axis, colloquially 'radius'}\\
+ *        b &\equiv& \textrm{ length of the semi-minor axis, colloquially 'radius'}\\
+ *        e &\equiv& \textrm{ eccentricity of the ellipse}\\
+ *        \theta_b &\equiv& \textrm{ angle to the intersection of the semi-minor axis and the ellipse, relative to the focus}\\
+ *        ae &\equiv& \textrm{ distance from the focus to the centroid}\\
+ *        r &=& \frac{a(1-e^2)}{1+e\cos(\theta)} = \frac{r_a(1-e)}{1+e\cos(\theta)} = \frac{r_p(1+e)}{1+e\cos(\theta)}\\
+ *        r_a &=& a(1-e)\\
+ *        r_p &=& a(1+e)\\
+ *        a &=& \frac{r_p+r_a}{2}\\
+ *        b &=& a\sqrt{1-e^2}\\
+ *        e &=& \frac{r_a-r_p}{r_a+r_p} = \sqrt{1-\frac{b^2}{a^2}}\\
+ *        \theta_b &=& \left[\pi - \arctan\left(\frac{b}{ae}\right)\right] \pm N\pi
+ *   \f}
+ *
+ */
 class DrawSketchHandlerEllipse : public DrawSketchHandler
 {
 public:
-    DrawSketchHandlerEllipse() : Mode(STATUS_SEEK_First),EditCurve(34){}
+    DrawSketchHandlerEllipse(int constructionMethod) :
+        constrMethod(constructionMethod),
+        editCurve(33)
+    {
+    }
     virtual ~DrawSketchHandlerEllipse(){}
-    /// mode table
+    /// Mode table, describes what step of the process we are in
     enum SelectMode {
-        STATUS_SEEK_First,      /**< enum value ----. */
-        STATUS_SEEK_Second,     /**< enum value ----. */
-        STATUS_SEEK_Third,     /**< enum value ----. */        
-        STATUS_Close
+        STATUS_SEEK_PERIAPSIS,  /**< enum value, looking for click to set periapsis. */
+        STATUS_SEEK_APOAPSIS,   /**< enum value, looking for click to set apoapsis. */
+        STATUS_SEEK_CENTROID,   /**< enum value, looking for click to set centroid. */
+        STATUS_SEEK_A,          /**< enum value, looking for click to set a. */
+        STATUS_SEEK_B,          /**< enum value, looking for click to set b. */
+        STATUS_Close            /**< enum value, finalizing and saving ellipse. */
+    };
+    /// Construction methods, describes the method used to construct the ellipse
+    enum ConstructionMethod {
+        CENTER_PERIAPSIS_B,     /**< enum value, click on center, then periapsis, then b point. */
+        PERIAPSIS_APOAPSIS_B    /**< enum value, click on periapsis, then apoapsis, then b point. */
     };
 
+    /**
+     * @brief Slot called when the create ellipse command is activated
+     * @param sketchgui A pointer to the active sketch
+     */
     virtual void activated(ViewProviderSketch *sketchgui)
     {
         setCursor(QPixmap(cursor_createellipse),7,7);
+        if (constrMethod == 0) {
+            method = CENTER_PERIAPSIS_B;
+            mode = STATUS_SEEK_CENTROID;
+        } else {
+            method = PERIAPSIS_APOAPSIS_B;
+            mode = STATUS_SEEK_PERIAPSIS;
+        }
     }
 
+
+    /**
+     * @brief Updates the ellipse when the cursor moves
+     * @param onSketchPos the position of the cursor on the sketch
+     */
     virtual void mouseMove(Base::Vector2D onSketchPos)
     {
-        if (Mode==STATUS_SEEK_First) {
-            setPositionText(onSketchPos);
-            if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2D(0.f,0.f))) { // TODO: ellipse prio 1
-                renderSuggestConstraintsCursor(sugConstr1);
-                return;
-            }
-        }
-        else if (Mode==STATUS_SEEK_Second) {
-            double rx0 = onSketchPos.fX - EditCurve[0].fX;
-            double ry0 = onSketchPos.fY - EditCurve[0].fY;
-            for (int i=0; i < 16; i++) {
-                double angle = i*M_PI/16.0;
-                double rx = rx0 * cos(angle) + ry0 * sin(angle);
-                double ry = -rx0 * sin(angle) + ry0 * cos(angle);
-                EditCurve[1+i] = Base::Vector2D(EditCurve[0].fX + rx, EditCurve[0].fY + ry);
-                EditCurve[17+i] = Base::Vector2D(EditCurve[0].fX - rx, EditCurve[0].fY - ry);
-            }
-            EditCurve[33] = EditCurve[1];
+        if (method == PERIAPSIS_APOAPSIS_B) {
+            if (mode == STATUS_SEEK_PERIAPSIS) {
+                setPositionText(onSketchPos);
+                if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2D(0.f,0.f))) { // TODO: ellipse prio 1
+                    renderSuggestConstraintsCursor(sugConstr1);
+                    return;
+                }
+            } else if (mode == STATUS_SEEK_APOAPSIS) {
+                solveEllipse(onSketchPos);
+                approximateEllipse();
 
-            // Display radius for user
-            float radius = (onSketchPos - EditCurve[0]).Length();
+                // Display radius for user
+                float semiMajorRadius = a * 2;
+                SbString text;
+                text.sprintf(" (%.1fR,%.1fR)", semiMajorRadius,semiMajorRadius);
+                setPositionText(onSketchPos, text);
 
-            SbString text;
-            text.sprintf(" (%.1fR,%.1fR)", radius,radius);
-            setPositionText(onSketchPos, text);
+                sketchgui->drawEdit(editCurve);
+                if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2D(0.f,0.f),
+                                       AutoConstraint::CURVE)) {
+                    renderSuggestConstraintsCursor(sugConstr2);
+                    return;
+                }
+            } else if (mode == STATUS_SEEK_B) {
+                solveEllipse(onSketchPos);
+                approximateEllipse();
 
-            sketchgui->drawEdit(EditCurve);
-            if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2D(0.f,0.f),
-                                   AutoConstraint::CURVE)) {
-                renderSuggestConstraintsCursor(sugConstr2);
-                return;
-            }
-        }
-        else if (Mode==STATUS_SEEK_Third) {                        
-            // angle between the major axis of the ellipse and the X axis
-            double a = (EditCurve[1]-EditCurve[0]).Length();
-            double phi = atan2(EditCurve[1].fY-EditCurve[0].fY,EditCurve[1].fX-EditCurve[0].fX);
-            
-            // This is the angle at cursor point
-            double angleatpoint = acos((onSketchPos.fX-EditCurve[0].fX+(onSketchPos.fY-EditCurve[0].fY)*tan(phi))/(a*(cos(phi)+tan(phi)*sin(phi))));
-            double b=(onSketchPos.fY-EditCurve[0].fY-a*cos(angleatpoint)*sin(phi))/(sin(angleatpoint)*cos(phi));
-                        
-            for (int i=1; i < 16; i++) {
-                double angle = i*M_PI/16.0;
-                double rx = a * cos(angle) * cos(phi) - b * sin(angle) * sin(phi); 
-                double ry = a * cos(angle) * sin(phi) + b * sin(angle) * cos(phi);
-                EditCurve[1+i] = Base::Vector2D(EditCurve[0].fX + rx, EditCurve[0].fY + ry);
-                EditCurve[17+i] = Base::Vector2D(EditCurve[0].fX - rx, EditCurve[0].fY - ry);
+                // Display radius for user
+                SbString text;
+                text.sprintf(" (%.1fR,%.1fR)", a, b);
+                setPositionText(onSketchPos, text);
+
+                sketchgui->drawEdit(editCurve);
+                if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2D(0.f,0.f),
+                                       AutoConstraint::CURVE)) {
+                    renderSuggestConstraintsCursor(sugConstr2);
+                    return;
+                }
             }
-            EditCurve[33] = EditCurve[1];
-            EditCurve[17] = EditCurve[16];
+        } else { // method is CENTER_PERIAPSIS_B
+            if (mode == STATUS_SEEK_CENTROID) {
+                setPositionText(onSketchPos);
+                if (seekAutoConstraint(sugConstr1, onSketchPos, Base::Vector2D(0.f,0.f))) { // TODO: ellipse prio 1
+                    renderSuggestConstraintsCursor(sugConstr1);
+                    return;
+                }
+            } else if (mode == STATUS_SEEK_PERIAPSIS) {
+                solveEllipse(onSketchPos);
+                approximateEllipse();
 
-            // Display radius for user
-            SbString text;
-            text.sprintf(" (%.1fR,%.1fR)", a, b);
-            setPositionText(onSketchPos, text);
+                // Display radius for user
+                float semiMajorRadius = a * 2;
+                SbString text;
+                text.sprintf(" (%.1fR,%.1fR)", semiMajorRadius,semiMajorRadius);
+                setPositionText(onSketchPos, text);
 
-            sketchgui->drawEdit(EditCurve);
-            if (seekAutoConstraint(sugConstr3, onSketchPos, Base::Vector2D(0.f,0.f),
-                                   AutoConstraint::CURVE)) {
-                renderSuggestConstraintsCursor(sugConstr3);
-                return;
+                sketchgui->drawEdit(editCurve);
+                if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2D(0.f,0.f),
+                                       AutoConstraint::CURVE)) {
+                    renderSuggestConstraintsCursor(sugConstr2);
+                    return;
+                }
+            } else if ((mode == STATUS_SEEK_A) || (mode == STATUS_SEEK_B)) {
+                solveEllipse(onSketchPos);
+                approximateEllipse();
+
+                // Display radius for user
+                SbString text;
+                text.sprintf(" (%.1fR,%.1fR)", a, b);
+                setPositionText(onSketchPos, text);
+
+                sketchgui->drawEdit(editCurve);
+                if (seekAutoConstraint(sugConstr2, onSketchPos, Base::Vector2D(0.f,0.f),
+                                       AutoConstraint::CURVE)) {
+                    renderSuggestConstraintsCursor(sugConstr2);
+                    return;
+                }
             }
         }
         applyCursor();
     }
 
+    /**
+     * @brief Changes drawing mode on user-click
+     * @param onSketchPos the position of the cursor on the sketch
+     * @return
+     */
     virtual bool pressButton(Base::Vector2D onSketchPos)
     {
-        if (Mode==STATUS_SEEK_First){
-            EditCurve[0] = onSketchPos;
-            Mode = STATUS_SEEK_Second;
-        } 
-        else if(Mode==STATUS_SEEK_Second) {
-            EditCurve[1] = onSketchPos;
-            Mode = STATUS_SEEK_Third;
-        } 
-        else {
-            EditCurve[2] = onSketchPos;
-            Mode = STATUS_Close;
+        if (method == PERIAPSIS_APOAPSIS_B) {
+            if (mode == STATUS_SEEK_PERIAPSIS) {
+                periapsis = onSketchPos;
+                mode = STATUS_SEEK_APOAPSIS;
+            }
+            else if (mode == STATUS_SEEK_APOAPSIS) {
+                apoapsis = onSketchPos;
+                mode = STATUS_SEEK_B;
+            }
+            else {
+                mode = STATUS_Close;
+            }
+        } else { // method is CENTER_PERIAPSIS_B
+            if (mode == STATUS_SEEK_CENTROID) {
+                centroid = onSketchPos;
+                mode = STATUS_SEEK_PERIAPSIS;
+            }
+            else if (mode == STATUS_SEEK_PERIAPSIS) {
+                periapsis = onSketchPos;
+                mode = STATUS_SEEK_B;
+            }
+            else {
+                mode = STATUS_Close;
+            }
         }
         return true;
     }
 
+    /**
+     * @brief Calls \c saveEllipse() after last user input
+     * @param onSketchPos the position of the cursor on the sketch
+     * @return
+     */
     virtual bool releaseButton(Base::Vector2D onSketchPos)
     {
-        if (Mode==STATUS_Close) {
-            unsetCursor();
-            resetPositionText();
-            
-            // angle between the major axis of the ellipse and the X axis
-            double a = (EditCurve[1]-EditCurve[0]).Length();
-            double phi = atan2(EditCurve[1].fY-EditCurve[0].fY,EditCurve[1].fX-EditCurve[0].fX);
-            
-            // This is the angle at cursor point
-            double angleatpoint = acos((EditCurve[2].fX-EditCurve[0].fX+(EditCurve[2].fY-EditCurve[0].fY)*tan(phi))/(a*(cos(phi)+tan(phi)*sin(phi))));
-            double b=abs((EditCurve[2].fY-EditCurve[0].fY-a*cos(angleatpoint)*sin(phi))/(sin(angleatpoint)*cos(phi)));
-                        
-            Base::Vector2D majAxisDir,minAxisDir,minAxisPoint,majAxisPoint;
-            // We always create a CCW ellipse, because we want our XY reference system to be in the +X +Y direction
-            // Our normal will then always be in the +Z axis (local +Z axis of the sketcher)
-            
-            if(a>b)
-            {
-                // force second semidiameter to be perpendicular to first semidiamater
-                majAxisDir = EditCurve[1] - EditCurve[0];
-                Base::Vector2D perp(-majAxisDir.fY,majAxisDir.fX);
-                perp.Normalize();
-                perp.Scale(abs(b));
-                minAxisPoint = EditCurve[0]+perp;
-                majAxisPoint = EditCurve[0]+majAxisDir;
+        if (mode == STATUS_Close) {
+            saveEllipse();
+        }
+        return true;
+    }
+protected:
+    std::vector<AutoConstraint> sugConstr1, sugConstr2;
+private:
+    SelectMode mode;
+    /// the method of constructing the ellipse
+    ConstructionMethod method;
+    int constrMethod;
+    /// periapsis position vector, in standard position in sketch coordinate system
+    Base::Vector2D periapsis;
+    /// apoapsis position vector, in standard position in sketch coordinate system
+    Base::Vector2D apoapsis;
+    /// centroid position vector, in standard position in sketch coordinate system
+    Base::Vector2D centroid;
+    /**
+     * @brief position vector of positive b point, in standard position in sketch coordinate system
+     * I.E. in polar perifocal system, the first intersection of the semiminor axis with the ellipse
+     * as theta increases from 0. This always happens when:
+     *    \f{eqnarray*}{
+     *        \theta_b &=& \left[\pi - \arctan\left(\frac{b}{ae}\right)\right]  \pm N 2\pi
+     *   \f}
+     *
+     * In a rotated R^3 cartesian system, centered at the centroid, +X towards periapsis, and
+     * +Z coming out of the sketch, this b position is in the +Y direction from the centroid.
+     */
+    Base::Vector2D positiveB;
+    /// cart. position vector for primary focus
+    Base::Vector2D f;
+    /// cart. position vector for other focus
+    Base::Vector2D fPrime;
+    /// Unit vector for apse line
+    Base::Vector2D apseHat;
+    /// length of semimajor axis, i.e. 'radius' colloquially
+    double a;
+    /// length of semiminor axis, i.e. 'radius' colloquially
+    double b;
+    /// eccentricity [unitless]
+    double e;
+    /// optimization, holds a term that helps calculate b in terms of a and e
+    double ratio;
+    /// holds product of a * e
+    double ae;
+    /// holds numerator of orbit equation of form a(1-e^2)
+    double num;
+    /// holds a radial distance from f to the ellipse for a given theta
+    double r;
+    /// angle of a point in a perifocal frame centered at f
+    double theta;
+    /// angle of apse line relative to sketch coordinate system
+    double phi;
+    /// holds a position vector for a point on the ellipse from f
+    Base::Vector2D pos;
+    /// holds a position vector for a point on the ellipse from fPrime
+    Base::Vector2D posPrime;
+    /// holds position vectors for a points on the ellipse
+    std::vector<Base::Vector2D> editCurve;
+    /// local i_hat vector for ellipse, from centroid to periapsis
+    Base::Vector3d iPrime;
+    /// local j_hat vector for ellipse, from centroid to b point
+    Base::Vector3d jPrime;
+    /// length (radius) of the fixed axis
+    double fixedAxisLength;
+    /// position vector of fixed axis point in sketch coordinates
+    Base::Vector2D fixedAxis;
+
+    /**
+     * @brief Computes a vector of 2D points representing an ellipse
+     * @param onSketchPos Current position of the cursor on the sketch
+     */
+    void solveEllipse(Base::Vector2D onSketchPos)
+    {
+        const double GOLDEN_RATIO = 1.6180339887;
+        Base::Vector3d k(0,0,1);
+
+        if (method == PERIAPSIS_APOAPSIS_B) {
+            if (mode == STATUS_SEEK_APOAPSIS) {
+                apoapsis = onSketchPos;
             }
-            else {
-                // force second semidiameter to be perpendicular to first semidiamater
-                minAxisDir = EditCurve[1] - EditCurve[0];
-                Base::Vector2D perp(minAxisDir.fY,-minAxisDir.fX);
-                perp.Normalize();
-                perp.Scale(abs(b));
-                majAxisPoint = EditCurve[0]+perp; 
-                minAxisPoint = EditCurve[0]+minAxisDir;
+            a = (apoapsis - periapsis).Length() / 2;
+            apseHat = (periapsis - apoapsis);
+            apseHat.Normalize();
+            centroid = apseHat;
+            centroid.Scale(-1 * a);
+            centroid = periapsis + centroid;
+            if (mode == STATUS_SEEK_APOAPSIS) {
+                // for first step, we draw an ellipse inscribed in a golden rectangle
+                ratio = 1 / GOLDEN_RATIO;   // ~= 0.6180339887
+                e = sqrt(ratio);            // ~= 0.7861513777
+                b = a * ratio;
             }
-            
-            Gui::Command::openCommand("Add sketch ellipse");
-            Gui::Command::doCommand(Gui::Command::Doc,
-                "App.ActiveDocument.%s.addGeometry(Part.Ellipse"
-                "(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(%f,%f,0)))",
-                    sketchgui->getObject()->getNameInDocument(),
-                    majAxisPoint.fX, majAxisPoint.fY,                                    
-                    minAxisPoint.fX, minAxisPoint.fY,
-                    EditCurve[0].fX, EditCurve[0].fY);
+            else if (mode == STATUS_SEEK_B) {
+                // Get the closest distance from onSketchPos to apse line, as a 'requested' value for b
+                Base::Vector2D cursor = Base::Vector2D(onSketchPos - f); // vector from f to cursor pos
+                // decompose cursor with a projection, then length of w_2 will give us b
+                Base::Vector2D w_1 = cursor;
+                w_1.ProjToLine(cursor, (periapsis - apoapsis)); // projection of cursor line onto apse line
+                Base::Vector2D w_2 = (cursor - w_1);
+                b = w_2.Length();
+
+                // limit us to ellipse or circles
+                if (b > a) {
+                    b = a;
+                }
 
-            Gui::Command::commitCommand();
-            Gui::Command::updateActive();
-            
-            // add auto constraints for the center point
-            if (sugConstr1.size() > 0) {
-                createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::mid);
-                sugConstr1.clear();
+                e = sqrt(1 - ((b * b) / (a * a)));
+                ratio = sqrt(1 - (e*e));
             }
+            ae = a * e;
+            f = apseHat;
+            f.Scale(ae);
+            f = centroid + f;
+            fPrime = apseHat;
+            fPrime.Scale(-1 * ae);
+            fPrime = centroid + fPrime;
+            phi = atan2(apseHat.fY, apseHat.fX);
+            num = a * (1 - (e * e));
+            // The ellipse is now solved
+        } else { // method == CENTER_PERIAPSIS_B
+            if (mode == STATUS_SEEK_PERIAPSIS) {
+                // solve the ellipse inscribed in a golden rectangle
+                periapsis = onSketchPos;
+                a = (centroid - periapsis).Length();
+                iPrime.x = periapsis.fX - centroid.fX;
+                iPrime.y = periapsis.fY - centroid.fY;
+                iPrime.z = 0;
+                jPrime = k % iPrime;   // j = k cross i
+
+                // these are constant for any ellipse inscribed in a golden rectangle
+                ratio = 1 / GOLDEN_RATIO;   // ~= 0.6180339887
+                e = sqrt(ratio);            // ~= 0.7861513777
+
+                b = a * ratio;
+                ae = a * e;
+                apseHat = (periapsis - centroid);
+                apseHat.Normalize();
+                f = apseHat;
+                f.Scale(ae);
+                f = centroid + f;
+                fPrime = apseHat;
+                fPrime.Scale(-1 * ae);
+                fPrime = centroid + fPrime;
+                apoapsis = apseHat;
+                apoapsis.Scale(-1 * a);
+                apoapsis = centroid + apoapsis;
+                phi = atan2(apseHat.fY, apseHat.fX);
+                num = a * (1 - (e * e));
+                fixedAxisLength = a;
+                fixedAxis = periapsis;
+            } else if ((mode == STATUS_SEEK_B) || (mode == STATUS_SEEK_A)) {
+                // while looking for the last click, we may switch back and forth
+                // between looking for a b point and looking for periapsis, so ensure
+                // we are in the right mode
+                Base::Vector2D cursor = Base::Vector2D(onSketchPos - centroid); // vector from centroid to cursor pos
+                // decompose cursor with a projection, then length of w_2 will give us b
+                Base::Vector2D w_1 = cursor;
+                w_1.ProjToLine(cursor, (fixedAxis - centroid)); // projection of cursor line onto fixed axis line
+                Base::Vector2D w_2 = (cursor - w_1);
+                if (w_2.Length() > fixedAxisLength) {
+                    // b is fixed, we are seeking a
+                    mode = STATUS_SEEK_A;
+                    jPrime.x = (fixedAxis - centroid).fX;
+                    jPrime.y = (fixedAxis - centroid).fY;
+                    jPrime.Normalize();
+                    iPrime = jPrime % k;    // cross
+                    b = fixedAxisLength;
+                    a = w_2.Length();
+                } else {
+                    // a is fixed, we are seeking b
+                    mode = STATUS_SEEK_B;
+                    iPrime.x = (fixedAxis - centroid).fX;
+                    iPrime.y = (fixedAxis - centroid).fY;
+                    iPrime.Normalize();
+                    jPrime = k % iPrime;    // cross
+                    a = fixedAxisLength;
+                    b = w_2.Length();
+                }
+                // now finish solving the ellipse
+                periapsis.fX = centroid.fX + (iPrime * a).x;
+                periapsis.fY = centroid.fY + (iPrime * a).y;
+                e = sqrt(1 - ((b * b) / (a * a)));
+                ratio = sqrt(1 - (e*e));
+                ae = a * e;
+                apseHat = (periapsis - centroid);
+                apseHat.Normalize();
+                f = apseHat;
+                f.Scale(ae);
+                f = centroid + f;
+                fPrime = apseHat;
+                fPrime.Scale(-1 * ae);
+                fPrime = centroid + fPrime;
+                apoapsis = apseHat;
+                apoapsis.Scale(-1 * a);
+                apoapsis = centroid + apoapsis;
+                phi = atan2(apseHat.fY, apseHat.fX);
+                num = a * (1 - (e * e));
+            }
+        }
+    }
 
-            // add suggested constraints for circumference
-            if (sugConstr2.size() > 0) {
-                //createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::none);
-                sugConstr2.clear();
+
+    /**
+     * @brief Computes a sequence of 2D vectors to approximate the ellipse
+     */
+    void approximateEllipse()
+    {
+        // We will approximate the ellipse as a sequence of connected chords
+        // Number of points per quadrant of the ellipse
+        double n = (editCurve.size() - 1) / 4;
+
+        // We choose points in the perifocal frame then translate them to sketch cartesian.
+        // This gives us a better approximation of an ellipse, i.e. more points where the
+        // curvature is higher.  If the eccentricity is high, we shift the points a bit towards
+        // the semi-minor axis.
+        double partitionAngle = (M_PI - atan2(b, ae)) / n;
+        double radianShift = 0;
+        if (e > 0.8) {radianShift = (partitionAngle / 5) * 4;}
+        for (int i=0; i < n; i++) {
+            theta = i * partitionAngle;
+            if (i > 0) {theta = theta + radianShift;}
+            r = num / (1 + (e * cos(theta)));
+            // r(pi/2) is semi-latus rectum, if we need it
+            pos.fX = r*cos(theta+phi);  // phi rotates, sin/cos translate
+            pos.fY = r*sin(theta+phi);
+            pos = pos + f;
+            posPrime.fX = r*cos(theta+phi+M_PI);
+            posPrime.fY = r*sin(theta+phi+M_PI);
+            posPrime = posPrime + fPrime;
+            // over the loop, loads Quadrant I points, by using f as origin
+            editCurve[i] = pos;
+            // over the loop, loads Quadrant III points, by using fPrime as origin
+            editCurve[(2*n) + i] = posPrime;
+            // load points with negative theta angles (i.e. cw)
+            if (i>0) {
+                pos.fX = r*cos(-1*theta+phi);
+                pos.fY = r*sin(-1*theta+phi);
+                pos = pos + f;
+                // loads Quadrant IV points
+                editCurve[(4*n) - i] = pos;
+                posPrime.fX = r*cos(-1*theta+phi+M_PI);
+                posPrime.fY = r*sin(-1*theta+phi+M_PI);
+                posPrime = posPrime + fPrime;
+                // loads Quadrant II points
+                editCurve[(2*n) - i] = posPrime;
             }
+        }
+        // load pos & neg b points
+        theta = M_PI - atan2(b, ae);        // the angle from f to the positive b point
+        r = num / (1 + (e * cos(theta)));
+        pos.fX = r*cos(theta+phi);
+        pos.fY = r*sin(theta+phi);
+        pos = pos + f;
+        editCurve[n] = pos; // positive
+        pos.fX = r*cos(-1*theta+phi);
+        pos.fY = r*sin(-1*theta+phi);
+        pos = pos + f;
+        editCurve[(3*n)] = pos; // negative
+        // force the curve to be a closed shape
+        editCurve[(4*n)] = editCurve[0];
+    }
 
-            EditCurve.clear();
-            sketchgui->drawEdit(EditCurve);
-            sketchgui->purgeHandler(); // no code after this line, Handler get deleted in ViewProvider
+    /**
+     * @brief Prints the ellipse data to STDOUT as an GNU Octave script
+     * @param onSketchPos position of the cursor on the sketch
+     */
+    void ellipseToOctave(Base::Vector2D onSketchPos)
+    {
+        double n = (editCurve.size() - 1) / 4;
+
+        // send a GNU Octave script to stdout to plot points for debugging
+        std::ostringstream octave;
+        octave << std::fixed << std::setprecision(12);
+        octave << "\nclear all;\nclose all;\nclc;\n\n";
+        octave << "periapsis = [" << periapsis.fX << ", " << periapsis.fY << "];\n";
+        octave << "apoapsis = [" << apoapsis.fX << ", " << apoapsis.fY << "];\n";
+        octave << "positiveB = [" << editCurve[n].fX << ", " << editCurve[n].fY << "];\n";
+        octave << "apseHat = [" << apseHat.fX << ", " << apseHat.fY << "];\n";
+        octave << "a = " << a << ";\n";
+        octave << "b = " << b << ";\n";
+        octave << "eccentricity = " << e << ";\n";
+        octave << "centroid = [" << centroid.fX << ", " << centroid.fY << "];\n";
+        octave << "f = [" << f.fX << ", " << f.fY << "];\n";
+        octave << "fPrime = [" << fPrime.fX << ", " << fPrime.fY << "];\n";
+        octave << "phi = " << phi << ";\n\n";
+        octave << "x = [";
+        for (int i=0; i < 4*n + 1; i++) {
+            octave << editCurve[i].fX;
+            if (i < 4*n) {
+                octave << ", ";
+            }
         }
-        return true;
+        octave << "];\n";
+        octave << "y = [";
+        for (int i=0; i < 4*n + 1; i++) {
+            octave << editCurve[i].fY;
+            if (i < 4*n) {
+                octave << ", ";
+            }
+        }
+        octave << "];\n\n";
+        octave << "% Draw ellipse points in red;\n";
+        octave << "plot (x, y, \"r.\", \"markersize\", 5);\n";
+        octave << "axis ([-300, 300, -300, 300], \"square\");grid on;\n";
+        octave << "hold on;\n\n";
+        octave << "% Draw centroid in blue, f in cyan, and fPrime in magenta;\n";
+        octave << "plot(centroid(1), centroid(2), \"b.\", \"markersize\", 5);\n";
+        octave << "plot(f(1), f(2), \"c.\", \"markersize\", 5);\n";
+        octave << "plot(fPrime(1), fPrime(2), \"m.\", \"markersize\", 5);\n";
+
+        octave << "n = [periapsis(1) - f(1), periapsis(2) - f(2)];\n";
+        octave << "h = quiver(f(1),f(2),n(1),n(2), 0);\n";
+        octave << "set (h, \"maxheadsize\", 0.1);\n\n";
+        octave << "% Draw the three position vectors used for Gui::Command::doCommand(...)\n";
+        octave << "periapsisVec = quiver(0,0,periapsis(1),periapsis(2), 0);\n";
+        octave << "set (periapsisVec, \"maxheadsize\", 0.01, \"color\", \"black\");\n";
+        octave << "centroidVec = quiver(0,0,centroid(1),centroid(2), 0);\n";
+        octave << "set (centroidVec, \"maxheadsize\", 0.01, \"color\", \"black\");\n";
+        octave << "bVec = quiver(0,0,positiveB(1),positiveB(2), 0);\n";
+        octave << "set (bVec, \"maxheadsize\", 0.01, \"color\", \"black\");\n\n";
+        octave << "% Draw the local x & y basis vectors, scaled to a and b, in red and blue, respectively\n";
+        octave << "xLocalVec = quiver(centroid(1),centroid(2),periapsis(1)-centroid(1),periapsis(2)-centroid(2), 0);\n";
+        octave << "set (xLocalVec, \"maxheadsize\", 0.01, \"color\", \"red\");\n";
+        octave << "yLocalVec = quiver(centroid(1),centroid(2), positiveB(1)-centroid(1), positiveB(2)-centroid(2), 0);\n";
+        octave << "set (yLocalVec, \"maxheadsize\", 0.01, \"color\", \"blue\");\nhold off;\n";
+        qDebug() << QString::fromStdString(octave.str());
     }
-protected:
-    SelectMode Mode;
-    std::vector<Base::Vector2D> EditCurve;
-    std::vector<AutoConstraint> sugConstr1, sugConstr2, sugConstr3;
 
+    /**
+     * @brief Finalizes and saves the drawn ellipse
+     * @return nothing
+     */
+    void saveEllipse()
+    {
+        unsetCursor();
+        resetPositionText();
+
+        /* There are a couple of issues with Gui::Command::doCommand(...) and
+         * GC_MakeEllipse(...) that cause bugs if not handled properly, even
+         * when we give them a mathematically-correct ellipse.
+         *
+         * GC_MakeEllipse may fail with a gce_InvertAxis error for a small
+         * circular ellipse when floating point roundoff or representation
+         * errors make the b axis slightly larger than the a axis.
+         *
+         * A similar, larger, issue arises in Gui::Command::doCommand(...) because
+         * we cast our double vector components into strings with a fixed
+         * precision of six, and then create new doubles from the strings
+         * in EllipsePy::PyInit(...).  Thus, by the time we call GC_MakeEllipse(...)
+         * in EllipsePy::PyInit(...), our ellipse may not be valid anymore
+         * because b is now greater than a.
+         *
+         * To handle these issues, we simulate the effects Gui::Command::doCommand(...)
+         * has on our ellipse, and we adjust our ellipse parameters until
+         * GC_MakeEllipse successfully creates an ellipse with our mangled
+         * parameters.
+         *
+         * In almost all cases, we only have to make our test ellipse one time;
+         * it is only in the rare edge cases that require repeated test ellipses
+         * until we get a valid one, or fail due to excessive attempts. With a
+         * limit of 25 attempts, I have been unable to make it fail.
+         */
+
+        // simulate loss of precision in centroid and periapsis
+        char cx[64];
+        char cy[64];
+        char px[64];
+        char py[64];
+        sprintf(cx, "%.6lf\n", centroid.fX);
+        sprintf(cy, "%.6lf\n", centroid.fY);
+        sprintf(px, "%.6lf\n", periapsis.fX);
+        sprintf(py, "%.6lf\n", periapsis.fY);
+        centroid.fX = atof(cx);
+        centroid.fY = atof(cy);
+        periapsis.fX = atof(px);
+        periapsis.fY = atof(py);
+
+        /* GC_MakeEllipse requires a right-handed coordinate system, with +X
+         * from centroid to periapsis, +Z out of the page.
+         */
+        Base::Vector3d k(0,0,1);
+        Base::Vector3d i(periapsis.fX - centroid.fX, periapsis.fY - centroid.fY, 0);
+        Base::Vector3d j = k % i;   // j = k cross i
+        double beta = 1e-7;
+        int count = 0;
+        int limit = 25;             // no infinite loops!
+        bool success = false;
+
+        // adjust b until our mangled vectors produce a good ellispe
+        do {
+            j = j.Normalize() * (b - double(count * beta));
+            positiveB.fX = centroid.fX + j.x;
+            positiveB.fY = centroid.fY + j.y;
+            char bx[64];
+            char by[64];
+            sprintf(bx, "%.6lf\n", positiveB.fX);
+            sprintf(by, "%.6lf\n", positiveB.fY);
+            positiveB.fX = atof(bx);
+            positiveB.fY = atof(by);
+            GC_MakeEllipse me(gp_Pnt(periapsis.fX,periapsis.fY,0),
+                              gp_Pnt(positiveB.fX,positiveB.fY,0),
+                              gp_Pnt(centroid.fX,centroid.fY,0));
+            count++;
+            success = me.IsDone();
+        } while (!success && (count <= limit));
+        if (!success) {
+            qDebug() << "Failed to create a valid mangled ellipse after" << count << "attempts";
+        }
+
+        Gui::Command::openCommand("Add sketch ellipse");
+        Gui::Command::doCommand(Gui::Command::Doc,
+            "App.ActiveDocument.%s.addGeometry(Part.Ellipse"
+            "(App.Vector(%f,%f,0),App.Vector(%f,%f,0),App.Vector(%f,%f,0)))",
+                sketchgui->getObject()->getNameInDocument(),
+                periapsis.fX, periapsis.fY,
+                positiveB.fX, positiveB.fY,
+                centroid.fX, centroid.fY);
+
+        Gui::Command::commitCommand();
+        Gui::Command::updateActive();
+
+        // add auto constraints for the center point
+        if (sugConstr1.size() > 0) {
+            createAutoConstraints(sugConstr1, getHighestCurveIndex(), Sketcher::mid);
+            sugConstr1.clear();
+        }
+
+        // add suggested constraints for circumference
+        if (sugConstr2.size() > 0) {
+            //createAutoConstraints(sugConstr2, getHighestCurveIndex(), Sketcher::none);
+            sugConstr2.clear();
+        }
+
+        // delete the temp construction curve from the sketch
+        editCurve.clear();
+        sketchgui->drawEdit(editCurve);
+        sketchgui->purgeHandler(); // no code after this line, Handler gets deleted in ViewProvider
+    }
 };
 
-DEF_STD_CMD_A(CmdSketcherCreateEllipse);
+/// @brief Macro that declares a new sketcher command class 'CmdSketcherCreateEllipseByCenter'
+DEF_STD_CMD_A(CmdSketcherCreateEllipseByCenter);
 
-CmdSketcherCreateEllipse::CmdSketcherCreateEllipse()
-  : Command("Sketcher_CreateEllipse")
+/**
+ * @brief ctor
+ */
+CmdSketcherCreateEllipseByCenter::CmdSketcherCreateEllipseByCenter()
+  : Command("Sketcher_CreateEllipseByCenter")
 {
     sAppModule      = "Sketcher";
     sGroup          = QT_TR_NOOP("Sketcher");
-    sMenuText       = QT_TR_NOOP("Create ellipse");
-    sToolTipText    = QT_TR_NOOP("Create an ellipse in the sketch");
+    sMenuText       = QT_TR_NOOP("Create ellipse by center");
+    sToolTipText    = QT_TR_NOOP("Create an ellipse by center in the sketch");
     sWhatsThis      = sToolTipText;
     sStatusTip      = sToolTipText;
     sPixmap         = "Sketcher_CreateEllipse";
     eType           = ForEdit;
 }
 
-void CmdSketcherCreateEllipse::activated(int iMsg)
+void CmdSketcherCreateEllipseByCenter::activated(int iMsg)
 {
-    ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerEllipse() );
+    ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerEllipse(0) );
 }
 
-bool CmdSketcherCreateEllipse::isActive(void)
+bool CmdSketcherCreateEllipseByCenter::isActive(void)
 {
     return isCreateGeoActive(getActiveGuiDocument());
 }
 
+/// @brief Macro that declares a new sketcher command class 'CmdSketcherCreateEllipseBy3Points'
+DEF_STD_CMD_A(CmdSketcherCreateEllipseBy3Points);
+
+/**
+ * @brief ctor
+ */
+CmdSketcherCreateEllipseBy3Points::CmdSketcherCreateEllipseBy3Points()
+  : Command("Sketcher_CreateEllipseBy3Points")
+{
+    sAppModule      = "Sketcher";
+    sGroup          = QT_TR_NOOP("Sketcher");
+    sMenuText       = QT_TR_NOOP("Create ellipse by 3 points");
+    sToolTipText    = QT_TR_NOOP("Create an ellipse by 3 points in the sketch");
+    sWhatsThis      = sToolTipText;
+    sStatusTip      = sToolTipText;
+    sPixmap         = "Sketcher_CreateEllipse"; /// @todo need ellipse icon with periapsis, apoapsis, and b point
+    eType           = ForEdit;
+}
+
+void CmdSketcherCreateEllipseBy3Points::activated(int iMsg)
+{
+    ActivateHandler(getActiveGuiDocument(),new DrawSketchHandlerEllipse(1) );
+}
+
+bool CmdSketcherCreateEllipseBy3Points::isActive(void)
+{
+    return isCreateGeoActive(getActiveGuiDocument());
+}
+
+/// @brief Macro that declares a new sketcher command class 'CmdSketcherCompCreateEllipse'
+DEF_STD_CMD_ACL(CmdSketcherCompCreateEllipse);
+
+/**
+ * @brief ctor
+ */
+CmdSketcherCompCreateEllipse::CmdSketcherCompCreateEllipse()
+  : Command("Sketcher_CompCreateEllipse")
+{
+    sAppModule      = "Sketcher";
+    sGroup          = QT_TR_NOOP("Sketcher");
+    sMenuText       = QT_TR_NOOP("Create ellipse");
+    sToolTipText    = QT_TR_NOOP("Create an ellipse in the sketch");
+    sWhatsThis      = sToolTipText;
+    sStatusTip      = sToolTipText;
+    eType           = ForEdit;
+}
+
+/**
+ * @brief Instantiates the ellipse handler when the ellipse command activated
+ * @param int iMsg
+ */
+void CmdSketcherCompCreateEllipse::activated(int iMsg)
+{
+    if (iMsg == 0) {
+        ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerEllipse(iMsg));
+    } else if (iMsg == 1) {
+        ActivateHandler(getActiveGuiDocument(), new DrawSketchHandlerEllipse(iMsg));
+    } else {
+        return;
+    }
+
+    // Since the default icon is reset when enabing/disabling the command we have
+    // to explicitly set the icon of the used command.
+    Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
+    QList<QAction*> a = pcAction->actions();
+
+    assert(iMsg < a.size());
+    pcAction->setIcon(a[iMsg]->icon());
+}
+
+Gui::Action * CmdSketcherCompCreateEllipse::createAction(void)
+{
+    Gui::ActionGroup* pcAction = new Gui::ActionGroup(this, Gui::getMainWindow());
+    pcAction->setDropDownMenu(true);
+    applyCommandData(this->className(), pcAction);
+
+    QAction* ellipseByCenter = pcAction->addAction(QString());
+    ellipseByCenter->setIcon(Gui::BitmapFactory().pixmapFromSvg("Sketcher_CreateEllipse", QSize(24,24)));
+     /// @todo replace with correct icon
+    QAction* ellipseBy3Points = pcAction->addAction(QString());
+    ellipseBy3Points->setIcon(Gui::BitmapFactory().pixmapFromSvg("Sketcher_CreateEllipse", QSize(24,24)));
+
+    _pcAction = pcAction;
+    languageChange();
+
+    // set ellipse by center, a, b as default method
+    pcAction->setIcon(ellipseByCenter->icon());
+    int defaultId = 0;
+    pcAction->setProperty("defaultAction", QVariant(defaultId));
+
+    return pcAction;
+}
+
+void CmdSketcherCompCreateEllipse::languageChange()
+{
+    Command::languageChange();
+
+    if (!_pcAction)
+        return;
+    Gui::ActionGroup* pcAction = qobject_cast<Gui::ActionGroup*>(_pcAction);
+    QList<QAction*> a = pcAction->actions();
+
+    QAction* ellipseByCenter = a[0];
+    ellipseByCenter->setText(QApplication::translate("CmdSketcherCompCreateEllipse","Center and radii"));
+    ellipseByCenter->setToolTip(QApplication::translate("Sketcher_CreateEllipse","Create an ellipse by its center and two radii"));
+    ellipseByCenter->setStatusTip(QApplication::translate("Sketcher_CreateEllipse","Create an ellipse by its center and two radii"));
+    QAction* ellipseBy3Points = a[1];
+    ellipseBy3Points->setText(QApplication::translate("CmdSketcherCompCreateEllipse","Periapsis, apoapsis, minor radius"));
+    ellipseBy3Points->setToolTip(QApplication::translate("Sketcher_CreateEllipse","Create a ellipse by periapsis, apoapsis, and minor radius"));
+    ellipseBy3Points->setStatusTip(QApplication::translate("Sketcher_CreateEllipse","Create a ellipse by periapsis, apoapsis, and minor radius"));
+}
+
+bool CmdSketcherCompCreateEllipse::isActive(void)
+{
+    return isCreateGeoActive(getActiveGuiDocument());
+}
+            
 // ======================================================================================
 
 /* XPM */
@@ -4112,7 +4711,9 @@ void CreateSketcherCommandsCreateGeo(void)
     rcCmdMgr.addCommand(new CmdSketcherCreateCircle());
     rcCmdMgr.addCommand(new CmdSketcherCreate3PointCircle());
     rcCmdMgr.addCommand(new CmdSketcherCompCreateCircle());
-    rcCmdMgr.addCommand(new CmdSketcherCreateEllipse());
+    rcCmdMgr.addCommand(new CmdSketcherCreateEllipseByCenter());
+    rcCmdMgr.addCommand(new CmdSketcherCreateEllipseBy3Points());
+    rcCmdMgr.addCommand(new CmdSketcherCompCreateEllipse());
     rcCmdMgr.addCommand(new CmdSketcherCreateArcOfEllipse());
     rcCmdMgr.addCommand(new CmdSketcherCreateLine());
     rcCmdMgr.addCommand(new CmdSketcherCreatePolyline());
diff --git a/src/Mod/Sketcher/Gui/Workbench.cpp b/src/Mod/Sketcher/Gui/Workbench.cpp
index 18e1b6f..7cb578f 100644
--- a/src/Mod/Sketcher/Gui/Workbench.cpp
+++ b/src/Mod/Sketcher/Gui/Workbench.cpp
@@ -137,14 +137,15 @@ inline void SketcherAddWorkspaceArcs<Gui::MenuItem>(Gui::MenuItem& geom){
             << "Sketcher_Create3PointArc"
             << "Sketcher_CreateCircle"
             << "Sketcher_Create3PointCircle"
-            << "Sketcher_CreateEllipse"
+            << "Sketcher_CreateEllipseByCenter"
+            << "Sketcher_CreateEllipseBy3Points"
             << "Sketcher_CreateArcOfEllipse";
 }
 template <>
 inline void SketcherAddWorkspaceArcs<Gui::ToolBarItem>(Gui::ToolBarItem& geom){
     geom    << "Sketcher_CompCreateArc"
             << "Sketcher_CompCreateCircle"
-            << "Sketcher_CreateEllipse"
+            << "Sketcher_CompCreateEllipse"
             << "Sketcher_CreateArcOfEllipse";
 }
 template <typename T>
ETA: lame_r on #freecad helped me out (thanks!).

Code: Select all

git push https://github.com/marktaff/FreeCAD_sf_master skt2_rebase_DeepSOIC_attempt
did the trick to push my code to my github repo. So you guys can look at the code there. Abdullah: is there anything else you need from me to get my code?
Last edited by marktaff on Sun Oct 19, 2014 9:51 pm, edited 2 times in total.
Available on chat.freenode.net#freecad while working on FreeCad
Post Reply