Shake sketch (poor-man freedom relevator)

Need help, or want to share a macro? Post here!
galou_breizh
Posts: 322
Joined: Wed Sep 15, 2010 9:38 am

Shake sketch (poor-man freedom relevator)

Postby galou_breizh » Thu Aug 14, 2014 7:35 pm

Hi,

I wrote a little macro that could help people visualize where the degrees of freedom are in a sketch. The principle is very simple, a random noise is added in all points and the sketch is then solved. What is free moves what is constrained does not.

I'll post it on the wiki if you find it useful and bug-free (at least with only few bugs).

I you think it's a good idea, I'll do a command out of it.

If someone is motivating to create an icon, please do it!

Gaël

FCMacro extension are not allowed as attachment so I include the macro directly:

Code: Select all

# -*- coding: utf-8 -*-

# FreeCAD macro to shake a sketch in order to discover its unconstrained parts.
#
# A Gaussian noise is introduced in all sketch points and the sketch is then
# solved.
# Beware that the sketch can look different because some constraints have
# several solutions. In this case, just undo.
#
# This file is released under the MIT License.
# Author: Gaël Ecorchard
# Version: 1.0, 2014-08, first release.

# Amplitude of the point displacements.
# The standard deviation of the Gaussian noise is the largest sketch dimension
# multiplied by this factor.
displacement_amplitude = 0.1

# End of configuration.

from random import gauss

import FreeCADGui as Gui
from FreeCAD import Base
from FreeCAD import Part

# For each sketch geometry type, map a list of points to move.
geom_points = {
    'point': [1],
    'line': [1, 2],  # first point, last point
    'circle': [0, 3],  # curve, center
    'arc': [1, 2, 3],  # first point, last point, center
}


class BoundingBox(object):
    xmin = None
    xmax = None
    ymin = None
    ymax = None

    def enlarge_x(self, x):
        if self.xmin is None:
            self.xmin = x
            self.xmax = x
            return
        if self.xmin > x:
            self.xmin = x
            return
        if self.xmax < x:
            self.xmax = x
            return

    def enlarge_y(self, y):
        if self.ymin is None:
            self.ymin = y
            self.ymax = y
            return
        if self.ymin > y:
            self.ymin = y
            return
        if self.ymax < y:
            self.ymax = y
            return

    def enlarge_point(self, point):
        self.enlarge_x(point.x)
        self.enlarge_y(point.y)

    def enlarge_line(self, line):
        self.enlarge_x(line.StartPoint.x)
        self.enlarge_x(line.EndPoint.x)
        self.enlarge_y(line.StartPoint.y)
        self.enlarge_y(line.EndPoint.y)

    def enlarge_circle(self, circle):
        self.enlarge_x(circle.Center.x - circle.Radius)
        self.enlarge_x(circle.Center.x + circle.Radius)
        self.enlarge_y(circle.Center.y - circle.Radius)
        self.enlarge_y(circle.Center.y + circle.Radius)

    def enlarge_arc_of_circle(self, arc):
        # TODO: correctly compute the arc extrema (cf. toShape().BoundBox)
        self.enlarge_x(arc.Center.x)
        self.enlarge_y(arc.Center.y)


def get_sketch_dims(sketch):
    bbox = BoundingBox()
    for geom in sketch.Geometry:
        if isinstance(geom, Base.Vector):
            bbox.enlarge_point(geom)
        elif isinstance(geom, Part.Line):
            bbox.enlarge_line(geom)
        elif isinstance(geom, Part.Circle):
            bbox.enlarge_circle(geom)
        elif isinstance(geom, Part.ArcOfCircle):
            bbox.enlarge_arc_of_circle(geom)
    if (bbox.xmin is not None) and (bbox.ymin is not None):
        return bbox.xmax - bbox.xmin, bbox.ymax - bbox.ymin
    else:
        return 0, 0


def add_noise(point, sigma):
    """Add a Gaussian noise with standard deviation sigma"""
    point.x = gauss(point.x, sigma)
    point.y = gauss(point.y, sigma)


def move_points(sketch, geom_index, point_indexes, sigma):
    # Direct access to sketch.Geometry[index] does not work. This would,
    # however prevent repeated recompute.
    for point_index in point_indexes:
        point = sketch.getPoint(geom_index, point_index)
        add_noise(point, sigma)
        sketch.movePoint(geom_index, point_index, point)

view_provider = Gui.activeDocument().getInEdit()

# Don't know how to exit from a macro.
do_move = True
if not view_provider:
    do_move = False

if do_move:
    sketch = view_provider.Object

    if sketch.TypeId != 'Sketcher::SketchObject':
        do_move = False

if do_move:
    sigma = max(get_sketch_dims(sketch)) * displacement_amplitude

    for i, geom in enumerate(sketch.Geometry):
        if isinstance(geom, Base.Vector):
            move_points(sketch, i, geom_points['point'], sigma)
        elif isinstance(geom, Part.Line):
            move_points(sketch, i, geom_points['line'], sigma)
        elif isinstance(geom, Part.Circle):
            move_points(sketch, i, geom_points['circle'], sigma)
        elif isinstance(geom, Part.ArcOfCircle):
            move_points(sketch, i, geom_points['arc'], sigma)
wmayer
Site Admin
Posts: 16460
Joined: Thu Feb 19, 2009 10:32 am

Re: Shake sketch (poor-man freedom relevator)

Postby wmayer » Thu Aug 14, 2014 9:08 pm

Might be useful for this issue: issue #1041
User avatar
bejant
Posts: 6076
Joined: Thu Jul 11, 2013 3:06 pm

Re: Shake sketch (poor-man freedom relevator)

Postby bejant » Mon Aug 18, 2014 6:16 pm

I made a Sketch from a Polyline in a zig-zag pattern, then used the Constraints Lock tool on some vertices, and left some others unconstrained. I ran the macro and it worked well and I like that the Sketch reverts back to the original orientation when Undo is used afterwards. So when Undo is used, the user has another chance the see the unconstrained entities move. As a suggestion, I think it would be even more helpful (especially to novices) if the things that moved when running this macro were also highlighted as mentioned in ticket 1041.
arcol
Posts: 223
Joined: Sun Nov 10, 2013 9:02 am

Re: Shake sketch (poor-man freedom relevator)

Postby arcol » Fri Oct 24, 2014 2:26 pm

Can somebody enlightens me how this is supposed to work?

I saved the macro under ~/.FreeCAD/constraints_find.FCMacro

I changed this line:

Code: Select all

from FreeCAD import Part
to this:

Code: Select all

import Part
The macro is listed under the Macro menu.
I can execute it, but nothing happens.

I suppose it should work like this:
[*] open the sketch
[*] Macro->Macros->constraints_find->EXECUTE
[*] Something should happen.

Any ideas?
jmaustpc
Posts: 10203
Joined: Tue Jul 26, 2011 6:28 am
Location: Australia

Re: Shake sketch (poor-man freedom relevator)

Postby jmaustpc » Fri Oct 24, 2014 3:20 pm

do you have any messages in report view?

For that matter, have you changed your FreeCAD preferences to redirect errors and Python messages to report view?
User avatar
NormandC
Posts: 18534
Joined: Sat Feb 06, 2010 9:52 pm
Location: Québec, Canada

Re: Shake sketch (poor-man freedom relevator)

Postby NormandC » Sat Oct 25, 2014 5:35 am

arcol wrote:Any ideas?
Sorry if I seem obvious, but if your sketch is fully constrained, nothing will happen.

After doing your change to the script, the macro works for me as expected. Elements that are unconstrained get moved.

OS: Ubuntu 14.04.1 LTS
Word size of OS: 64-bit
Word size of FreeCAD: 64-bit
Version: 0.15.4138 (Git)
Branch: master
Hash: f119e740c87918b103140b66b2316ae96f136b0e
Python version: 2.7.6
Qt version: 4.8.6
Coin version: 4.0.0a
OCC version: 6.7.1
arcol
Posts: 223
Joined: Sun Nov 10, 2013 9:02 am

Re: Shake sketch (poor-man freedom relevator)

Postby arcol » Mon Oct 27, 2014 12:08 pm

jmaustpc wrote:do you have any messages in report view?

For that matter, have you changed your FreeCAD preferences to redirect errors and Python messages to report view?
Yes, I did:
freecad_preference_general_output.png
freecad_preference_general_output.png (130.7 KiB) Viewed 5213 times
No output from the macro. I added a print statement at the end, and I see it in the report dialog each time I execute the macro:

Code: Select all

Active view is challenge_constraint_macro : 1[*] (at 0x57d7f30)
Tried to move all points by a random value
At the end of the macro file:

Code: Select all

print "Tried to move all points by a random value"
normandc wrote: Sorry if I seem obvious, but if your sketch is fully constrained, nothing will happen.

After doing your change to the script, the macro works for me as expected. Elements that are unconstrained get moved.
It was not fully constrained, but had 3 degrees of freedom, with 43 contraints already placed.

I player a bit more, and created some test sketches, and with a very basic sketch (a single rectangle) the points indeed move ONCE.

I was expecting some actual shaking.

But for my test file, it does exactly nothing.
Here is it attached (I attached in this thread originally):
challenge_constraint_macro.fcstd
(37.79 KiB) Downloaded 60 times
Does this macro works for anybody with the above testfile?

Normandc: To better word the problem: I can't figure out where should I put the missing constraints. And why I'm unable to move a single point in the sketch. Any idea?
ulrich1a
Posts: 1958
Joined: Sun Jul 07, 2013 12:08 pm

Re: Shake sketch (poor-man freedom relevator)

Postby ulrich1a » Mon Oct 27, 2014 6:37 pm

arcol wrote:It was not fully constrained, but had 3 degrees of freedom, with 43 contraints already placed.

I player a bit more, and created some test sketches, and with a very basic sketch (a single rectangle) the points indeed move ONCE.
I was expecting some actual shaking.
But for my test file, it does exactly nothing.
It seems your sketch has redundant constraints. Sketches with more than one redundant constraints do not move at all in most cases.
The sketch had two symmetry constraints at the same location. And there was a perpendicular constraint at the two construction lines. If one of the symmetry constraints is against the one construction line and not a point, the perpendicular constraint could be redundant. It is very easy to make redundant constraints in combination with the symmetry constraint, that are not detected by the sketcher solver. For me it seems also the Shake sketch macro do not help in this case, as it do not detect the redundant constraints.
After removing the two symmetry constraints and the perpendicular constraint, I was able to fully constrain the sketch with a little different approach.
For a discussion about redundant constraints have a look into the sketcher tutorial, in the FreeCAD-tutorial collection.

Ulrich
User avatar
bejant
Posts: 6076
Joined: Thu Jul 11, 2013 3:06 pm

Re: Shake sketch (poor-man freedom relevator)

Postby bejant » Mon Oct 27, 2014 9:33 pm

arcol, the way I used the original macro was to copy and paste it into the python console, with a Sketch open for editing, then press the Enter key...

After making your change to the macro it worked for me by selecting it from Macro > Macros, the pressing the Execute button.

It only "shakes" the sketch once. You can undo / redo for a shaking effect, but I too didn't see anything move in your Sketch02.