Macro Shake Sketch: Difference between revisions
(changed code tag for addonmgr to work) |
(ver 1.3 2022-06-01 add "FreeCAD.ActiveDocument.openTransaction(u"Shake Sketch")") |
||
Line 6: | Line 6: | ||
|Name=Macro Shake Sketch |
|Name=Macro Shake Sketch |
||
|Icon=Macro_Shake_Sketch.png |
|Icon=Macro_Shake_Sketch.png |
||
|Description=Shake a sketch in order to discover its unconstrained parts. Enter edit mode for a sketch and launch the macro. The macro will add a random noise on all sketch points. The sketch is then solved, constrained parts will retain their position, free parts will move {{ColoredText|#ff0000|#ffff00|( |
|Description=Shake a sketch in order to discover its unconstrained parts. Enter edit mode for a sketch and launch the macro. The macro will add a random noise on all sketch points. The sketch is then solved, constrained parts will retain their position, free parts will move {{ColoredText|#ff0000|#ffff00|(Use the Undo (Ctrl+Z) and Redo (Ctrl+Y) button (labelled "Shake Sketch")) or run this macro on a copy of the original sketch.}} |
||
|Author=Gaël Ecorchard, heda |
|Author=Gaël Ecorchard, heda |
||
|Version=1. |
|Version=1.3 |
||
|Date= |
|Date=2022-06-01 |
||
|FCVersion=All |
|FCVersion=All |
||
|Download=[https://www.freecadweb.org/wiki/images/4/4a/Macro_Shake_Sketch.png ToolBar Icon] |
|Download=[https://www.freecadweb.org/wiki/images/4/4a/Macro_Shake_Sketch.png ToolBar Icon] |
||
Line 20: | Line 20: | ||
<!--T:4--> |
<!--T:4--> |
||
''' |
Restore the original Sketch with the '''Undo (Ctrl+Z)''' and '''Redo (Ctrl+Y)''' button (labeled "Shake Sketch") '''or be careful working on a copy of your file because the macro "dismantles all" to display and you may need to start over.''' |
||
==Install== <!--T:7--> |
==Install== <!--T:7--> |
||
Line 51: | Line 51: | ||
# Author: heda v1.2 |
# Author: heda v1.2 |
||
# Version: |
# Version: |
||
# - 1.3: 2022-06-01 |
|||
# * added openTransaction(u"Shake Sketch") for restore the original Sketch |
|||
# - 1.2: 2021-10-22 |
# - 1.2: 2021-10-22 |
||
# * made macro runnable for current versions of fc |
# * made macro runnable for current versions of fc |
||
Line 61: | Line 63: | ||
# - 1.0: 2014-08 |
# - 1.0: 2014-08 |
||
# * first release |
# * first release |
||
# Amplitude of the point displacements. |
# Amplitude of the point displacements. |
||
# The standard deviation of the Gaussian noise is the largest sketch dimension |
# The standard deviation of the Gaussian noise is the largest sketch dimension |
||
# multiplied by this factor. |
# multiplied by this factor. |
||
⚫ | |||
__title__ = 'Sketch Shaker' |
|||
⚫ | |||
__date__ = '2022/06/01' |
|||
__icon__ = 'https://wiki.freecadweb.org/images/4/4a/Macro_Shake_Sketch.png' |
|||
displacement_amplitude = 0.1 |
displacement_amplitude = 0.1 |
||
debug_print = False |
debug_print = False |
||
title = 'Sketch Shaker Macro (ver: {})'.format(__version__) |
|||
# End of configuration. |
# End of configuration. |
||
Line 80: | Line 85: | ||
import Part |
import Part |
||
print('Running |
print('Running Macro ' + __title__ + ' ver: ' + __version__) |
||
FreeCAD.ActiveDocument.openTransaction(u"Shake Sketch") # memorise les actions (avec annuler restore) |
|||
# For each sketch geometry type, map a list of points to move. |
# For each sketch geometry type, map a list of points to move. |
||
Line 199: | Line 205: | ||
msg = 'A sketch needs to be in edit to be shaken.' |
msg = 'A sketch needs to be in edit to be shaken.' |
||
print(msg) |
print(msg) |
||
_ = QMessageBox.information(None, |
_ = QMessageBox.information(None, __title__, msg) |
||
else: |
else: |
||
sketch = view_provider.Object |
sketch = view_provider.Object |
||
Line 212: | Line 218: | ||
toggle_constraints(sketch, to_virtual) |
toggle_constraints(sketch, to_virtual) |
||
msg = ('Shake sketch will deform the loose parts of the sketch.\n' |
msg = ('Shake sketch will deform the loose parts of the sketch.\n' |
||
'The deformation |
'The deformation can be restored with : \n' |
||
'the Undo (Ctrl+Z) and Redo (Ctrl+Y) button (labeled "Shake Sketch").\n' |
|||
'If that is not desired, click Cancel,\n' |
'If that is not desired, click Cancel,\n' |
||
'and run the macro on a copy of the sketch.\n\n' |
'and run the macro on a copy of the sketch.\n\n' |
||
Line 218: | Line 225: | ||
'Visibility of constraints is restored' |
'Visibility of constraints is restored' |
||
' after shaking the sketch.') |
' after shaking the sketch.') |
||
reply = QMessageBox.information(None, |
reply = QMessageBox.information(None, 'Running Macro ' + __title__ + ' ver: ' + __version__, msg, |
||
QMessageBox.Ok | QMessageBox.Cancel, |
QMessageBox.Ok | QMessageBox.Cancel, |
||
QMessageBox.Ok) |
QMessageBox.Ok) |
||
Line 250: | Line 257: | ||
'for additional info about sketch status.') |
'for additional info about sketch status.') |
||
_ = QMessageBox.information(None, |
_ = QMessageBox.information(None, 'Running Macro ' + __title__ + ' ver: ' + __version__, msg) |
||
toggle_constraints(sketch, to_virtual) |
toggle_constraints(sketch, to_virtual) |
||
print('Macro finished.') |
print('Macro finished.') |
||
</pre> |
</pre> |
Revision as of 16:25, 1 June 2022
Description |
---|
Shake a sketch in order to discover its unconstrained parts. Enter edit mode for a sketch and launch the macro. The macro will add a random noise on all sketch points. The sketch is then solved, constrained parts will retain their position, free parts will move (Use the Undo (Ctrl+Z) and Redo (Ctrl+Y) button (labelled "Shake Sketch")) or run this macro on a copy of the original sketch. Macro version: 1.3 Last modified: 2022-06-01 FreeCAD version: All Download: ToolBar Icon Author: Gaël Ecorchard, heda |
Author |
Gaël Ecorchard, heda |
Download |
ToolBar Icon |
Links |
Macros recipes How to install macros How to customize toolbars |
Macro Version |
1.3 |
Date last modified |
2022-06-01 |
FreeCAD Version(s) |
All |
Default shortcut |
None |
See also |
None |
Description
Shake a sketch in order to discover its unconstrained parts. Enter edit mode for a sketch and launch the macro. The macro will add a random noise on all sketch points. The sketch is then solved, constrained parts will retain their position, free parts will move. Always run this macro on a copy of the original sketch.
Restore the original Sketch with the Undo (Ctrl+Z) and Redo (Ctrl+Y) button (labeled "Shake Sketch") or be careful working on a copy of your file because the macro "dismantles all" to display and you may need to start over.
Install
Available in Addon manager, or manual install.
Script
Macro Shake_Sketch.py
# -*- 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 v1.0 & v1.1 # Author: heda v1.2 # Version: # - 1.3: 2022-06-01 # * added openTransaction(u"Shake Sketch") for restore the original Sketch # - 1.2: 2021-10-22 # * made macro runnable for current versions of fc # * added Part.LineSegment # * added start info & result dialogue # * hides constraints during shake # * added simple debug printing # - 1.1: 2014-10-31 # * correct import for Part # - 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. __title__ = 'Sketch Shaker' __version__ = '1.3' __date__ = '2022/06/01' __icon__ = 'https://wiki.freecadweb.org/images/4/4a/Macro_Shake_Sketch.png' displacement_amplitude = 0.1 debug_print = False # End of configuration. from random import gauss from PySide.QtGui import QMessageBox import FreeCADGui as Gui from FreeCAD import Base import Part print('Running Macro ' + __title__ + ' ver: ' + __version__) FreeCAD.ActiveDocument.openTransaction(u"Shake Sketch") # memorise les actions (avec annuler restore) # For each sketch geometry type, map a list of points to move. g_geom_points = { Base.Vector: [1], Part.Line: [1, 2], # first point, last point Part.LineSegment: [1, 2], # first point, last point Part.Circle: [0, 3], # curve, center Part.ArcOfCircle: [1, 2, 3], # first point, last point, center } # moves bsplines and conics via lines and circles, no op for Part.Points def dprint(msg, *args): if debug_print: if args: print(msg.format(*args)) else: print(msg) class BoundingBox: xmin = xmax = ymin = ymax = None def enlarge_x(self, x): if self.xmin is None: self.xmin = self.xmax = x elif self.xmin > x: self.xmin = x elif self.xmax < x: self.xmax = x def enlarge_y(self, y): if self.ymin is None: self.ymin = self.ymax = y elif self.ymin > y: self.ymin = y elif self.ymax < y: self.ymax = y 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, Part.LineSegment)): 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 None in (bbox.xmin, bbox.ymin): dprint('sketch bbox not found') return 0, 0 else: dprint('sketch bbox found') return bbox.xmax - bbox.xmin, bbox.ymax - bbox.ymin def add_noise(point, sigma): """Add a Gaussian noise with standard deviation sigma""" dprint(' x0:{:>9.3f} y0:{:>9.3f}', point.x, point.y) point.x = gauss(point.x, sigma) point.y = gauss(point.y, sigma) dprint(' xt:{:>9.3f} yt:{:>9.3f}', point.x, point.y) def move_points(sketch, geom_index, sigma): point_indexes = g_geom_points.get(type(sketch.Geometry[i]), []) # Direct access to sketch.Geometry[index] does not work. This would, # however prevent repeated recompute. # not checked validity of comment for v1.2 moved = False for point_index in point_indexes: dprint('---- geo idx [{:>3} ] -- pt idx [{:>3} ] ----', geom_index, point_index) point = sketch.getPoint(geom_index, point_index) add_noise(point, sigma) try: sketch.movePoint(geom_index, point_index, point) except ValueError as e: dprint(repr(e)) new_pos = sketch.getPoint(geom_index, point_index) test = point.isEqual(new_pos, 0) dprint(' did it move? {}', test) if test: moved = True return moved def toggle_constraints(sketch, toggle): for cid in toggle: sketch.toggleVirtualSpace(cid) view_provider = Gui.activeDocument().getInEdit() shake_it = False if not view_provider: msg = 'A sketch needs to be in edit to be shaken.' print(msg) _ = QMessageBox.information(None, __title__, msg) else: sketch = view_provider.Object if sketch.TypeId == 'Sketcher::SketchObject': sketch.recompute() # ensure update to_virtual = [cid for cid, cts in enumerate(sketch.Constraints) if cts.InVirtualSpace] sketch_span = get_sketch_dims(sketch) sigma = max(sketch_span) * displacement_amplitude dprint('sketch span: dx:{:>9.3f} dy:{:>9.3f}', *sketch_span) dprint('sigma for gauss-dist: {:.3f}', sigma) toggle_constraints(sketch, to_virtual) msg = ('Shake sketch will deform the loose parts of the sketch.\n' 'The deformation can be restored with : \n' 'the Undo (Ctrl+Z) and Redo (Ctrl+Y) button (labeled "Shake Sketch").\n' 'If that is not desired, click Cancel,\n' 'and run the macro on a copy of the sketch.\n\n' 'Visibility of constraints has been toggled.\n' 'Visibility of constraints is restored' ' after shaking the sketch.') reply = QMessageBox.information(None, 'Running Macro ' + __title__ + ' ver: ' + __version__, msg, QMessageBox.Ok | QMessageBox.Cancel, QMessageBox.Ok) shake_it = reply == QMessageBox.Ok if not shake_it: toggle_constraints(sketch, to_virtual) if shake_it: nbr_moves = 0 for i in range(sketch.GeometryCount): did_move = move_points(sketch, i, sigma) if did_move: nbr_moves += 1 msg = 'Did {} moves. Sketch has a total of {} geometry entities.\n\n' msg = msg.format(nbr_moves, sketch.GeometryCount) open_verts = sketch.OpenVertices if open_verts: if len(open_verts) == 1: ov, form = 'one', 'vertex' else: ov, form = len(open_verts), 'vertices' msg += 'Sketch has {} open {}.'.format(ov, form) msg += ('\nA sketch with open vertices' ' cannot be used to create a solid.') else: msg += 'Sketch is free from open vertices.' msg += ('\n\nMenu: "Sketch/Validate Sketch..." can be used\n' 'for additional info about sketch status.') _ = QMessageBox.information(None, 'Running Macro ' + __title__ + ' ver: ' + __version__, msg) toggle_constraints(sketch, to_virtual) print('Macro finished.')