diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml
index 09ab5698fd..175d975d86 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -6,7 +6,7 @@ open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
-liberapay: # Replace with a single Liberapay username
+liberapay: FreeCAD
issuehunt: # Replace with a single IssueHunt username
otechie: # Replace with a single Otechie username
custom: ['https://www.patreon.com/yorikvanhavre', 'https://www.patreon.com/kkremitzki', 'https://www.patreon.com/thundereal']
diff --git a/src/Mod/Arch/importOBJ.py b/src/Mod/Arch/importOBJ.py
index b449846717..12919344ce 100644
--- a/src/Mod/Arch/importOBJ.py
+++ b/src/Mod/Arch/importOBJ.py
@@ -72,14 +72,23 @@ def getIndices(obj,shape,offsetv,offsetvn):
try:
if not isinstance(e.Curve,Part.LineSegment):
if not curves:
- myshape = obj.Shape.copy(False)
- myshape.Placement=obj.getGlobalPlacement()
+ if obj.isDerivedFrom("App::Link"):
+ myshape = obj.LinkedObject.Shape.copy(False)
+ myshape.Placement=obj.LinkPlacement
+ else:
+ myshape = obj.Shape.copy(False)
+ myshape.Placement=obj.getGlobalPlacement()
mesh=MeshPart.meshFromShape(Shape=myshape, LinearDeflection=0.1, AngularDeflection=0.7, Relative=True)
FreeCAD.Console.PrintWarning(translate("Arch","Found a shape containing curves, triangulating")+"\n")
break
except: # unimplemented curve type
- myshape = obj.Shape.copy(False)
- myshape.Placement=obj.getGlobalPlacement()
+ if obj.isDerivedFrom("App::Link"):
+ if obj.Shape:
+ myshape = obj.Shape.copy(False)
+ myshape.Placement=obj.LinkPlacement
+ else:
+ myshape = obj.Shape.copy(False)
+ myshape.Placement=obj.getGlobalPlacement()
mesh=MeshPart.meshFromShape(Shape=myshape, LinearDeflection=0.1, AngularDeflection=0.7, Relative=True)
FreeCAD.Console.PrintWarning(translate("Arch","Found a shape containing curves, triangulating")+"\n")
break
@@ -157,7 +166,7 @@ def export(exportList,filename,colors=None):
materials = []
outfile.write("mtllib " + os.path.basename(filenamemtl) + "\n")
for obj in objectslist:
- if obj.isDerivedFrom("Part::Feature") or obj.isDerivedFrom("Mesh::Feature"):
+ if obj.isDerivedFrom("Part::Feature") or obj.isDerivedFrom("Mesh::Feature") or obj.isDerivedFrom("App::Link"):
hires = None
if FreeCAD.GuiUp:
visible = obj.ViewObject.isVisible()
diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py
index 4c12910ac3..8076310f39 100644
--- a/src/Mod/Draft/DraftGui.py
+++ b/src/Mod/Draft/DraftGui.py
@@ -1893,8 +1893,7 @@ class DraftToolBar:
return str(a)
def togglesnap(self):
- if hasattr(FreeCADGui,"Snapper"):
- FreeCADGui.Snapper.toggle()
+ FreeCADGui.doCommand('FreeCADGui.runCommand("Draft_Snap_Lock")')
def togglenearsnap(self):
if hasattr(FreeCADGui,"Snapper"):
diff --git a/src/Mod/Draft/InitGui.py b/src/Mod/Draft/InitGui.py
index 286da11671..66330e4c3a 100644
--- a/src/Mod/Draft/InitGui.py
+++ b/src/Mod/Draft/InitGui.py
@@ -116,6 +116,7 @@ class DraftWorkbench(FreeCADGui.Workbench):
if hasattr(FreeCADGui, "draftToolBar"):
if not hasattr(FreeCADGui.draftToolBar, "loadedPreferences"):
FreeCADGui.addPreferencePage(":/ui/preferences-draft.ui", QT_TRANSLATE_NOOP("Draft", "Draft"))
+ FreeCADGui.addPreferencePage(":/ui/preferences-draftinterface.ui", QT_TRANSLATE_NOOP("Draft", "Draft"))
FreeCADGui.addPreferencePage(":/ui/preferences-draftsnap.ui", QT_TRANSLATE_NOOP("Draft", "Draft"))
FreeCADGui.addPreferencePage(":/ui/preferences-draftvisual.ui", QT_TRANSLATE_NOOP("Draft", "Draft"))
FreeCADGui.addPreferencePage(":/ui/preferences-drafttexts.ui", QT_TRANSLATE_NOOP("Draft", "Draft"))
@@ -140,7 +141,7 @@ class DraftWorkbench(FreeCADGui.Workbench):
if hasattr(FreeCADGui, "Snapper"):
FreeCADGui.Snapper.hide()
import draftutils.init_draft_statusbar as dsb
- dsb.hide_draft_statusbar()
+ dsb.hide_draft_statusbar()
FreeCAD.Console.PrintLog("Draft workbench deactivated.\n")
def ContextMenu(self, recipient):
diff --git a/src/Mod/Draft/Resources/Draft.qrc b/src/Mod/Draft/Resources/Draft.qrc
index 5467200ade..c4096b88f3 100644
--- a/src/Mod/Draft/Resources/Draft.qrc
+++ b/src/Mod/Draft/Resources/Draft.qrc
@@ -153,6 +153,7 @@
translations/Draft_zh-CN.qm
translations/Draft_zh-TW.qm
ui/preferences-draft.ui
+ ui/preferences-draftinterface.ui
ui/preferences-draftsnap.ui
ui/preferences-drafttexts.ui
ui/preferences-draftvisual.ui
diff --git a/src/Mod/Draft/Resources/ui/preferences-draft.ui b/src/Mod/Draft/Resources/ui/preferences-draft.ui
index 3a41695f63..005858b37e 100644
--- a/src/Mod/Draft/Resources/ui/preferences-draft.ui
+++ b/src/Mod/Draft/Resources/ui/preferences-draft.ui
@@ -6,8 +6,8 @@
0
0
- 584
- 881
+ 500
+ 560
@@ -17,16 +17,7 @@
6
-
- 9
-
-
- 9
-
-
- 9
-
-
+
9
-
@@ -41,41 +32,6 @@
General Draft Settings
- -
-
-
- If this is checked, copy mode will be kept across command, otherwise commands will always start in no-copy mode
-
-
- Global copy mode
-
-
- false
-
-
- copymode
-
-
- Mod/Draft
-
-
-
- -
-
-
- Normally, after copying objects, the copies get selected. If this option is checked, the base objects will be selected instead.
-
-
- Select base objects after copying
-
-
- selectBaseObjects
-
-
- Mod/Draft
-
-
-
-
-
@@ -233,68 +189,149 @@ Values with differences below this value will be treated as same. This value wil
-
-
+
- When this is checked, the Draft tools will create Part primitives instead of Draft objects, when available.
+ If this option is checked, the layers drop-down list will also show groups, allowing you to automatically add objects to groups too.
- Use Part Primitives when available
-
-
- UsePartPrimitives
-
-
- Mod/Draft
-
-
-
- -
-
-
- If this is checked, objects will appear as filled by default. Otherwise, they will appear as wireframe
-
-
- Fill objects with faces whenever possible
+ Show groups in layers list drop-down button
- true
+ false
- fillmode
+ AutogroupAddGroups
Mod/Draft
+
+
+
+ -
+
+
+ Draft tools options
+
+
+
+ 9
+
-
-
-
- When drawing lines, set focus on Length instead of X coordinate
+
+
+ 0
-
- focusOnLength
-
-
- Mod/Draft
-
-
-
- -
-
-
- If this option is set, when creating Draft objects on top of an existing face of another object, the "Support" property of the Draft object will be set to the base object. This was the standard behaviour before FreeCAD 0.19
-
-
- Set the Support property when possible
-
-
- useSupport
-
-
- Mod/Draft
-
-
+
-
+
+
+ When drawing lines, set focus on Length instead of X coordinate.
+This allows to point the direction and type the distance.
+
+
+ Set focus on Length instead of X coordinate
+
+
+ focusOnLength
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ If this option is set, when creating Draft objects on top of an existing face of another object, the "Support" property of the Draft object will be set to the base object. This was the standard behaviour before FreeCAD 0.19
+
+
+ Set the Support property when possible
+
+
+ useSupport
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ If this is checked, objects will appear as filled by default.
+Otherwise, they will appear as wireframe
+
+
+ Fill objects with faces whenever possible
+
+
+ true
+
+
+ fillmode
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Normally, after copying objects, the copies get selected.
+If this option is checked, the base objects will be selected instead.
+
+
+ Select base objects after copying
+
+
+ selectBaseObjects
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ If this is checked, copy mode will be kept across command,
+otherwise commands will always start in no-copy mode
+
+
+ Global copy mode
+
+
+ false
+
+
+ copymode
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Force Draft Tools to create Part primitives instead of Draft objects.
+Note that this is not fully supported, and many object will be not editable with Draft Modifiers.
+
+
+ Use Part Primitives when available
+
+
+ UsePartPrimitives
+
+
+ Mod/Draft
+
+
+
+
-
@@ -320,25 +357,6 @@ Values with differences below this value will be treated as same. This value wil
- -
-
-
- If this option is checked, the layers drop-down list will also show groups, allowing you to automatically add objects to groups too.
-
-
- Show groups in layers list drop-down button
-
-
- false
-
-
- AutogroupAddGroups
-
-
- Mod/Draft
-
-
-
@@ -408,7 +426,7 @@ Values with differences below this value will be treated as same. This value wil
This is the default color for objects being drawn while in construction mode.
-
+
44
125
@@ -428,965 +446,6 @@ Values with differences below this value will be treated as same. This value wil
- -
-
-
- In-Command Shortcuts
-
-
-
-
-
-
-
-
-
-
-
-
- Relative
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- R
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutRelative
-
-
- Mod/Draft
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- Continue
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- T
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutContinue
-
-
- Mod/Draft
-
-
-
-
-
-
-
- -
-
-
-
-
-
- Close
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- O
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutClose
-
-
- Mod/Draft
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- Copy
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- P
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutCopy
-
-
- Mod/Draft
-
-
-
-
-
- -
-
-
-
-
-
- Subelement Mode
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- D
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutSubelementMode
-
-
- Mod/Draft
-
-
-
-
-
- -
-
-
-
-
-
- Fill
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- L
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutFill
-
-
- Mod/Draft
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- Exit
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- A
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutExit
-
-
- Mod/Draft
-
-
-
-
-
- -
-
-
-
-
-
- Select Edge
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- E
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutSelectEdge
-
-
- Mod/Draft
-
-
-
-
-
- -
-
-
-
-
-
- Add Hold
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- Q
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutAddHold
-
-
- Mod/Draft
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- Length
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- H
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutLength
-
-
- Mod/Draft
-
-
-
-
-
- -
-
-
-
-
-
- Wipe
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- W
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutWipe
-
-
- Mod/Draft
-
-
-
-
-
- -
-
-
-
-
-
- Set WP
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- U
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutSetWP
-
-
- Mod/Draft
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- Cycle Snap
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- `
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutCycleSnap
-
-
- Mod/Draft
-
-
-
-
-
- -
-
-
- -
-
-
-
-
- -
-
-
-
-
-
-
-
-
- Snap
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- S
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutSnap
-
-
- Mod/Draft
-
-
-
-
-
- -
-
-
-
-
-
- Increase Radius
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- [
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutIncreaseRadius
-
-
- Mod/Draft
-
-
-
-
-
- -
-
-
-
-
-
- Decrease Radius
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- ]
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutDecreaseRadius
-
-
- Mod/Draft
-
-
-
-
-
-
-
- -
-
-
-
-
-
-
-
-
- Restrict X
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- X
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutRestrictX
-
-
- Mod/Draft
-
-
-
-
-
- -
-
-
-
-
-
- Restrict Y
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- Y
-
-
- 1
-
-
-
-
-
- false
-
-
- inCommandShortcutRestrictY
-
-
- Mod/Draft
-
-
-
-
-
- -
-
-
-
-
-
- Restrict Z
-
-
-
- -
-
-
- true
-
-
-
- 0
- 0
-
-
-
-
- 25
- 16777215
-
-
-
- Z
-
-
- 1
-
-
-
-
-
- false
-
-
- RestrictZ
-
-
- Mod/Draft
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Mod/Draft/Resources/ui/preferences-draftinterface.ui b/src/Mod/Draft/Resources/ui/preferences-draftinterface.ui
new file mode 100644
index 0000000000..7f231e4ce7
--- /dev/null
+++ b/src/Mod/Draft/Resources/ui/preferences-draftinterface.ui
@@ -0,0 +1,975 @@
+
+
+ Gui::Dialog::DlgSettingsDraft
+
+
+
+ 0
+ 0
+ 456
+ 338
+
+
+
+ User interface settings
+
+
+
+ 6
+
+
+ 9
+
+
-
+
+
+ In-Command Shortcuts
+
+
+
-
+
+
+ 0
+
+
-
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ `
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutCycleSnap
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ S
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutSnap
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Close
+
+
+
+ -
+
+
+ Relative
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ R
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutRelative
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ O
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutClose
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ T
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutContinue
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Continue
+
+
+
+ -
+
+
+ Copy
+
+
+
+ -
+
+
+ Increase Radius
+
+
+
+ -
+
+
+ Cycle Snap
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ [
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutIncreaseRadius
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ ]
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutDecreaseRadius
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Snap
+
+
+
+ -
+
+
+ Decrease Radius
+
+
+
+ -
+
+
+ Length
+
+
+
+ -
+
+
+ Wipe
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ D
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutSubelementMode
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Add Hold
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ L
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutFill
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Exit
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ P
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutCopy
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Fill
+
+
+
+ -
+
+
+ Subelement Mode
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ E
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutSelectEdge
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ H
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutLength
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Select Edge
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ W
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutWipe
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ A
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutExit
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ Q
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutAddHold
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Set WP
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ U
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutSetWP
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Restrict X
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ X
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutRestrictX
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Restrict Y
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ Y
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ inCommandShortcutRestrictY
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Restrict Z
+
+
+
+ -
+
+
+ true
+
+
+
+ 0
+ 0
+
+
+
+
+ 25
+ 16777215
+
+
+
+ Z
+
+
+ 1
+
+
+
+
+
+ false
+
+
+ RestrictZ
+
+
+ Mod/Draft
+
+
+
+
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Enable draft statusbar customization
+
+
+ Draft Statusbar
+
+
+ true
+
+
+ DisplayStatusbar
+
+
+ Mod/Draft
+
+
+
-
+
+
+ 0
+
+
-
+
+
+ Enable snap statusbar widget
+
+
+ Draft snap widget
+
+
+ true
+
+
+ DisplayStatusbarSnapWidget
+
+
+ Mod/Draft
+
+
+
+ -
+
+
+ Enable draft statusbar annotation scale widget
+
+
+ Annotation scale widget
+
+
+ DisplayStatusbarScaleWidget
+
+
+ Mod/Draft
+
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+ qPixmapFromMimeSource
+
+
+ Gui::PrefCheckBox
+ QCheckBox
+
+
+
+ Gui::PrefLineEdit
+ QLineEdit
+
+
+
+
+
+
diff --git a/src/Mod/Draft/draftguitools/gui_arcs.py b/src/Mod/Draft/draftguitools/gui_arcs.py
index b29766c1f4..eed62a60fb 100644
--- a/src/Mod/Draft/draftguitools/gui_arcs.py
+++ b/src/Mod/Draft/draftguitools/gui_arcs.py
@@ -139,7 +139,7 @@ class Arc_3Points(gui_base.GuiCommandSimplest):
Parameters
----------
point: Base::Vector
- The dynamic point pased by the callback
+ The dynamic point passed by the callback
as we move the pointer in the 3D view.
info: str
diff --git a/src/Mod/Draft/draftguitools/gui_base.py b/src/Mod/Draft/draftguitools/gui_base.py
index d3f072b7d3..86cc109d88 100644
--- a/src/Mod/Draft/draftguitools/gui_base.py
+++ b/src/Mod/Draft/draftguitools/gui_base.py
@@ -33,7 +33,7 @@ import draftutils.todo as todo
from draftutils.messages import _msg, _log
-class GuiCommandSimplest:
+class GuiCommandSimplest(object):
"""Simplest base class for GuiCommands.
This class only sets up the command name and the document object
@@ -126,100 +126,7 @@ class GuiCommandNeedsSelection(GuiCommandSimplest):
return False
-class GuiCommandSimplest:
- """Simplest base class for GuiCommands.
-
- This class only sets up the command name and the document object
- to use for the command.
- When it is executed, it logs the command name to the log file,
- and prints the command name to the console.
-
- It implements the `IsActive` method, which must return `True`
- when the command should be available.
- It should return `True` when there is an active document,
- otherwise the command (button or menu) should be disabled.
-
- This class is meant to be inherited by other GuiCommand classes
- to quickly log the command name, and set the correct document object.
-
- Parameter
- ---------
- name: str, optional
- It defaults to `'None'`.
- The name of the action that is being run,
- for example, `'Heal'`, `'Flip dimensions'`,
- `'Line'`, `'Circle'`, etc.
-
- doc: App::Document, optional
- It defaults to the value of `App.activeDocument()`.
- The document object itself, which indicates where the actions
- of the command will be executed.
-
- Attributes
- ----------
- command_name: str
- This is the command name, which is assigned by `name`.
-
- doc: App::Document
- This is the document object itself, which is assigned by `doc`.
-
- This attribute should be used by functions to make sure
- that the operations are performed in the correct document
- and not in other documents.
- To set the active document we can use
-
- >>> App.setActiveDocument(self.doc.Name)
- """
-
- def __init__(self, name="None", doc=App.activeDocument()):
- self.command_name = name
- self.doc = doc
-
- def IsActive(self):
- """Return True when this command should be available.
-
- It is `True` when there is a document.
- """
- if App.activeDocument():
- return True
- else:
- return False
-
- def Activated(self):
- """Execute when the command is called.
-
- Log the command name to the log file and console.
- Also update the `doc` attribute.
- """
- self.doc = App.activeDocument()
- _log("Document: {}".format(self.doc.Label))
- _log("GuiCommand: {}".format(self.command_name))
- _msg("{}".format(16*"-"))
- _msg("GuiCommand: {}".format(self.command_name))
-
-
-class GuiCommandNeedsSelection(GuiCommandSimplest):
- """Base class for GuiCommands that need a selection to be available.
-
- It re-implements the `IsActive` method to return `True`
- when there is both an active document and an active selection.
-
- It inherits `GuiCommandSimplest` to set up the document
- and other behavior. See this class for more information.
- """
-
- def IsActive(self):
- """Return True when this command should be available.
-
- It is `True` when there is a selection.
- """
- if App.activeDocument() and Gui.Selection.getSelection():
- return True
- else:
- return False
-
-
-class GuiCommandBase:
+class GuiCommandBase(object):
"""Generic class that is the basis of all Gui commands.
This class should eventually replace `DraftTools.DraftTool`,
diff --git a/src/Mod/Draft/draftguitools/gui_groups.py b/src/Mod/Draft/draftguitools/gui_groups.py
index bc790bbf0c..3aadd4e965 100644
--- a/src/Mod/Draft/draftguitools/gui_groups.py
+++ b/src/Mod/Draft/draftguitools/gui_groups.py
@@ -51,7 +51,7 @@ class AddToGroup(gui_base.GuiCommandNeedsSelection):
It adds selected objects to a group, or removes them from any group.
- It inherits `GuiCommandNeedsSelection` to only be availbale
+ It inherits `GuiCommandNeedsSelection` to only be available
when there is a document and a selection.
See this class for more information.
"""
@@ -151,7 +151,7 @@ class SelectGroup(gui_base.GuiCommandNeedsSelection):
in this case it works in an intuitive manner, selecting
only the objects under the group.
- It inherits `GuiCommandNeedsSelection` to only be availbale
+ It inherits `GuiCommandNeedsSelection` to only be available
when there is a document and a selection.
See this class for more information.
"""
diff --git a/src/Mod/Draft/draftguitools/gui_snapper.py b/src/Mod/Draft/draftguitools/gui_snapper.py
index 37a4a9e211..84ee1dc9e4 100644
--- a/src/Mod/Draft/draftguitools/gui_snapper.py
+++ b/src/Mod/Draft/draftguitools/gui_snapper.py
@@ -32,21 +32,28 @@ defined by `gui_trackers.gridTracker`.
# This module provides tools to handle point snapping and
# everything that goes with it (toolbar buttons, cursor icons, etc.).
+from pivy import coin
+from PySide import QtCore, QtGui
+
import collections as coll
import inspect
import itertools
import math
-from pivy import coin
-from PySide import QtCore, QtGui
-import FreeCAD
-import FreeCADGui
import Draft
import DraftVecUtils
-from FreeCAD import Vector
+import DraftGeomUtils
+
+import FreeCAD as App
+import FreeCADGui as Gui
+
+import Part
+
import draftguitools.gui_trackers as trackers
+from draftutils.init_tools import get_draft_snap_commands
from draftutils.messages import _msg, _wrn
+
__title__ = "FreeCAD Draft Snap tools"
__author__ = "Yorik van Havre"
__url__ = "https://www.freecadweb.org"
@@ -117,6 +124,27 @@ class Snapper:
self.callbackMove = None
self.snapObjectIndex = 0
+ # snap keys, it's important tha they are in this order for
+ # saving in preferences and for properly restore the toolbar
+ self.snaps = ['Lock', # 0
+ 'Near', # 1 former "passive" snap
+ 'Extension', # 2
+ 'Parallel', # 3
+ 'Grid', # 4
+ "Endpoint", # 5
+ 'Midpoint', # 6
+ 'Perpendicular', # 7
+ 'Angle', # 8
+ 'Center', # 9
+ 'Ortho', # 10
+ 'Intersection', # 11
+ 'Special', # 12
+ 'Dimensions', # 13
+ 'WorkingPlane' # 14
+ ]
+
+ self.init_active_snaps()
+
# the snapmarker has "dot","circle" and "square" available styles
if self.snapStyle:
self.mk = coll.OrderedDict([('passive', 'empty'),
@@ -159,6 +187,21 @@ class Snapper:
('intersection', ':/icons/Snap_Intersection.svg'),
('special', ':/icons/Snap_Special.svg')])
+
+ def init_active_snaps(self):
+ """
+ set self.active_snaps according to user prefs
+ """
+ self.active_snaps = []
+ param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
+ snap_modes = param.GetString("snapModes")
+ i = 0
+ for snap in snap_modes:
+ if bool(int(snap)):
+ self.active_snaps.append(self.snaps[i])
+ i += 1
+
+
def cstr(self, lastpoint, constrain, point):
"""Return constraints if needed."""
if constrain or self.mask:
@@ -170,6 +213,7 @@ class Snapper:
self.radiusTracker.update(fpt)
return fpt
+
def snap(self, screenpos,
lastpoint=None, active=True,
constrain=False, noTracker=False):
@@ -193,12 +237,13 @@ class Snapper:
global Part, DraftGeomUtils
import Part, DraftGeomUtils
+
self.spoint = None
if not hasattr(self, "toolbar"):
self.makeSnapToolBar()
- mw = FreeCADGui.getMainWindow()
- bt = mw.findChild(QtGui.QToolBar, "Draft Snap")
+ mw = Gui.getMainWindow()
+ bt = mw.findChild(QtGui.QToolBar,"Draft Snap")
if not bt:
mw.addToolBar(self.toolbar)
else:
@@ -298,10 +343,12 @@ class Snapper:
self.running = False
return fp
+
def cycleSnapObject(self):
"""Increse the index of the snap object by one."""
self.snapObjectIndex = self.snapObjectIndex + 1
+
def snapToObject(self, lastpoint, active, constrain,
eline, point, oldActive):
"""Snap to an object."""
@@ -310,7 +357,7 @@ class Snapper:
subname = self.snapInfo['SubName']
obj = parent.getSubObject(subname, retType=1)
else:
- obj = FreeCAD.ActiveDocument.getObject(self.snapInfo['Object'])
+ obj = App.ActiveDocument.getObject(self.snapInfo['Object'])
parent = obj
subname = self.snapInfo['Component']
if not obj:
@@ -434,9 +481,9 @@ class Snapper:
# calculating the nearest snap point
shortest = 1000000000000000000
- origin = Vector(self.snapInfo['x'],
- self.snapInfo['y'],
- self.snapInfo['z'])
+ origin = App.Vector(self.snapInfo['x'],
+ self.snapInfo['y'],
+ self.snapInfo['z'])
winner = None
fp = point
for snap in snaps:
@@ -454,7 +501,7 @@ class Snapper:
if self.radius:
dv = point.sub(winner[2])
if (dv.Length > self.radius):
- if (not oldActive) and self.isEnabled("passive"):
+ if (not oldActive) and self.isEnabled("Near"):
winner = self.snapToVertex(self.snapInfo)
# setting the cursors
@@ -479,28 +526,31 @@ class Snapper:
self.running = False
return self.spoint
+
def toWP(self, point):
"""Project the given point on the working plane, if needed."""
if self.isEnabled("WorkingPlane"):
- if hasattr(FreeCAD, "DraftWorkingPlane"):
- return FreeCAD.DraftWorkingPlane.projectPoint(point)
+ if hasattr(App, "DraftWorkingPlane"):
+ return App.DraftWorkingPlane.projectPoint(point)
return point
+
def getApparentPoint(self, x, y):
"""Return a 3D point, projected on the current working plane."""
view = Draft.get3DView()
pt = view.getPoint(x, y)
if self.mask != "z":
- if hasattr(FreeCAD, "DraftWorkingPlane"):
+ if hasattr(App,"DraftWorkingPlane"):
if view.getCameraType() == "Perspective":
camera = view.getCameraNode()
p = camera.getField("position").getValue()
- dv = pt.sub(Vector(p[0], p[1], p[2]))
+ dv = pt.sub(App.Vector(p[0], p[1], p[2]))
else:
dv = view.getViewDirection()
- return FreeCAD.DraftWorkingPlane.projectPoint(pt, dv)
+ return App.DraftWorkingPlane.projectPoint(pt, dv)
return pt
+
def snapToDim(self, obj):
snaps = []
if obj.ViewObject:
@@ -509,6 +559,7 @@ class Snapper:
snaps.append([obj.ViewObject.Proxy.p3, 'endpoint', self.toWP(obj.ViewObject.Proxy.p3)])
return snaps
+
def snapToExtensions(self, point, last, constrain, eline):
"""Return a point snapped to extension or parallel line.
@@ -526,7 +577,7 @@ class Snapper:
self.extLine.on()
self.setCursor(tsnap[1])
return tsnap[2], eline
- if self.isEnabled("extension"):
+ if self.isEnabled("Extension"):
tsnap = self.snapToExtOrtho(last, constrain, eline)
if tsnap:
if (tsnap[0].sub(point)).Length < self.radius:
@@ -553,74 +604,79 @@ class Snapper:
self.setCursor(tsnap[1])
return tsnap[2], eline
- for o in (self.lastObj[1], self.lastObj[0]):
- if o and (self.isEnabled('extension')
- or self.isEnabled('parallel')):
- ob = FreeCAD.ActiveDocument.getObject(o)
- if ob:
- if ob.isDerivedFrom("Part::Feature"):
- edges = ob.Shape.Edges
- if Draft.getType(ob) == "Wall":
- for so in [ob]+ob.Additions:
- if Draft.getType(so) == "Wall":
- if so.Base:
- edges.extend(so.Base.Shape.Edges)
- edges.reverse()
- if (not self.maxEdges) or (len(edges) <= self.maxEdges):
- for e in edges:
- if DraftGeomUtils.geomType(e) == "Line":
- np = self.getPerpendicular(e,point)
- if not DraftGeomUtils.isPtOnEdge(np,e):
- if (np.sub(point)).Length < self.radius:
- if self.isEnabled('extension'):
- if np != e.Vertexes[0].Point:
- p0 = e.Vertexes[0].Point
- if self.tracker and not self.selectMode:
- self.tracker.setCoords(np)
- self.tracker.setMarker(self.mk['extension'])
- self.tracker.on()
- if self.extLine:
- if self.snapStyle:
- dv = np.sub(p0)
- self.extLine.p1(p0.add(dv.multiply(0.5)))
- else:
- self.extLine.p1(p0)
- self.extLine.p2(np)
- self.extLine.on()
- self.setCursor('extension')
- ne = Part.LineSegment(p0,np).toShape()
- # storing extension line for intersection calculations later
- if len(self.lastExtensions) == 0:
- self.lastExtensions.append(ne)
- elif len(self.lastExtensions) == 1:
- if not DraftGeomUtils.areColinear(ne,self.lastExtensions[0]):
- self.lastExtensions.append(self.lastExtensions[0])
- self.lastExtensions[0] = ne
- else:
- if (not DraftGeomUtils.areColinear(ne,self.lastExtensions[0])) and \
- (not DraftGeomUtils.areColinear(ne,self.lastExtensions[1])):
- self.lastExtensions[1] = self.lastExtensions[0]
- self.lastExtensions[0] = ne
- return np,ne
+ for o in (self.lastObj[1], self.lastObj[0]):
+ if o and (self.isEnabled('Extension')
+ or self.isEnabled('Parallel')):
+ ob = App.ActiveDocument.getObject(o)
+ if not ob:
+ continue
+ if not ob.isDerivedFrom("Part::Feature"):
+ continue
+ edges = ob.Shape.Edges
+ if Draft.getType(ob) == "Wall":
+ for so in [ob]+ob.Additions:
+ if Draft.getType(so) == "Wall":
+ if so.Base:
+ edges.extend(so.Base.Shape.Edges)
+ edges.reverse()
+ if (not self.maxEdges) or (len(edges) <= self.maxEdges):
+ for e in edges:
+ if DraftGeomUtils.geomType(e) != "Line":
+ continue
+ np = self.getPerpendicular(e,point)
+ if DraftGeomUtils.isPtOnEdge(np,e):
+ continue
+ if (np.sub(point)).Length < self.radius:
+ if self.isEnabled('Extension'):
+ if np != e.Vertexes[0].Point:
+ p0 = e.Vertexes[0].Point
+ if self.tracker and not self.selectMode:
+ self.tracker.setCoords(np)
+ self.tracker.setMarker(self.mk['extension'])
+ self.tracker.on()
+ if self.extLine:
+ if self.snapStyle:
+ dv = np.sub(p0)
+ self.extLine.p1(p0.add(dv.multiply(0.5)))
else:
- if self.isEnabled('parallel'):
- if last:
- ve = DraftGeomUtils.vec(e)
- if not DraftVecUtils.isNull(ve):
- de = Part.LineSegment(last,last.add(ve)).toShape()
- np = self.getPerpendicular(de,point)
- if (np.sub(point)).Length < self.radius:
- if self.tracker and not self.selectMode:
- self.tracker.setCoords(np)
- self.tracker.setMarker(self.mk['parallel'])
- self.tracker.on()
- self.setCursor('parallel')
- return np,de
+ self.extLine.p1(p0)
+ self.extLine.p2(np)
+ self.extLine.on()
+ self.setCursor('extension')
+ ne = Part.LineSegment(p0,np).toShape()
+ # storing extension line for intersection calculations later
+ if len(self.lastExtensions) == 0:
+ self.lastExtensions.append(ne)
+ elif len(self.lastExtensions) == 1:
+ if not DraftGeomUtils.areColinear(ne,self.lastExtensions[0]):
+ self.lastExtensions.append(self.lastExtensions[0])
+ self.lastExtensions[0] = ne
+ else:
+ if (not DraftGeomUtils.areColinear(ne,self.lastExtensions[0])) and \
+ (not DraftGeomUtils.areColinear(ne,self.lastExtensions[1])):
+ self.lastExtensions[1] = self.lastExtensions[0]
+ self.lastExtensions[0] = ne
+ return np,ne
+ else:
+ if self.isEnabled('Parallel'):
+ if last:
+ ve = DraftGeomUtils.vec(e)
+ if not DraftVecUtils.isNull(ve):
+ de = Part.LineSegment(last,last.add(ve)).toShape()
+ np = self.getPerpendicular(de,point)
+ if (np.sub(point)).Length < self.radius:
+ if self.tracker and not self.selectMode:
+ self.tracker.setCoords(np)
+ self.tracker.setMarker(self.mk['parallel'])
+ self.tracker.on()
+ self.setCursor('parallel')
+ return np,de
return point,eline
+
def snapToCrossExtensions(self, point):
"""Snap to the intersection of the last 2 extension lines."""
- if self.isEnabled('extension'):
+ if self.isEnabled('Extension'):
if len(self.lastExtensions) == 2:
np = DraftGeomUtils.findIntersection(self.lastExtensions[0], self.lastExtensions[1], True, True)
if np:
@@ -648,19 +704,20 @@ class Snapper:
return p
return None
- def snapToPolar(self, point, last):
+
+ def snapToPolar(self,point,last):
"""Snap to polar lines from the given point."""
- if self.isEnabled('ortho') and (not self.mask):
+ if self.isEnabled('Ortho') and (not self.mask):
if last:
vecs = []
- if hasattr(FreeCAD, "DraftWorkingPlane"):
- ax = [FreeCAD.DraftWorkingPlane.u,
- FreeCAD.DraftWorkingPlane.v,
- FreeCAD.DraftWorkingPlane.axis]
+ if hasattr(App,"DraftWorkingPlane"):
+ ax = [App.DraftWorkingPlane.u,
+ App.DraftWorkingPlane.v,
+ App.DraftWorkingPlane.axis]
else:
- ax = [FreeCAD.Vector(1, 0, 0),
- FreeCAD.Vector(0, 1, 0),
- FreeCAD.Vector(0, 0, 1)]
+ ax = [App.Vector(1, 0, 0),
+ App.Vector(0, 1, 0),
+ App.Vector(0, 0, 1)]
for a in self.polarAngles:
if a == 90:
vecs.extend([ax[0], ax[0].negative()])
@@ -687,11 +744,12 @@ class Snapper:
return np,de
return point, None
+
def snapToGrid(self, point):
"""Return a grid snap point if available."""
if self.grid:
if self.grid.Visible:
- if self.isEnabled("grid"):
+ if self.isEnabled("Grid"):
np = self.grid.getClosestNode(point)
if np:
dv = point.sub(np)
@@ -704,10 +762,11 @@ class Snapper:
return np
return point
+
def snapToEndpoints(self, shape):
"""Return a list of endpoints snap locations."""
snaps = []
- if self.isEnabled("endpoint"):
+ if self.isEnabled("Endpoint"):
if hasattr(shape, "Vertexes"):
for v in shape.Vertexes:
snaps.append([v.Point, 'endpoint', self.toWP(v.Point)])
@@ -722,20 +781,22 @@ class Snapper:
snaps.append([v, 'endpoint', self.toWP(v)])
return snaps
+
def snapToMidpoint(self, shape):
"""Return a list of midpoints snap locations."""
snaps = []
- if self.isEnabled("midpoint"):
+ if self.isEnabled("Midpoint"):
if isinstance(shape, Part.Edge):
mp = DraftGeomUtils.findMidpoint(shape)
if mp:
snaps.append([mp, 'midpoint', self.toWP(mp)])
return snaps
+
def snapToPerpendicular(self, shape, last):
"""Return a list of perpendicular snap locations."""
snaps = []
- if self.isEnabled("perpendicular"):
+ if self.isEnabled("Perpendicular"):
if last:
if isinstance(shape, Part.Edge):
if DraftGeomUtils.geomType(shape) == "Line":
@@ -755,10 +816,11 @@ class Snapper:
snaps.append([np, 'perpendicular', self.toWP(np)])
return snaps
+
def snapToOrtho(self, shape, last, constrain):
"""Return a list of ortho snap locations."""
snaps = []
- if self.isEnabled("ortho"):
+ if self.isEnabled("Ortho"):
if constrain:
if isinstance(shape, Part.Edge):
if last:
@@ -772,9 +834,10 @@ class Snapper:
snaps.append([p, 'ortho', self.toWP(p)])
return snaps
+
def snapToExtOrtho(self, last, constrain, eline):
"""Return an ortho X extension snap location."""
- if self.isEnabled("extension") and self.isEnabled("ortho"):
+ if self.isEnabled("Extension") and self.isEnabled("Ortho"):
if constrain and last and self.constraintAxis and self.extLine:
tmpEdge1 = Part.LineSegment(last, last.add(self.constraintAxis)).toShape()
tmpEdge2 = Part.LineSegment(self.extLine.p1(), self.extLine.p2()).toShape()
@@ -793,6 +856,7 @@ class Snapper:
return None
return None
+
def snapToHold(self, point):
"""Return a snap location that is orthogonal to hold points.
@@ -800,15 +864,15 @@ class Snapper:
"""
if not self.holdPoints:
return None
- if hasattr(FreeCAD, "DraftWorkingPlane"):
- u = FreeCAD.DraftWorkingPlane.u
- v = FreeCAD.DraftWorkingPlane.v
+ if hasattr(App, "DraftWorkingPlane"):
+ u = App.DraftWorkingPlane.u
+ v = App.DraftWorkingPlane.v
else:
- u = FreeCAD.Vector(1, 0, 0)
- v = FreeCAD.Vector(0, 1, 0)
+ u = App.Vector(1, 0, 0)
+ v = App.Vector(0, 1, 0)
if len(self.holdPoints) > 1:
# first try mid points
- if self.isEnabled("midpoint"):
+ if self.isEnabled("Midpoint"):
l = list(self.holdPoints)
for p1, p2 in itertools.combinations(l, 2):
p3 = p1.add((p2.sub(p1)).multiply(0.5))
@@ -843,9 +907,10 @@ class Snapper:
return [p, 'extension', fp]
return None
+
def snapToExtPerpendicular(self, last):
"""Return a perpendicular X extension snap location."""
- if self.isEnabled("extension") and self.isEnabled("perpendicular"):
+ if self.isEnabled("Extension") and self.isEnabled("Perpendicular"):
if last and self.extLine:
if self.extLine.p1() != self.extLine.p2():
tmpEdge = Part.LineSegment(self.extLine.p1(), self.extLine.p2()).toShape()
@@ -853,10 +918,11 @@ class Snapper:
return [np, 'perpendicular', np]
return None
+
def snapToElines(self, e1, e2):
"""Return a snap at the infinite intersection of the given edges."""
snaps = []
- if self.isEnabled("intersection") and self.isEnabled("extension"):
+ if self.isEnabled("Intersection") and self.isEnabled("Extension"):
if e1 and e2:
# get the intersection points
pts = DraftGeomUtils.findIntersection(e1, e2, True, True)
@@ -865,10 +931,11 @@ class Snapper:
snaps.append([p, 'intersection', self.toWP(p)])
return snaps
+
def snapToAngles(self, shape):
"""Return a list of angle snap locations."""
snaps = []
- if self.isEnabled("angle"):
+ if self.isEnabled("Angle"):
rad = shape.Curve.Radius
pos = shape.Curve.Center
for i in (0, 30, 45, 60, 90,
@@ -882,10 +949,11 @@ class Snapper:
snaps.append([cur, 'angle', self.toWP(cur)])
return snaps
+
def snapToCenter(self, shape):
"""Return a list of center snap locations."""
snaps = []
- if self.isEnabled("center"):
+ if self.isEnabled("Center"):
pos = shape.Curve.Center
c = self.toWP(pos)
if hasattr(shape.Curve, "Radius"):
@@ -895,30 +963,32 @@ class Snapper:
195, 217.5, 232.5, 255,
285, 307.5, 322.5, 345):
ang = math.radians(i)
- cur = Vector(math.sin(ang) * rad + pos.x,
- math.cos(ang) * rad + pos.y,
- pos.z)
+ cur = App.Vector(math.sin(ang) * rad + pos.x,
+ math.cos(ang) * rad + pos.y,
+ pos.z)
snaps.append([cur, 'center', c])
else:
snaps.append([c, 'center', c])
return snaps
+
def snapToFace(self, shape):
"""Return a face center snap location."""
snaps = []
- if self.isEnabled("center"):
+ if self.isEnabled("Center"):
pos = shape.CenterOfMass
c = self.toWP(pos)
snaps.append([pos, 'center', c])
return snaps
+
def snapToIntersection(self, shape):
"""Return a list of intersection snap locations."""
snaps = []
- if self.isEnabled("intersection"):
+ if self.isEnabled("Intersection"):
# get the stored objects to calculate intersections
if self.lastObj[0]:
- obj = FreeCAD.ActiveDocument.getObject(self.lastObj[0])
+ obj = App.ActiveDocument.getObject(self.lastObj[0])
if obj:
if obj.isDerivedFrom("Part::Feature") or (Draft.getType(obj) == "Axis"):
if (not self.maxEdges) or (len(obj.Shape.Edges) <= self.maxEdges):
@@ -944,10 +1014,11 @@ class Snapper:
# when trying to read their types
return snaps
+
def snapToPolygon(self, obj):
"""Return a list of polygon center snap locations."""
snaps = []
- if self.isEnabled("center"):
+ if self.isEnabled("Center"):
c = obj.Placement.Base
for edge in obj.Shape.Edges:
p1 = edge.Vertexes[0].Point
@@ -958,23 +1029,24 @@ class Snapper:
snaps.append([v2, 'center', self.toWP(c)])
return snaps
+
def snapToVertex(self, info, active=False):
- """Return a vertex snap location."""
- p = Vector(info['x'], info['y'], info['z'])
+ p = App.Vector(info['x'], info['y'], info['z'])
if active:
- if self.isEnabled("passive"):
+ if self.isEnabled("Near"):
return [p, 'endpoint', self.toWP(p)]
else:
return []
- elif self.isEnabled("passive"):
+ elif self.isEnabled("Near"):
return [p, 'passive', p]
else:
return []
+
def snapToSpecials(self, obj, lastpoint=None, eline=None):
"""Return special snap locations, if any."""
snaps = []
- if self.isEnabled("special"):
+ if self.isEnabled("Special"):
if (Draft.getType(obj) == "Wall"):
# special snapping for wall: snap to its base shape if it is linear
@@ -1007,6 +1079,7 @@ class Snapper:
return snaps
+
def getScreenDist(self, dist, cursor):
"""Return a distance in 3D space from a screen pixels distance."""
view = Draft.get3DView()
@@ -1014,6 +1087,7 @@ class Snapper:
p2 = view.getPoint((cursor[0] + dist, cursor[1]))
return (p2.sub(p1)).Length
+
def getPerpendicular(self, edge, pt):
"""Return a point on an edge, perpendicular to the given point."""
dv = pt.sub(edge.Vertexes[0].Point)
@@ -1021,6 +1095,7 @@ class Snapper:
np = (edge.Vertexes[0].Point).add(nv)
return np
+
def setArchDims(self, p1, p2):
"""Show arc dimensions between 2 points."""
if self.isEnabled("Dimensions"):
@@ -1037,16 +1112,17 @@ class Snapper:
if self.dim2.Distance:
self.dim2.on()
+
def setCursor(self, mode=None):
"""Set or reset the cursor to the given mode or resets."""
if self.selectMode:
- mw = FreeCADGui.getMainWindow()
+ mw = Gui.getMainWindow()
for w in mw.findChild(QtGui.QMdiArea).findChildren(QtGui.QWidget):
if w.metaObject().className() == "SIM::Coin3D::Quarter::QuarterWidget":
w.unsetCursor()
self.cursorMode = None
elif not mode:
- mw = FreeCADGui.getMainWindow()
+ mw = Gui.getMainWindow()
for w in mw.findChild(QtGui.QMdiArea).findChildren(QtGui.QWidget):
if w.metaObject().className() == "SIM::Coin3D::Quarter::QuarterWidget":
w.unsetCursor()
@@ -1064,17 +1140,19 @@ class Snapper:
qp.drawPixmap(QtCore.QPoint(16, 8), tp)
qp.end()
cur = QtGui.QCursor(newicon, 8, 8)
- mw = FreeCADGui.getMainWindow()
+ mw = Gui.getMainWindow()
for w in mw.findChild(QtGui.QMdiArea).findChildren(QtGui.QWidget):
if w.metaObject().className() == "SIM::Coin3D::Quarter::QuarterWidget":
w.setCursor(cur)
self.cursorMode = mode
+
def restack(self):
"""Lower the grid tracker so it doesn't obscure other objects."""
if self.grid:
self.grid.lowerTracker()
+
def off(self, hideSnapBar=False):
"""Finish snapping."""
if self.tracker:
@@ -1108,6 +1186,7 @@ class Snapper:
self.running = False
self.holdPoints = []
+
def setSelectMode(self, mode):
"""Set the snapper into select mode (hides snapping temporarily)."""
self.selectMode = mode
@@ -1117,16 +1196,18 @@ class Snapper:
if self.trackLine:
self.trackLine.off()
+
def setAngle(self, delta=None):
"""Keep the current angle."""
if delta:
self.mask = delta
- elif isinstance(self.mask, FreeCAD.Vector):
+ elif isinstance(self.mask, App.Vector):
self.mask = None
elif self.trackLine:
if self.trackLine.Visible:
self.mask = self.trackLine.p2().sub(self.trackLine.p1())
+
def constrain(self, point, basepoint=None, axis=None):
"""Return a constrained point.
@@ -1138,15 +1219,15 @@ class Snapper:
used as basepoint.
"""
# without the Draft module fully loaded, no axes system!"
- if not hasattr(FreeCAD, "DraftWorkingPlane"):
+ if not hasattr(App, "DraftWorkingPlane"):
return point
- point = Vector(point)
+ point = App.Vector(point)
# setup trackers if needed
if not self.constrainLine:
if self.snapStyle:
- self.constrainLine = trackers.lineTracker(scolor=FreeCADGui.draftToolBar.getDefaultColor("snap"))
+ self.constrainLine = trackers.lineTracker(scolor=Gui.draftToolBar.getDefaultColor("snap"))
else:
self.constrainLine = trackers.lineTracker(dotted=True)
@@ -1162,23 +1243,23 @@ class Snapper:
if self.mask:
self.affinity = self.mask
if not self.affinity:
- self.affinity = FreeCAD.DraftWorkingPlane.getClosestAxis(delta)
- if isinstance(axis, FreeCAD.Vector):
+ self.affinity = App.DraftWorkingPlane.getClosestAxis(delta)
+ if isinstance(axis, App.Vector):
self.constraintAxis = axis
elif axis == "x":
- self.constraintAxis = FreeCAD.DraftWorkingPlane.u
+ self.constraintAxis = App.DraftWorkingPlane.u
elif axis == "y":
- self.constraintAxis = FreeCAD.DraftWorkingPlane.v
+ self.constraintAxis = App.DraftWorkingPlane.v
elif axis == "z":
- self.constraintAxis = FreeCAD.DraftWorkingPlane.axis
+ self.constraintAxis = App.DraftWorkingPlane.axis
else:
if self.affinity == "x":
- self.constraintAxis = FreeCAD.DraftWorkingPlane.u
+ self.constraintAxis = App.DraftWorkingPlane.u
elif self.affinity == "y":
- self.constraintAxis = FreeCAD.DraftWorkingPlane.v
+ self.constraintAxis = App.DraftWorkingPlane.v
elif self.affinity == "z":
- self.constraintAxis = FreeCAD.DraftWorkingPlane.axis
- elif isinstance(self.affinity, FreeCAD.Vector):
+ self.constraintAxis = App.DraftWorkingPlane.axis
+ elif isinstance(self.affinity, App.Vector):
self.constraintAxis = self.affinity
else:
self.constraintAxis = None
@@ -1201,6 +1282,7 @@ class Snapper:
return npoint
+
def unconstrain(self):
"""Unset the basepoint and the constrain line."""
self.basepoint = None
@@ -1208,6 +1290,7 @@ class Snapper:
if self.constrainLine:
self.constrainLine.off()
+
def getPoint(self, last=None, callback=None, movecallback=None,
extradlg=None, title=None, mode="point"):
"""Get a 3D point from the screen.
@@ -1229,7 +1312,7 @@ class Snapper:
if point:
print "got a 3D point: ",point
- FreeCADGui.Snapper.getPoint(callback=cb)
+ Gui.Snapper.getPoint(callback=cb)
If the callback function accepts more than one argument,
it will also receive the last snapped object. Finally, a qt widget
@@ -1243,7 +1326,7 @@ class Snapper:
self.pt = None
self.lastSnappedObject = None
self.holdPoints = []
- self.ui = FreeCADGui.draftToolBar
+ self.ui = Gui.draftToolBar
self.view = Draft.get3DView()
# remove any previous leftover callbacks
@@ -1259,20 +1342,20 @@ class Snapper:
mousepos = event.getPosition()
ctrl = event.wasCtrlDown()
shift = event.wasShiftDown()
- self.pt = FreeCADGui.Snapper.snap(mousepos, lastpoint=last,
- active=ctrl, constrain=shift)
- if hasattr(FreeCAD, "DraftWorkingPlane"):
+ self.pt = Gui.Snapper.snap(mousepos, lastpoint=last,
+ active=ctrl, constrain=shift)
+ if hasattr(App, "DraftWorkingPlane"):
self.ui.displayPoint(self.pt, last,
- plane=FreeCAD.DraftWorkingPlane,
- mask=FreeCADGui.Snapper.affinity)
+ plane=App.DraftWorkingPlane,
+ mask=App.Snapper.affinity)
if movecallback:
movecallback(self.pt, self.snapInfo)
def getcoords(point, relative=False):
"""Get the global coordinates from a point."""
self.pt = point
- if relative and last and hasattr(FreeCAD, "DraftWorkingPlane"):
- v = FreeCAD.DraftWorkingPlane.getGlobalCoords(point)
+ if relative and last and hasattr(App, "DraftWorkingPlane"):
+ v = App.DraftWorkingPlane.getGlobalCoords(point)
self.pt = last.add(v)
accept()
@@ -1289,8 +1372,8 @@ class Snapper:
self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.callbackMove)
self.callbackClick = None
self.callbackMove = None
- obj = FreeCADGui.Snapper.lastSnappedObject
- FreeCADGui.Snapper.off()
+ obj = Gui.Snapper.lastSnappedObject
+ Gui.Snapper.off()
self.ui.offUi()
if callback:
if len(inspect.getargspec(callback).args) > 1:
@@ -1306,7 +1389,7 @@ class Snapper:
self.view.removeEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(), self.callbackMove)
self.callbackClick = None
self.callbackMove = None
- FreeCADGui.Snapper.off()
+ Gui.Snapper.off()
self.ui.offUi()
if callback:
if len(inspect.getargspec(callback).args) > 1:
@@ -1330,123 +1413,98 @@ class Snapper:
self.callbackClick = self.view.addEventCallbackPivy(coin.SoMouseButtonEvent.getClassTypeId(),click)
self.callbackMove = self.view.addEventCallbackPivy(coin.SoLocation2Event.getClassTypeId(),move)
+
def makeSnapToolBar(self):
"""Build the Snap toolbar."""
- mw = FreeCADGui.getMainWindow()
+ mw = Gui.getMainWindow()
self.toolbar = QtGui.QToolBar(mw)
mw.addToolBar(QtCore.Qt.TopToolBarArea, self.toolbar)
self.toolbar.setObjectName("Draft Snap")
self.toolbar.setWindowTitle(QtCore.QCoreApplication.translate("Workbench", "Draft Snap"))
- self.toolbarButtons = []
- # grid button
- self.gridbutton = QtGui.QAction(mw)
- self.gridbutton.setIcon(QtGui.QIcon.fromTheme("Draft_Grid", QtGui.QIcon(":/icons/Draft_Grid.svg")))
- self.gridbutton.setText(QtCore.QCoreApplication.translate("Draft_ToggleGrid", "Grid"))
- self.gridbutton.setToolTip(QtCore.QCoreApplication.translate("Draft_ToggleGrid", "Toggles the Draft grid On/Off"))
- self.gridbutton.setObjectName("GridButton")
- self.gridbutton.setWhatsThis("Draft_ToggleGrid")
- QtCore.QObject.connect(self.gridbutton, QtCore.SIGNAL("triggered()"), self.toggleGrid)
- self.toolbar.addAction(self.gridbutton)
+ snap_gui_commands = get_draft_snap_commands()
+ self.init_draft_snap_buttons(snap_gui_commands, self.toolbar, "_Button")
+ self.restore_snap_buttons_state(self.toolbar,"_Button")
- # master button
- self.masterbutton = QtGui.QAction(mw)
- self.masterbutton.setIcon(QtGui.QIcon.fromTheme("Snap_Lock", QtGui.QIcon(":/icons/Snap_Lock.svg")))
- self.masterbutton.setText(QtCore.QCoreApplication.translate("Draft_Snap_Lock", "Lock"))
- self.masterbutton.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_Lock", "Toggle On/Off"))
- self.masterbutton.setObjectName("SnapButtonMain")
- self.masterbutton.setWhatsThis("Draft_ToggleSnap")
- self.masterbutton.setCheckable(True)
- self.masterbutton.setChecked(True)
- QtCore.QObject.connect(self.masterbutton,
- QtCore.SIGNAL("toggled(bool)"), self.toggle)
- self.toolbar.addAction(self.masterbutton)
- for c,i in self.cursors.items():
- if i:
- b = QtGui.QAction(mw)
- b.setIcon(QtGui.QIcon.fromTheme(i.replace(':/icons/', '').replace('.svg', ''), QtGui.QIcon(i)))
- if c == "passive":
- b.setText(QtCore.QCoreApplication.translate("Draft_Snap_Near", "Nearest"))
- b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_Near", "Nearest"))
- else:
- b.setText(QtCore.QCoreApplication.translate("Draft_Snap_"+c.capitalize(), c.capitalize()))
- b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_"+c.capitalize(), c.capitalize()))
- b.setObjectName("SnapButton" + c)
- b.setWhatsThis("Draft_" + c.capitalize())
- b.setCheckable(True)
- b.setChecked(True)
- self.toolbar.addAction(b)
- self.toolbarButtons.append(b)
- QtCore.QObject.connect(b, QtCore.SIGNAL("toggled(bool)"),
- self.saveSnapModes)
+ if not Draft.getParam("showSnapBar",True):
+ self.toolbar.hide()
- # adding non-snap button
- for n in ("Dimensions", "WorkingPlane"):
- b = QtGui.QAction(mw)
- b.setIcon(QtGui.QIcon.fromTheme("Snap_" + n, QtGui.QIcon(":/icons/Snap_"+n+".svg")))
- b.setText(QtCore.QCoreApplication.translate("Draft_Snap_" + n,n))
- b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap_" + n,n))
- b.setObjectName("SnapButton" + n)
- b.setWhatsThis("Draft_" + n)
+
+ def init_draft_snap_buttons(self, commands, context, button_suffix):
+ """
+ Init Draft Snap toolbar buttons.
+
+ Parameters:
+ commands Snap command list,
+ use: get_draft_snap_commands():
+ context The toolbar or action group the buttons have
+ to be added to
+ button_suffix The suffix that have to be applied to the command name
+ to define the button name
+ """
+ for gc in commands:
+ # setup toolbar buttons
+ command = 'Gui.runCommand("' + gc + '")'
+ b = QtGui.QAction(context)
+ b.setIcon(QtGui.QIcon(':/icons/' + gc[6:] + '.svg'))
+ b.setText(QtCore.QCoreApplication.translate("Draft_Snap", "Snap " + gc[11:]))
+ b.setToolTip(QtCore.QCoreApplication.translate("Draft_Snap", "Snap " + gc[11:]))
+ b.setObjectName(gc + button_suffix)
+ b.setWhatsThis("Draft_" + gc[11:].capitalize())
b.setCheckable(True)
b.setChecked(True)
- self.toolbar.addAction(b)
- QtCore.QObject.connect(b, QtCore.SIGNAL("toggled(bool)"),
- self.saveSnapModes)
- self.toolbarButtons.append(b)
+ context.addAction(b)
+ QtCore.QObject.connect(b,
+ QtCore.SIGNAL("triggered()"),
+ lambda f=Gui.doCommand,
+ arg=command:f(arg))
- # set status tip where needed
- for b in self.toolbar.actions():
+ for b in context.actions():
if len(b.statusTip()) == 0:
b.setStatusTip(b.toolTip())
- # restoring states
- t = Draft.getParam("snapModes", "111111111101111")
- if t:
- c = 0
- for b in [self.masterbutton] + self.toolbarButtons:
- if len(t) > c:
- state = bool(int(t[c]))
- b.setChecked(state)
+
+ def restore_snap_buttons_state(self, toolbar, button_suffix):
+ """
+ Restore toolbar button's checked state according to
+ "snapModes" saved in preferences
+ """
+ # set status tip where needed
+ param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
+ snap_modes = param.GetString("snapModes")
+
+ for button in toolbar.actions():
+ if len(button.statusTip()) == 0:
+ button.setStatusTip(button.toolTip())
+
+ # restore toolbar buttons state
+ if snap_modes:
+ for action in toolbar.findChildren(QtGui.QAction):
+ snap = action.objectName()[11:].replace(button_suffix, "")
+ if snap in Gui.Snapper.snaps:
+ i = Gui.Snapper.snaps.index(snap)
+ state = bool(int(snap_modes[i]))
+ action.setChecked(state)
if state:
- b.setToolTip(b.toolTip() + " (ON)")
+ action.setToolTip(action.toolTip() + " (ON)")
else:
- b.setToolTip(b.toolTip() + " (OFF)")
- c += 1
- if not Draft.getParam("showSnapBar", True):
- self.toolbar.hide()
+ action.setToolTip(action.toolTip() + " (OFF)")
+
+
+ def get_snap_toolbar(self):
+ """Retuns snap toolbar object."""
+ mw = Gui.getMainWindow()
+ if mw:
+ toolbar = mw.findChild(QtGui.QToolBar, "Draft Snap")
+ if toolbar:
+ return toolbar
+ return None
+
def toggleGrid(self):
- """Run Draft_ToggleGrid."""
- FreeCADGui.runCommand("Draft_ToggleGrid")
+ """Toggle FreeCAD Draft Grid."""
+ Gui.runCommand("Draft_ToggleGrid")
- def saveSnapModes(self):
- """Save the snap modes for next sessions."""
- t = ''
- for b in [self.masterbutton] + self.toolbarButtons:
- t += str(int(b.isChecked()))
- if b.isChecked():
- b.setToolTip(b.toolTip().replace("OFF", "ON"))
- else:
- b.setToolTip(b.toolTip().replace("ON", "OFF"))
- Draft.setParam("snapModes", t)
-
- def toggle(self, checked=None):
- """Toggle the snap mode."""
- if hasattr(self, "toolbarButtons"):
- if checked is None:
- self.masterbutton.toggle()
- elif checked:
- if hasattr(self, "savedButtonStates"):
- for i in range(len(self.toolbarButtons)):
- self.toolbarButtons[i].setEnabled(True)
- self.toolbarButtons[i].setChecked(self.savedButtonStates[i])
- else:
- self.savedButtonStates = []
- for i in range(len(self.toolbarButtons)):
- self.savedButtonStates.append(self.toolbarButtons[i].isChecked())
- self.toolbarButtons[i].setEnabled(False)
- self.saveSnapModes()
def showradius(self):
"""Show the snap radius indicator."""
@@ -1456,42 +1514,82 @@ class Snapper:
self.radiusTracker.update(self.radius)
self.radiusTracker.on()
- def isEnabled(self, but):
- """Return true if the given button is turned on."""
- for b in self.toolbarButtons:
- if str(b.objectName()) == "SnapButton" + but:
- return (b.isEnabled() and b.isChecked())
- return False
+
+ def isEnabled(self, snap):
+ """Returns true if the given snap is on"""
+ if "Lock" in self.active_snaps and snap in self.active_snaps:
+ return True
+ else:
+ return False
+
+
+ def toggle_snap(self, snap, set_to = None):
+ """Sets the given snap on/off according to the given parameter"""
+ if set_to: # set mode
+ if set_to is True:
+ if not snap in self.active_snaps:
+ self.active_snaps.append(snap)
+ status = True
+ elif set_to is False:
+ if snap in self.active_snaps:
+ self.active_snaps.remove(snap)
+ status = False
+ else: # toggle mode, default
+ if not snap in self.active_snaps:
+ self.active_snaps.append(snap)
+ status = True
+ elif snap in self.active_snaps:
+ self.active_snaps.remove(snap)
+ status = False
+ self.save_snap_state()
+ return status
+
+
+ def save_snap_state(self):
+ """
+ Save snap state to user preferences to be restored in next session.
+ """
+ param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
+ snap_modes = ""
+ for snap in self.snaps:
+ if snap in self.active_snaps:
+ snap_modes += "1"
+ else:
+ snap_modes += "0"
+ param.SetString("snapModes",snap_modes)
+
def show(self):
"""Show the toolbar and the grid."""
if not hasattr(self, "toolbar"):
self.makeSnapToolBar()
- mw = FreeCADGui.getMainWindow()
- bt = mw.findChild(QtGui.QToolBar, "Draft Snap")
+ bt = self.get_snap_toolbar()
if not bt:
+ mw = FreeCADGui.getMainWindow()
mw.addToolBar(self.toolbar)
self.toolbar.setParent(mw)
self.toolbar.show()
self.toolbar.toggleViewAction().setVisible(True)
- if FreeCADGui.ActiveDocument:
+ if Gui.ActiveDocument:
self.setTrackers()
- if not FreeCAD.ActiveDocument.Objects:
- if FreeCADGui.ActiveDocument.ActiveView:
- if FreeCADGui.ActiveDocument.ActiveView.getCameraType() == 'Orthographic':
- c = FreeCADGui.ActiveDocument.ActiveView.getCameraNode()
+ if not App.ActiveDocument.Objects:
+ if Gui.ActiveDocument.ActiveView:
+ if Gui.ActiveDocument.ActiveView.getCameraType() == 'Orthographic':
+ c = Gui.ActiveDocument.ActiveView.getCameraNode()
if c.orientation.getValue().getValue() == (0.0, 0.0, 0.0, 1.0):
- p = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
+ p = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
h = p.GetInt("defaultCameraHeight",0)
if h:
c.height.setValue(h)
+
def hide(self):
"""Hide the toolbar."""
if hasattr(self, "toolbar"):
self.toolbar.hide()
self.toolbar.toggleViewAction().setVisible(True)
+
def setGrid(self):
"""Set the grid, if visible."""
self.setTrackers()
@@ -1499,6 +1597,7 @@ class Snapper:
if self.grid.Visible:
self.grid.set()
+
def setTrackers(self):
"""Set the trackers."""
v = Draft.get3DView()
@@ -1523,7 +1622,7 @@ class Snapper:
self.tracker = trackers.snapTracker()
self.trackLine = trackers.lineTracker()
if self.snapStyle:
- c = FreeCADGui.draftToolBar.getDefaultColor("snap")
+ c = Gui.draftToolBar.getDefaultColor("snap")
self.extLine = trackers.lineTracker(scolor=c)
self.extLine2 = trackers.lineTracker(scolor=c)
else:
@@ -1546,9 +1645,11 @@ class Snapper:
self.trackers[8].append(self.extLine2)
self.trackers[9].append(self.holdTracker)
self.activeview = v
+
if self.grid and (not self.forceGridOff):
self.grid.set()
+
def addHoldPoint(self):
"""Add hold snap point to list of hold points."""
if self.spoint and self.spoint not in self.holdPoints:
diff --git a/src/Mod/Draft/draftguitools/gui_snaps.py b/src/Mod/Draft/draftguitools/gui_snaps.py
index 36ca85ffa0..b26a8becea 100644
--- a/src/Mod/Draft/draftguitools/gui_snaps.py
+++ b/src/Mod/Draft/draftguitools/gui_snaps.py
@@ -28,13 +28,74 @@
# \brief Provide the Draft_Snap commands used by the snapping mechanism
# in Draft.
+from PySide import QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
import FreeCADGui as Gui
+
import draftguitools.gui_base as gui_base
from draftutils.translate import _tr
+# UTILITIES -----------------------------------------------------------------
+
+
+def get_snap_statusbar_widget():
+ """Return snap statusbar button."""
+ mw = Gui.getMainWindow()
+ if mw:
+ sb = mw.statusBar()
+ if sb:
+ return sb.findChild(QtGui.QToolBar,"draft_snap_widget")
+ return None
+
+
+def sync_snap_toolbar_button(button, status):
+ """Set snap toolbar button to given state."""
+ snap_toolbar = Gui.Snapper.get_snap_toolbar()
+ if not snap_toolbar:
+ return
+ for a in snap_toolbar.actions():
+ if a.objectName() == button:
+ if button == "Draft_Snap_Lock_Button":
+ # for lock button
+ snap_toolbar.actions()[0].setChecked(status)
+ for a in snap_toolbar.actions()[1:]:
+ a.setEnabled(status)
+ else:
+ # for every other button
+ a.setChecked(status)
+ if a.isChecked():
+ a.setToolTip(a.toolTip().replace("OFF","ON"))
+ else:
+ a.setToolTip(a.toolTip().replace("ON","OFF"))
+
+
+def sync_snap_statusbar_button(button, status):
+ """Set snap statusbar button to given state."""
+ ssw = get_snap_statusbar_widget()
+ if not ssw:
+ return
+ for child in ssw.children():
+ if child.objectName() == "Snap_Statusbutton":
+ ssb = child
+ actions = []
+ for a in ssb.menu().actions() + ssw.children()[-6:]:
+ actions.append(a)
+
+ if button == "Draft_Snap_Lock_Statusbutton":
+ ssb.setChecked(status)
+ for a in actions[1:]:
+ a.setEnabled(status)
+ else:
+ for a in actions:
+ if a.objectName() == button:
+ a.setChecked(status)
+
+
+# SNAP GUI TOOLS ------------------------------------------------------------
+
+
class Draft_Snap_Lock(gui_base.GuiCommandSimplest):
"""GuiCommand for the Draft_Snap_Lock tool.
@@ -42,7 +103,7 @@ class Draft_Snap_Lock(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Main toggle snap"))
+ super(Draft_Snap_Lock, self).__init__(name=_tr("Main toggle snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -57,11 +118,13 @@ class Draft_Snap_Lock(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
-
+ super(Draft_Snap_Lock, self).Activated()
+
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "masterbutton"):
- Gui.Snapper.masterbutton.toggle()
+ status = Gui.Snapper.toggle_snap('Lock')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Lock"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Lock"+"_Statusbutton", status)
Gui.addCommand('Draft_Snap_Lock', Draft_Snap_Lock())
@@ -74,7 +137,7 @@ class Draft_Snap_Midpoint(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Midpoint snap"))
+ super(Draft_Snap_Midpoint, self).__init__(name=_tr("Midpoint snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -87,13 +150,13 @@ class Draft_Snap_Midpoint(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Midpoint, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtonmidpoint":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Midpoint')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Midpoint"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Midpoint_Statusbutton", status)
Gui.addCommand('Draft_Snap_Midpoint', Draft_Snap_Midpoint())
@@ -106,7 +169,7 @@ class Draft_Snap_Perpendicular(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Perpendicular snap"))
+ super(Draft_Snap_Perpendicular, self).__init__(name=_tr("Perpendicular snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -121,13 +184,13 @@ class Draft_Snap_Perpendicular(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Perpendicular, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtonperpendicular":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Perpendicular')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Perpendicular"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Perpendicular_Statusbutton", status)
Gui.addCommand('Draft_Snap_Perpendicular', Draft_Snap_Perpendicular())
@@ -140,7 +203,7 @@ class Draft_Snap_Grid(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Grid snap"))
+ super(Draft_Snap_Grid, self).__init__(name=_tr("Grid snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -152,13 +215,13 @@ class Draft_Snap_Grid(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Grid, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtongrid":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Grid')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Grid"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Grid_Statusbutton", status)
Gui.addCommand('Draft_Snap_Grid', Draft_Snap_Grid())
@@ -171,7 +234,7 @@ class Draft_Snap_Intersection(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Intersection snap"))
+ super(Draft_Snap_Intersection, self).__init__(name=_tr("Intersection snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -186,13 +249,13 @@ class Draft_Snap_Intersection(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Intersection, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtonintersection":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Intersection')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Intersection"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Intersection_Statusbutton", status)
Gui.addCommand('Draft_Snap_Intersection', Draft_Snap_Intersection())
@@ -205,7 +268,7 @@ class Draft_Snap_Parallel(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Parallel snap"))
+ super(Draft_Snap_Parallel, self).__init__(name=_tr("Parallel snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -218,13 +281,13 @@ class Draft_Snap_Parallel(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Parallel, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtonparallel":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Parallel')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Parallel"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Parallel_Statusbutton", status)
Gui.addCommand('Draft_Snap_Parallel', Draft_Snap_Parallel())
@@ -237,7 +300,7 @@ class Draft_Snap_Endpoint(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Endpoint snap"))
+ super(Draft_Snap_Endpoint, self).__init__(name=_tr("Endpoint snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -250,13 +313,13 @@ class Draft_Snap_Endpoint(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Endpoint, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtonendpoint":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Endpoint')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Endpoint"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Endpoint_Statusbutton", status)
Gui.addCommand('Draft_Snap_Endpoint', Draft_Snap_Endpoint())
@@ -270,7 +333,7 @@ class Draft_Snap_Angle(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Angle snap (30 and 45 degrees)"))
+ super(Draft_Snap_Angle, self).__init__(name=_tr("Angle snap (30 and 45 degrees)"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -284,13 +347,13 @@ class Draft_Snap_Angle(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Angle, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtonangle":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Angle')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Angle"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Angle_Statusbutton", status)
Gui.addCommand('Draft_Snap_Angle', Draft_Snap_Angle())
@@ -303,7 +366,7 @@ class Draft_Snap_Center(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Arc center snap"))
+ super(Draft_Snap_Center, self).__init__(name=_tr("Arc center snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -315,13 +378,13 @@ class Draft_Snap_Center(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Center, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtoncenter":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Center')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Center"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Center_Statusbutton", status)
Gui.addCommand('Draft_Snap_Center', Draft_Snap_Center())
@@ -334,7 +397,7 @@ class Draft_Snap_Extension(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Edge extension snap"))
+ super(Draft_Snap_Extension, self).__init__(name=_tr("Edge extension snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -347,13 +410,13 @@ class Draft_Snap_Extension(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Extension, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtonextension":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Extension')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Extension"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Extension_Statusbutton", status)
Gui.addCommand('Draft_Snap_Extension', Draft_Snap_Extension())
@@ -366,7 +429,7 @@ class Draft_Snap_Near(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Near snap"))
+ super(Draft_Snap_Near, self).__init__(name=_tr("Near snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -378,13 +441,13 @@ class Draft_Snap_Near(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Near, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtonpassive":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Near')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Near"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Near_Statusbutton", status)
Gui.addCommand('Draft_Snap_Near', Draft_Snap_Near())
@@ -398,7 +461,7 @@ class Draft_Snap_Ortho(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Orthogonal snap"))
+ super(Draft_Snap_Ortho, self).__init__(name=_tr("Orthogonal snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -412,13 +475,13 @@ class Draft_Snap_Ortho(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Ortho, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtonortho":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Ortho')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Ortho"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Ortho"+"_Statusbutton", status)
Gui.addCommand('Draft_Snap_Ortho', Draft_Snap_Ortho())
@@ -431,7 +494,7 @@ class Draft_Snap_Special(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Special point snap"))
+ super(Draft_Snap_Special, self).__init__(name=_tr("Special point snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -444,13 +507,13 @@ class Draft_Snap_Special(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Special, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtonspecial":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Special')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Special"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Special_Statusbutton", status)
Gui.addCommand('Draft_Snap_Special', Draft_Snap_Special())
@@ -464,7 +527,7 @@ class Draft_Snap_Dimensions(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Dimension display"))
+ super(Draft_Snap_Dimensions, self).__init__(name=_tr("Dimension display"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -478,13 +541,13 @@ class Draft_Snap_Dimensions(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_Dimensions, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtonDimensions":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('Dimensions')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_Dimensions"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_Dimensions"+"_Statusbutton", status)
Gui.addCommand('Draft_Snap_Dimensions', Draft_Snap_Dimensions())
@@ -500,7 +563,7 @@ class Draft_Snap_WorkingPlane(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Working plane snap"))
+ super(Draft_Snap_WorkingPlane, self).__init__(name=_tr("Working plane snap"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -520,13 +583,13 @@ class Draft_Snap_WorkingPlane(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(Draft_Snap_WorkingPlane, self).Activated()
if hasattr(Gui, "Snapper"):
- if hasattr(Gui.Snapper, "toolbarButtons"):
- for b in Gui.Snapper.toolbarButtons:
- if b.objectName() == "SnapButtonWorkingPlane":
- b.toggle()
+ status = Gui.Snapper.toggle_snap('WorkingPlane')
+ # change interface consistently
+ sync_snap_toolbar_button("Draft_Snap_WorkingPlane"+"_Button", status)
+ sync_snap_statusbar_button("Draft_Snap_WorkingPlane_Statusbutton", status)
Gui.addCommand('Draft_Snap_WorkingPlane', Draft_Snap_WorkingPlane())
@@ -539,7 +602,7 @@ class ShowSnapBar(gui_base.GuiCommandSimplest):
"""
def __init__(self):
- super().__init__(name=_tr("Show snap toolbar"))
+ super(ShowSnapBar, self).__init__(name=_tr("Show snap toolbar"))
def GetResources(self):
"""Set icon, menu and tooltip."""
@@ -553,7 +616,7 @@ class ShowSnapBar(gui_base.GuiCommandSimplest):
def Activated(self):
"""Execute when the command is called."""
- super().Activated()
+ super(ShowSnapBar, self).Activated()
if hasattr(Gui, "Snapper"):
Gui.Snapper.show()
diff --git a/src/Mod/Draft/draftguitools/gui_togglemodes.py b/src/Mod/Draft/draftguitools/gui_togglemodes.py
index 2a6644e5ce..954024be08 100644
--- a/src/Mod/Draft/draftguitools/gui_togglemodes.py
+++ b/src/Mod/Draft/draftguitools/gui_togglemodes.py
@@ -160,7 +160,7 @@ class ToggleDisplayMode(gui_base.GuiCommandNeedsSelection):
Switches the display mode of selected objects from flatlines
to wireframe and back.
- It inherits `GuiCommandNeedsSelection` to only be availbale
+ It inherits `GuiCommandNeedsSelection` to only be available
when there is a document and a selection.
See this class for more information.
"""
diff --git a/src/Mod/Draft/draftobjects/label.py b/src/Mod/Draft/draftobjects/label.py
index 6d6af72010..e025c30703 100644
--- a/src/Mod/Draft/draftobjects/label.py
+++ b/src/Mod/Draft/draftobjects/label.py
@@ -62,7 +62,7 @@ def make_label(targetpoint=None, target=None, direction=None,
["Horizontal","Vertical","Custom"]
distance : Quantity
- Lenght of the straight segment of label leader line
+ Length of the straight segment of label leader line
labeltype : String
Label type in
diff --git a/src/Mod/Draft/draftutils/init_draft_statusbar.py b/src/Mod/Draft/draftutils/init_draft_statusbar.py
index 1d55d611fd..56e1612d75 100644
--- a/src/Mod/Draft/draftutils/init_draft_statusbar.py
+++ b/src/Mod/Draft/draftutils/init_draft_statusbar.py
@@ -1,11 +1,3 @@
-"""Draft Statusbar commands.
-
-This module provide the code for the Draft Statusbar, activated by initGui
-"""
-## @package init_draft_statusbar
-# \ingroup DRAFT
-# \brief This module provides the code for the Draft Statusbar.
-
# ***************************************************************************
# * *
# * Copyright (c) 2020 Carlo Pavan *
@@ -29,12 +21,22 @@ This module provide the code for the Draft Statusbar, activated by initGui
# * USA *
# * *
# ***************************************************************************
+"""Draft Statusbar commands.
-import FreeCAD as App
-import FreeCADGui as Gui
+This module provide the code for the Draft Statusbar, activated by initGui
+"""
+## @package init_draft_statusbar
+# \ingroup DRAFT
+# \brief This module provides the code for the Draft Statusbar.
+
+from PySide import QtCore
from PySide import QtGui
from PySide.QtCore import QT_TRANSLATE_NOOP
+import FreeCAD as App
+import FreeCADGui as Gui
+
+from draftutils.init_tools import get_draft_snap_commands
#----------------------------------------------------------------------------
# SCALE WIDGET FUNCTIONS
@@ -124,7 +126,8 @@ def label_to_scale(label):
return scale
except:
err = QT_TRANSLATE_NOOP("draft",
- "Unable to convert input into a scale factor")
+ "Unable to convert input into a "
+ "scale factor")
App.Console.PrintWarning(err)
return None
@@ -137,22 +140,25 @@ def _set_scale(action):
mw = Gui.getMainWindow()
sb = mw.statusBar()
- statuswidget = sb.findChild(QtGui.QToolBar,"draft_status_widget")
+ scale_widget = sb.findChild(QtGui.QToolBar,"draft_status_scale_widget")
if action.text() == QT_TRANSLATE_NOOP("draft","custom"):
dialog_text = QT_TRANSLATE_NOOP("draft",
- "Set custom annotation scale in format x:x, x=x"
+ "Set custom annotation scale in "
+ "format x:x, x=x"
)
- custom_scale = QtGui.QInputDialog.getText(None, "Set custom scale", dialog_text)
+ custom_scale = QtGui.QInputDialog.getText(None, "Set custom scale",
+ dialog_text)
if custom_scale[1]:
print(custom_scale[0])
scale = label_to_scale(custom_scale[0])
- if scale is None: return
+ if scale is None:
+ return
param.SetFloat("DraftAnnotationScale", scale)
cs = scale_to_label(scale)
- statuswidget.scaleLabel.setText(cs)
+ scale_widget.scaleLabel.setText(cs)
else:
text_scale = action.text()
- statuswidget.scaleLabel.setText(text_scale)
+ scale_widget.scaleLabel.setText(text_scale)
scale = label_to_scale(text_scale)
param.SetFloat("DraftAnnotationScale", scale)
@@ -164,21 +170,131 @@ def init_draft_statusbar(sb):
"""
this function initializes draft statusbar
"""
+ param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
+
+ # SNAP WIDGET - init ----------------------------------------------------
- statuswidget = QtGui.QToolBar()
- statuswidget.setObjectName("draft_status_widget")
+ snap_widget = QtGui.QToolBar()
+ snap_widget.setObjectName("draft_snap_widget")
+ snap_widget.setIconSize(QtCore.QSize(16,16))
+
+ # GRID BUTTON - init
+ gridbutton = QtGui.QPushButton(snap_widget)
+ gridbutton.setIcon(QtGui.QIcon.fromTheme("Draft",
+ QtGui.QIcon(":/icons/"
+ "Draft_Grid.svg")))
+ gridbutton.setToolTip(QT_TRANSLATE_NOOP("Draft",
+ "Toggles Grid On/Off"))
+ gridbutton.setObjectName("Grid_Statusbutton")
+ gridbutton.setWhatsThis("Draft_ToggleGrid")
+ gridbutton.setFlat(True)
+ QtCore.QObject.connect(gridbutton,QtCore.SIGNAL("clicked()"),
+ lambda f=Gui.doCommand,
+ arg='Gui.runCommand("Draft_ToggleGrid")':f(arg))
+ snap_widget.addWidget(gridbutton)
+
+ # SNAP BUTTON - init
+ snappref = param.GetString("snapModes","111111111101111")[0]
+ snapbutton = QtGui.QPushButton(snap_widget)
+ snapbutton.setIcon(QtGui.QIcon.fromTheme("Draft",
+ QtGui.QIcon(":/icons/"
+ "Snap_Lock.svg")))
+ snapbutton.setObjectName("Snap_Statusbutton")
+ snapbutton.setWhatsThis("Draft_ToggleLockSnap")
+ snapbutton.setToolTip(QT_TRANSLATE_NOOP("Draft",
+ "Object snapping"))
+ snapbutton.setCheckable(True)
+ snapbutton.setChecked(bool(int(snappref)))
+ snapbutton.setFlat(True)
+
+ snaps_menu = QtGui.QMenu(snapbutton)
+ snaps_menu.setObjectName("draft_statusbar_snap_toolbar")
+
+ snap_gui_commands = get_draft_snap_commands()
+ if 'Draft_Snap_Ortho' in snap_gui_commands:
+ snap_gui_commands.remove('Draft_Snap_Ortho')
+ if 'Draft_Snap_WorkingPlane' in snap_gui_commands:
+ snap_gui_commands.remove('Draft_Snap_WorkingPlane')
+ if 'Draft_Snap_Dimensions' in snap_gui_commands:
+ snap_gui_commands.remove('Draft_Snap_Dimensions')
+ Gui.Snapper.init_draft_snap_buttons(snap_gui_commands,snaps_menu, "_Statusbutton")
+ Gui.Snapper.restore_snap_buttons_state(snaps_menu, "_Statusbutton")
+
+ snapbutton.setMenu(snaps_menu)
+ snap_widget.addWidget(snapbutton)
+
+
+ # DIMENSION BUTTON - init
+ dimpref = param.GetString("snapModes","111111111101111")[13]
+ dimbutton = QtGui.QPushButton(snap_widget)
+ dimbutton.setIcon(QtGui.QIcon.fromTheme("Draft",
+ QtGui.QIcon(":/icons/"
+ "Snap_Dimensions.svg")))
+ dimbutton.setToolTip(QT_TRANSLATE_NOOP("Draft",
+ "Toggles Visual Aid Dimensions On/Off"))
+ dimbutton.setObjectName("Draft_Snap_Dimensions_Statusbutton")
+ dimbutton.setWhatsThis("Draft_ToggleDimensions")
+ dimbutton.setFlat(True)
+ dimbutton.setCheckable(True)
+ dimbutton.setChecked(bool(int(dimpref)))
+ QtCore.QObject.connect(dimbutton,QtCore.SIGNAL("clicked()"),
+ lambda f=Gui.doCommand,
+ arg='Gui.runCommand("Draft_Snap_Dimensions")':f(arg))
+ snap_widget.addWidget(dimbutton)
+
+ # ORTHO BUTTON - init
+ ortopref = param.GetString("snapModes","111111111101111")[10]
+ orthobutton = QtGui.QPushButton(snap_widget)
+ orthobutton.setIcon(QtGui.QIcon.fromTheme("Draft",
+ QtGui.QIcon(":/icons/"
+ "Snap_Ortho.svg")))
+ orthobutton.setObjectName("Draft_Snap_Ortho"+"_Statusbutton")
+ orthobutton.setWhatsThis("Draft_ToggleOrtho")
+ orthobutton.setToolTip(QT_TRANSLATE_NOOP("Draft",
+ "Toggles Ortho On/Off"))
+ orthobutton.setFlat(True)
+ orthobutton.setCheckable(True)
+ orthobutton.setChecked(bool(int(ortopref)))
+ QtCore.QObject.connect(orthobutton,QtCore.SIGNAL("clicked()"),
+ lambda f=Gui.doCommand,
+ arg='Gui.runCommand("Draft_Snap_Ortho")':f(arg))
+ snap_widget.addWidget(orthobutton)
+
+ # WORKINGPLANE BUTTON - init
+ wppref = param.GetString("snapModes","111111111101111")[14]
+ wpbutton = QtGui.QPushButton(snap_widget)
+ wpbutton.setIcon(QtGui.QIcon.fromTheme("Draft",
+ QtGui.QIcon(":/icons/"
+ "Snap_WorkingPlane.svg")))
+ wpbutton.setObjectName("Draft_Snap_WorkingPlane_Statusbutton")
+ wpbutton.setWhatsThis("Draft_ToggleWorkingPlaneSnap")
+ wpbutton.setToolTip(QT_TRANSLATE_NOOP("Draft",
+ "Toggles Constrain to Working Plane On/Off"))
+ wpbutton.setFlat(True)
+ wpbutton.setCheckable(True)
+ wpbutton.setChecked(bool(int(wppref)))
+ QtCore.QObject.connect(wpbutton,QtCore.SIGNAL("clicked()"),
+ lambda f=Gui.doCommand,
+ arg='Gui.runCommand("Draft_Snap_WorkingPlane")':f(arg))
+ snap_widget.addWidget(wpbutton)
+
+ # add snap widget to the statusbar
+ sb.insertPermanentWidget(2, snap_widget)
+ snap_widget.show()
+
- # SCALE TOOL -------------------------------------------------------------
+ # SCALE WIDGET ----------------------------------------------------------
+ scale_widget = QtGui.QToolBar()
+ scale_widget.setObjectName("draft_status_scale_widget")
# get scales list according to system units
draft_scales = get_scales()
# get draft annotation scale
- param = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
draft_annotation_scale = param.GetFloat("DraftAnnotationScale", 1.0)
# initializes scale widget
- statuswidget.draft_scales = draft_scales
+ scale_widget.draft_scales = draft_scales
scaleLabel = QtGui.QPushButton("Scale")
scaleLabel.setObjectName("ScaleLabel")
scaleLabel.setFlat(True)
@@ -194,39 +310,70 @@ def init_draft_statusbar(sb):
scaleLabel.setText(scale_label)
tooltip = "Set the scale used by draft annotation tools"
scaleLabel.setToolTip(QT_TRANSLATE_NOOP("draft",tooltip))
- statuswidget.addWidget(scaleLabel)
- statuswidget.scaleLabel = scaleLabel
+ scale_widget.addWidget(scaleLabel)
+ scale_widget.scaleLabel = scaleLabel
- # ADD TOOLS TO STATUS BAR ------------------------------------------------
- sb.addPermanentWidget(statuswidget)
- statuswidget.show()
+ # add scale widget to the statusbar
+ sb.insertPermanentWidget(3, scale_widget)
+ scale_widget.show()
def show_draft_statusbar():
"""
shows draft statusbar if present or initializes it
"""
+ params = App.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft")
+ display_statusbar = params.GetBool("DisplayStatusbar", True)
+
+ if not display_statusbar:
+ return
+
mw = Gui.getMainWindow()
if mw:
sb = mw.statusBar()
- statuswidget = sb.findChild(QtGui.QToolBar,"draft_status_widget")
- if statuswidget:
- statuswidget.show()
- else:
+
+ scale_widget = sb.findChild(QtGui.QToolBar,
+ "draft_status_scale_widget")
+ if scale_widget:
+ scale_widget.show()
+ elif params.GetBool("DisplayStatusbarScaleWidget", True):
init_draft_statusbar(sb)
+
+ snap_widget = sb.findChild(QtGui.QToolBar,"draft_snap_widget")
+ if snap_widget:
+ snap_widget.show()
+ elif params.GetBool("DisplayStatusbarSnapWidget", True):
+ init_draft_statusbar(sb)
+
def hide_draft_statusbar():
"""
hides draft statusbar if present
"""
mw = Gui.getMainWindow()
- if mw:
- sb = mw.statusBar()
- statuswidget = sb.findChild(QtGui.QToolBar,"draft_status_widget")
- if statuswidget:
- statuswidget.hide()
- else:
- # when switching workbenches, the toolbar sometimes "jumps"
- # out of the status bar to any other dock area...
- statuswidget = mw.findChild(QtGui.QToolBar,"draft_status_widget")
- if statuswidget:
- statuswidget.hide()
\ No newline at end of file
+ if not mw:
+ return
+ sb = mw.statusBar()
+
+ # hide scale widget
+ scale_widget = sb.findChild(QtGui.QToolBar,
+ "draft_status_scale_widget")
+ if scale_widget:
+ scale_widget.hide()
+ else:
+ # when switching workbenches, the toolbar sometimes "jumps"
+ # out of the status bar to any other dock area...
+ scale_widget = mw.findChild(QtGui.QToolBar,
+ "draft_status_scale_widget")
+ if scale_widget:
+ scale_widget.hide()
+
+ # hide snap widget
+ snap_widget = sb.findChild(QtGui.QToolBar,"draft_snap_widget")
+ if snap_widget:
+ snap_widget.hide()
+ else:
+ # when switching workbenches, the toolbar sometimes "jumps"
+ # out of the status bar to any other dock area...
+ snap_widget = mw.findChild(QtGui.QToolBar,"draft_snap_widget")
+ if snap_widget:
+ snap_widget.hide()
\ No newline at end of file
diff --git a/src/Mod/Draft/draftutils/init_tools.py b/src/Mod/Draft/draftutils/init_tools.py
index 49b85cbcf6..a4787f1a9d 100644
--- a/src/Mod/Draft/draftutils/init_tools.py
+++ b/src/Mod/Draft/draftutils/init_tools.py
@@ -115,15 +115,15 @@ def get_draft_utility_commands():
def get_draft_snap_commands():
"""Return the snapping commands list."""
- return ['Draft_Snap_Lock', 'Draft_Snap_Midpoint',
- 'Draft_Snap_Perpendicular',
- 'Draft_Snap_Grid', 'Draft_Snap_Intersection',
- 'Draft_Snap_Parallel',
- 'Draft_Snap_Endpoint', 'Draft_Snap_Angle',
- 'Draft_Snap_Center',
- 'Draft_Snap_Extension', 'Draft_Snap_Near',
- 'Draft_Snap_Ortho', 'Draft_Snap_Special',
- 'Draft_Snap_Dimensions', 'Draft_Snap_WorkingPlane']
+ return ['Draft_Snap_Lock',
+ 'Draft_Snap_Endpoint', 'Draft_Snap_Midpoint',
+ 'Draft_Snap_Center', 'Draft_Snap_Angle',
+ 'Draft_Snap_Intersection', 'Draft_Snap_Perpendicular',
+ 'Draft_Snap_Extension', 'Draft_Snap_Parallel',
+ 'Draft_Snap_Special', 'Draft_Snap_Near',
+ 'Draft_Snap_Ortho', 'Draft_Snap_Grid',
+ 'Draft_Snap_WorkingPlane', 'Draft_Snap_Dimensions',
+ ]
def init_draft_toolbars(workbench):
diff --git a/src/Mod/Mesh/App/MeshPy.xml b/src/Mod/Mesh/App/MeshPy.xml
index 974b027428..a43402f289 100644
--- a/src/Mod/Mesh/App/MeshPy.xml
+++ b/src/Mod/Mesh/App/MeshPy.xml
@@ -469,7 +469,7 @@ an empty dictionary if there is no intersection.
getPlanarSegments(dev,[min faces=0]) -> list
Get all planes of the mesh as segment.
In the worst case each triangle can be regarded as single
-plane if none of its neighours is coplanar.
+plane if none of its neighbors are coplanar.
diff --git a/src/Mod/Part/App/AppPartPy.cpp b/src/Mod/Part/App/AppPartPy.cpp
index 1210c81d55..6bbee793bb 100644
--- a/src/Mod/Part/App/AppPartPy.cpp
+++ b/src/Mod/Part/App/AppPartPy.cpp
@@ -750,7 +750,7 @@ private:
p1.Transform(loc.Transformation());
p2.Transform(loc.Transformation());
p3.Transform(loc.Transformation());
- // TODO: verify if tolerence should be hard coded
+ // TODO: verify if tolerance should be hard coded
if (!p1.IsEqual(p2, 0.01) && !p2.IsEqual(p3, 0.01) && !p3.IsEqual(p1, 0.01)) {
PyObject *t1 = PyTuple_Pack(3, PyFloat_FromDouble(p1.X()), PyFloat_FromDouble(p1.Y()), PyFloat_FromDouble(p1.Z()));
PyObject *t2 = PyTuple_Pack(3, PyFloat_FromDouble(p2.X()), PyFloat_FromDouble(p2.Y()), PyFloat_FromDouble(p2.Z()));
diff --git a/src/Mod/Part/Gui/DlgSettingsObjectColor.ui b/src/Mod/Part/Gui/DlgSettingsObjectColor.ui
index 9de8a266bd..0a4d195120 100644
--- a/src/Mod/Part/Gui/DlgSettingsObjectColor.ui
+++ b/src/Mod/Part/Gui/DlgSettingsObjectColor.ui
@@ -40,7 +40,7 @@
The default color for new shapes
-
+
204
204
@@ -89,7 +89,7 @@
The default line color for new shapes
-
+
25
25
@@ -157,7 +157,7 @@
The default color for new vertices
-
+
25
25
@@ -225,7 +225,7 @@
The color of bounding boxes in the 3D view
-
+
255
255
@@ -248,6 +248,12 @@
0
+
+ Bottom side of surface will be rendered the same way than top.
+If not checked, it depends on the option "Backlight color"
+(preferences section Display -> 3D View); either the backlight color
+will be used or black.
+
Two-side rendering
@@ -303,6 +309,9 @@
-
+
+ Text color for document annotations
+
AnnotationTextColor
diff --git a/src/Mod/PartDesign/CMakeLists.txt b/src/Mod/PartDesign/CMakeLists.txt
index 177550408a..435ce86d35 100644
--- a/src/Mod/PartDesign/CMakeLists.txt
+++ b/src/Mod/PartDesign/CMakeLists.txt
@@ -16,6 +16,8 @@ if(BUILD_GUI)
TestPartDesignGui.py
InvoluteGearFeature.py
InvoluteGearFeature.ui
+ SprocketFeature.py
+ SprocketFeature.ui
)
endif(BUILD_GUI)
@@ -60,6 +62,13 @@ set(PartDesign_GearScripts
fcgear/svggear.py
)
+set(PartDesign_SprocketScripts
+ fcsprocket/__init__.py
+ fcsprocket/fcsprocket.py
+ fcsprocket/fcsprocketdialog.py
+ fcsprocket/sprocket.py
+)
+
set(PartDesign_WizardShaft
WizardShaft/__init__.py
WizardShaft/WizardShaft.svg
@@ -76,6 +85,7 @@ add_custom_target(PartDesignScripts ALL SOURCES
${PartDesign_OtherScripts}
${PartDesign_TestScripts}
${PartDesign_GearScripts}
+ ${PartDesign_SprocketScripts}
)
fc_target_copy_resource(PartDesignScripts
@@ -85,6 +95,7 @@ fc_target_copy_resource(PartDesignScripts
${PartDesign_OtherScripts}
${PartDesign_TestScripts}
${PartDesign_GearScripts}
+ ${PartDesign_SprocketScripts}
)
INSTALL(
@@ -113,7 +124,13 @@ INSTALL(
${PartDesign_GearScripts}
DESTINATION
Mod/PartDesign/fcgear
-
+)
+
+INSTALL(
+ FILES
+ ${PartDesign_SprocketScripts}
+ DESTINATION
+ Mod/PartDesign/fcsprocket
)
if(BUILD_FEM)
diff --git a/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc b/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc
index e8db9a825e..34550529ba 100644
--- a/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc
+++ b/src/Mod/PartDesign/Gui/Resources/PartDesign.qrc
@@ -37,6 +37,7 @@
icons/PartDesign_Revolution.svg
icons/PartDesign_Scaled.svg
icons/PartDesign_ShapeBinder.svg
+ icons/PartDesign_Sprocket.svg
icons/PartDesign_SubShapeBinder.svg
icons/PartDesign_Subtractive_Box.svg
icons/PartDesign_Subtractive_Cone.svg
diff --git a/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Sprocket.svg b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Sprocket.svg
new file mode 100644
index 0000000000..15bc199d19
--- /dev/null
+++ b/src/Mod/PartDesign/Gui/Resources/icons/PartDesign_Sprocket.svg
@@ -0,0 +1,492 @@
+
+
+
+
diff --git a/src/Mod/PartDesign/Gui/Workbench.cpp b/src/Mod/PartDesign/Gui/Workbench.cpp
index 350c14dbe3..b8489d36fe 100644
--- a/src/Mod/PartDesign/Gui/Workbench.cpp
+++ b/src/Mod/PartDesign/Gui/Workbench.cpp
@@ -509,9 +509,10 @@ Gui::MenuItem* Workbench::setupMenuBar() const
<< "PartDesign_Boolean"
<< "Separator"
//<< "PartDesign_Hole"
- << "PartDesign_InvoluteGear"
- << "Separator"
- << "PartDesign_Migrate";
+ << "PartDesign_Migrate"
+ << "PartDesign_Sprocket"
+ << "PartDesign_InvoluteGear";
+
// For 0.13 a couple of python packages like numpy, matplotlib and others
// are not deployed with the installer on Windows. Thus, the WizardShaft is
diff --git a/src/Mod/PartDesign/InitGui.py b/src/Mod/PartDesign/InitGui.py
index f25bd79358..c935ffb033 100644
--- a/src/Mod/PartDesign/InitGui.py
+++ b/src/Mod/PartDesign/InitGui.py
@@ -51,6 +51,7 @@ class PartDesignWorkbench ( Workbench ):
import PartDesign
try:
from PartDesign import InvoluteGearFeature
+ from PartDesign import SprocketFeature
except ImportError:
print("Involute gear module cannot be loaded")
#try:
diff --git a/src/Mod/PartDesign/SprocketFeature.py b/src/Mod/PartDesign/SprocketFeature.py
new file mode 100644
index 0000000000..95977518cb
--- /dev/null
+++ b/src/Mod/PartDesign/SprocketFeature.py
@@ -0,0 +1,248 @@
+#***************************************************************************
+#* Copyright (c) 2020 Adam Spontarelli *
+#* *
+#* This program is free software; you can redistribute it and/or modify *
+#* it under the terms of the GNU Lesser General Public License (LGPL) *
+#* as published by the Free Software Foundation; either version 2 of *
+#* the License, or (at your option) any later version. *
+#* for detail see the LICENCE text file. *
+#* *
+#* This program is distributed in the hope that it will be useful, *
+#* but WITHOUT ANY WARRANTY; without even the implied warranty of *
+#* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+#* GNU Library General Public License for more details. *
+#* *
+#* You should have received a copy of the GNU Library General Public *
+#* License along with this program; if not, write to the Free Software *
+#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
+#* USA *
+#* *
+#***************************************************************************
+
+import FreeCAD, Part
+from fcsprocket import fcsprocket
+from fcsprocket import sprocket
+
+if FreeCAD.GuiUp:
+ import FreeCADGui
+ from PySide import QtCore, QtGui
+ from FreeCADGui import PySideUic as uic
+
+__title__="PartDesign SprocketObject management"
+__author__ = "Adam Spontarelli"
+__url__ = "http://www.freecadweb.org"
+
+
+def makeSprocket(name):
+ """
+ makeSprocket(name): makes a Sprocket
+ """
+ obj = FreeCAD.ActiveDocument.addObject("Part::Part2DObjectPython",name)
+ Sprocket(obj)
+ if FreeCAD.GuiUp:
+ ViewProviderSprocket(obj.ViewObject)
+ #FreeCAD.ActiveDocument.recompute()
+ if FreeCAD.GuiUp:
+ body=FreeCADGui.ActiveDocument.ActiveView.getActiveObject("pdbody")
+ part=FreeCADGui.ActiveDocument.ActiveView.getActiveObject("part")
+ if body:
+ body.Group=body.Group+[obj]
+ elif part:
+ part.Group=part.Group+[obj]
+ return obj
+
+class CommandSprocket:
+
+ """
+ the Fem Sprocket command definition
+ """
+
+ def GetResources(self):
+ return {'Pixmap' : 'PartDesign_Sprocket',
+ 'MenuText': QtCore.QT_TRANSLATE_NOOP("PartDesign_Sprocket","Sprocket..."),
+ 'Accel': "",
+ 'ToolTip': QtCore.QT_TRANSLATE_NOOP("PartDesign_Sprocket","Creates or edit the sprocket definition.")}
+
+ def Activated(self):
+
+ FreeCAD.ActiveDocument.openTransaction("Create Sprocket")
+ FreeCADGui.addModule("SprocketFeature")
+ FreeCADGui.doCommand("SprocketFeature.makeSprocket('Sprocket')")
+ FreeCADGui.doCommand("Gui.activeDocument().setEdit(App.ActiveDocument.ActiveObject.Name,0)")
+
+ def IsActive(self):
+ if FreeCAD.ActiveDocument:
+ return True
+ else:
+ return False
+
+
+class Sprocket:
+ """
+ The Sprocket object
+ """
+
+ def __init__(self,obj):
+ self.Type = "Sprocket"
+ obj.addProperty("App::PropertyInteger","NumberOfTeeth","Sprocket","Number of gear teeth")
+ obj.addProperty("App::PropertyLength","Pitch","Sprocket","Chain Pitch")
+ obj.addProperty("App::PropertyLength","RollerDiameter","Sprocket","Roller Diameter")
+ obj.addProperty("App::PropertyString","ANSISize","Sprocket","ANSI Size")
+
+ obj.NumberOfTeeth = 50
+ obj.Pitch = "0.375 in"
+ obj.RollerDiameter = "0.20 in"
+ obj.ANSISize = "35"
+
+ obj.Proxy = self
+
+
+ def execute(self,obj):
+ w = fcsprocket.FCWireBuilder()
+ sprocket.CreateSprocket(w, obj.Pitch.Value, obj.NumberOfTeeth, obj.RollerDiameter.Value)
+
+ sprocketw = Part.Wire([o.toShape() for o in w.wire])
+ obj.Shape = sprocketw
+ obj.positionBySupport();
+ return
+
+
+class ViewProviderSprocket:
+ """
+ A View Provider for the Sprocket object
+ """
+
+ def __init__(self,vobj):
+ vobj.Proxy = self
+
+ def getIcon(self):
+ return ":/icons/PartDesign_Sprocket.svg"
+
+ def attach(self, vobj):
+ self.ViewObject = vobj
+ self.Object = vobj.Object
+
+ def setEdit(self,vobj,mode):
+ taskd = SprocketTaskPanel(self.Object,mode)
+ taskd.obj = vobj.Object
+ taskd.update()
+ FreeCADGui.Control.showDialog(taskd)
+ return True
+
+ def unsetEdit(self,vobj,mode):
+ FreeCADGui.Control.closeDialog()
+ return
+
+ def __getstate__(self):
+ return None
+
+ def __setstate__(self,state):
+ return None
+
+
+class SprocketTaskPanel:
+ """
+ The editmode TaskPanel for Sprocket objects
+ """
+
+ def __init__(self,obj,mode):
+ self.obj = obj
+
+ self.form=FreeCADGui.PySideUic.loadUi(FreeCAD.getHomePath() + "Mod/PartDesign/SprocketFeature.ui")
+ self.form.setWindowIcon(QtGui.QIcon(":/icons/PartDesign_Sprocket.svg"))
+
+ QtCore.QObject.connect(self.form.Quantity_Pitch, QtCore.SIGNAL("valueChanged(double)"), self.pitchChanged)
+ QtCore.QObject.connect(self.form.Quantity_RollerDiameter, QtCore.SIGNAL("valueChanged(double)"), self.rollerDiameterChanged)
+ QtCore.QObject.connect(self.form.spinBox_NumberOfTeeth, QtCore.SIGNAL("valueChanged(int)"), self.numTeethChanged)
+ QtCore.QObject.connect(self.form.comboBox_ANSISize, QtCore.SIGNAL("currentTextChanged(const QString)"), self.ANSISizeChanged)
+
+ self.update()
+
+ if mode == 0: # fresh created
+ self.obj.Proxy.execute(self.obj) # calculate once
+ FreeCAD.Gui.SendMsgToActiveView("ViewFit")
+
+ def transferTo(self):
+ """
+ Transfer from the dialog to the object
+ """
+ self.obj.NumberOfTeeth = self.form.spinBox_NumberOfTeeth.value()
+ self.obj.Pitch = self.form.Quantity_Pitch.text()
+ self.obj.RollerDiameter = self.form.Quantity_RollerDiameter.text()
+ self.obj.ANSISize = self.form.comboBox_ANSISize.currentText()
+
+ def transferFrom(self):
+ """
+ Transfer from the object to the dialog
+ """
+ self.form.spinBox_NumberOfTeeth.setValue(self.obj.NumberOfTeeth)
+ self.form.Quantity_Pitch.setText(self.obj.Pitch.UserString)
+ self.form.Quantity_RollerDiameter.setText(self.obj.RollerDiameter.UserString)
+ self.form.comboBox_ANSISize.setCurrentText(self.obj.ANSISize)
+
+ def pitchChanged(self, value):
+ self.obj.Pitch = value
+ self.obj.Proxy.execute(self.obj)
+ FreeCAD.Gui.SendMsgToActiveView("ViewFit")
+
+ def ANSISizeChanged(self, size):
+ """
+ ANSI B29.1-2011 standard roller chain sizes in USCS units (inches)
+ {size: [Pitch, Roller Diameter]}
+ """
+ ANSIRollerTable = {"25": [0.250, 0.130],
+ "35": [0.375, 0.200],
+ "41": [0.500, 0.306],
+ "40": [0.500, 0.312],
+ "50": [0.625, 0.400],
+ "60": [0.750, 0.469],
+ "80": [1.000, 0.625],
+ "100":[1.250, 0.750],
+ "120":[1.500, 0.875],
+ "140":[1.750, 1.000],
+ "160":[2.000, 1.125],
+ "180":[2.250, 1.460],
+ "200":[2.500, 1.562],
+ "240":[3.000, 1.875]}
+
+ self.obj.Pitch = str(ANSIRollerTable[size][0]) + " in"
+ self.obj.RollerDiameter = str(ANSIRollerTable[size][1]) + " in"
+ self.form.Quantity_Pitch.setText(self.obj.Pitch.UserString)
+ self.form.Quantity_RollerDiameter.setText(self.obj.RollerDiameter.UserString)
+
+ self.obj.Proxy.execute(self.obj)
+ FreeCAD.Gui.SendMsgToActiveView("ViewFit")
+
+ def rollerDiameterChanged(self, value):
+ self.obj.RollerDiameter = value
+ self.obj.Proxy.execute(self.obj)
+
+ def numTeethChanged(self, value):
+ self.obj.NumberOfTeeth = value
+ self.obj.Proxy.execute(self.obj)
+ FreeCAD.Gui.SendMsgToActiveView("ViewFit")
+
+ def getStandardButtons(self):
+ return int(QtGui.QDialogButtonBox.Ok) | int(QtGui.QDialogButtonBox.Cancel)| int(QtGui.QDialogButtonBox.Apply)
+
+ def clicked(self,button):
+ if button == QtGui.QDialogButtonBox.Apply:
+ self.transferTo()
+ self.obj.Proxy.execute(self.obj)
+
+ def update(self):
+ self.transferFrom()
+
+ def accept(self):
+ self.transferTo()
+ FreeCAD.ActiveDocument.recompute()
+ FreeCADGui.ActiveDocument.resetEdit()
+
+ def reject(self):
+ FreeCADGui.ActiveDocument.resetEdit()
+ FreeCAD.ActiveDocument.abortTransaction()
+
+
+
+if FreeCAD.GuiUp:
+ FreeCADGui.addCommand('PartDesign_Sprocket', CommandSprocket())
diff --git a/src/Mod/PartDesign/SprocketFeature.ui b/src/Mod/PartDesign/SprocketFeature.ui
new file mode 100644
index 0000000000..c1960a8837
--- /dev/null
+++ b/src/Mod/PartDesign/SprocketFeature.ui
@@ -0,0 +1,232 @@
+
+
+ SprocketParameter
+
+
+
+ 0
+ 0
+ 195
+ 142
+
+
+
+ Sprocket parameter
+
+
+
+
-
+
+
+ Number of teeth:
+
+
+
+ -
+
+
+ 3
+
+
+ 9999
+
+
+ 50
+
+
+
+
+ -
+
+
+ ANSI Size:
+
+
+
+ -
+
+
+
-
+
+ 25
+
+
+ -
+
+ 35
+
+
+ -
+
+ 41
+
+
+ -
+
+ 40
+
+
+ -
+
+ 50
+
+
+ -
+
+ 60
+
+
+ -
+
+ 80
+
+
+ -
+
+ 100
+
+
+ -
+
+ 120
+
+
+ -
+
+ 140
+
+
+ -
+
+ 160
+
+
+ -
+
+ 180
+
+
+ -
+
+ 200
+
+
+ -
+
+ 240
+
+
+
+
+
+ -
+
+
+
+ Chain Pitch:
+
+
+
+
+ -
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 20
+
+
+
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ in
+
+
+ 3
+
+
+ 2000.000000000000000
+
+
+ 0.01
+
+
+
+ 0.001
+
+
+ 0.375
+
+
+
+
+ -
+
+
+
+ Roller Diameter:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 80
+ 20
+
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ in
+
+
+ 3
+
+
+ 50.000000000000000
+
+
+ 0.01
+
+
+
+ 0.01
+
+
+ 0.20
+
+
+
+
+
+
+
+
+ Gui::InputField
+ QLineEdit
+
+
+
+
+
+
diff --git a/src/Mod/PartDesign/fcsprocket/README.md b/src/Mod/PartDesign/fcsprocket/README.md
new file mode 100644
index 0000000000..3759e676ce
--- /dev/null
+++ b/src/Mod/PartDesign/fcsprocket/README.md
@@ -0,0 +1,24 @@
+================================================
+ FCSprocket: a Sprocket Generator for FreeCAD
+================================================
+
+This is a simple sprocket generation tool. Sprockets are used in combination
+with roller chain to transmit power. The tooth profiles are drawn according
+to ANSI standards from the methods outlined in:
+
+ Standard handbook of chains : chains for power transmission and material
+ handling. Boca Raton: Taylor & Francis, 2006. Print.
+
+ AND
+
+ Oberg, Erik, et al. Machinery's handbook : a reference book for the
+ mechanical engineer, designer, manufacturing engineer, draftsman,
+ toolmaker, and machinist. New York: Industrial Press, 2016. Print.
+
+
+This code is based on the work of David Douard and his implementation of the
+gear generator (fcgear) found in FreeCAD.
+
+
+Copyright 2020 Adam Spontarelli .
+Distributed under the LGPL licence.
diff --git a/src/Mod/PartDesign/fcsprocket/__init__.py b/src/Mod/PartDesign/fcsprocket/__init__.py
new file mode 100644
index 0000000000..8b13789179
--- /dev/null
+++ b/src/Mod/PartDesign/fcsprocket/__init__.py
@@ -0,0 +1 @@
+
diff --git a/src/Mod/PartDesign/fcsprocket/fcsprocket.py b/src/Mod/PartDesign/fcsprocket/fcsprocket.py
new file mode 100644
index 0000000000..533c5d47c5
--- /dev/null
+++ b/src/Mod/PartDesign/fcsprocket/fcsprocket.py
@@ -0,0 +1,105 @@
+# (c) 2014 David Douard
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (LGPL)
+# as published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+# for detail see the LICENCE text file.
+#
+# FCGear is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with FCGear; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+
+from math import cos, sin, pi, acos, asin, atan, sqrt
+
+import FreeCAD, Part
+from FreeCAD import Base, Console
+from . import sprocket
+rotate = sprocket.rotate
+
+def makeSprocket(P, N, Dr):
+ if FreeCAD.ActiveDocument is None:
+ FreeCAD.newDocument("Sprocket")
+ doc = FreeCAD.ActiveDocument
+ w = FCWireBuilder()
+ sprocket.CreateSprocket(w, P, N, Dr)
+ sprocketw = Part.Wire([o.toShape() for o in w.wire])
+ sprocket = doc.addObject("Part::Feature", "Sprocket")
+ sprocket.Shape = sprocketw
+ return sprocket
+
+class FCWireBuilder(object):
+ """A helper class to prepare a Part.Wire object"""
+ def __init__(self):
+ self.pos = None
+ self.theta = 0.0
+ self.wire = []
+
+ def move(self, p):
+ """set current position"""
+ self.pos = Base.Vector(*p)
+
+ def line(self, p):
+ """Add a segment between self.pos and p"""
+ p = rotate(p, self.theta)
+ end = Base.Vector(*p)
+ self.wire.append(Part.LineSegment(self.pos, end))
+ self.pos = end
+
+ def arc(self, p, r, sweep):
+ """"Add an arc from self.pos to p which radius is r
+ sweep (0 or 1) determine the orientation of the arc
+ """
+ p = rotate(p, self.theta)
+ end = Base.Vector(*p)
+ mid = Base.Vector(*(midpoints(p, self.pos, r)[sweep]))
+ self.wire.append(Part.Arc(self.pos, mid, end))
+ self.pos = end
+
+ def curve(self, *points):
+ """Add a Bezier curve from self.pos to points[-1]
+ every other points are the control points of the Bezier curve (which
+ will thus be of degree len(points) )
+ """
+ points = [Base.Vector(*rotate(p, self.theta)) for p in points]
+ bz = Part.BezierCurve()
+ bz.setPoles([self.pos] + points)
+ self.wire.append(bz)
+ self.pos = points[-1]
+
+ def close(self):
+ pass
+
+def midpoints(p1, p2, r):
+ """A very ugly function that returns the midpoint of a p1 and p2
+ on the circle which radius is r and which pass through p1 and
+ p2
+
+ Return the 2 possible solutions
+ """
+ vx, vy = p2[0]-p1[0], p2[1]-p1[1]
+ b = (vx**2 + vy**2)**.5
+ v = (vx/b, vy/b)
+ cosA = b**2 / (2*b*r)
+ A = acos(cosA)
+
+ vx, vy = rotate(v, A)
+ c1 = (p1[0]+r*vx, p1[1]+r*vy)
+ m1x, m1y = ((p1[0]+p2[0])/2 - c1[0], (p1[1]+p2[1])/2 - c1[1])
+ dm1 = (m1x**2+m1y**2)**.5
+ m1x, m1y = (c1[0] + r*m1x/dm1, c1[1] + r*m1y/dm1)
+ m1 = (m1x, m1y)
+
+ vx, vy = rotate(v, -A)
+ c2 = (p1[0]+r*vx, p1[1]+r*vy)
+ m2x, m2y = ((p1[0]+p2[0])/2 - c2[0], (p1[1]+p2[1])/2 - c2[1])
+ dm2 = (m2x**2+m2y**2)**.5
+ m2x, m2y = (c2[0] + r*m2x/dm2, c2[1] + r*m2y/dm2)
+ m2 = (m2x, m2y)
+
+ return m1, m2
diff --git a/src/Mod/PartDesign/fcsprocket/fcsprocketdialog.py b/src/Mod/PartDesign/fcsprocket/fcsprocketdialog.py
new file mode 100644
index 0000000000..7360a7235d
--- /dev/null
+++ b/src/Mod/PartDesign/fcsprocket/fcsprocketdialog.py
@@ -0,0 +1,66 @@
+# (c) 2020 Adam Spontarelli
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (LGPL)
+# as published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+# for detail see the LICENCE text file.
+#
+# FCGear is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with FCGear; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+
+from PySide import QtGui as qt
+import fcsprocket
+import FreeCAD, FreeCADGui
+
+class SprocketCreationFrame(qt.QFrame):
+ def __init__(self, parent=None):
+ super(SprocketCreationFrame, self).__init__(parent)
+ self.P = qt.QSpinBox(value=0.375)
+ self.N = qt.QDoubleSpinBox(value=45)
+ self.Dr = qt.QDoubleSpinBox(value=0.20)
+
+ l = qt.QFormLayout(self)
+ l.setFieldGrowthPolicy(l.ExpandingFieldsGrow)
+ l.addRow('Number of teeth:', self.N)
+ l.addRow('Chain Pitch (in):', self.P)
+ l.addRow('Roller Diameter (in):', self.Dr)
+
+
+class SprocketDialog(qt.QDialog):
+ def __init__(self, parent=None):
+ super(SprocketDialog, self).__init__(parent)
+ self.gc = SprocketCreationFrame()
+
+ btns = qt.QDialogButtonBox.Ok | qt.QDialogButtonBox.Cancel
+ buttonBox = qt.QDialogButtonBox(btns,
+ accepted=self.accept,
+ rejected=self.reject)
+ l = qt.QVBoxLayout(self)
+ l.addWidget(self.gc)
+ l.addWidget(buttonBox)
+ self.setWindowTitle('Sprocket creation dialog')
+
+ def accept(self):
+ if FreeCAD.ActiveDocument is None:
+ FreeCAD.newDocument("Sprocket")
+
+ gear = fcgear.makeSprocket(self.gc.m.value(),
+ self.gc.Z.value(),
+ self.gc.angle.value(),
+ not self.gc.split.currentIndex())
+ FreeCADGui.SendMsgToActiveView("ViewFit")
+ return super(SprocketDialog, self).accept()
+
+
+if __name__ == '__main__':
+ a = qt.QApplication([])
+ w = SprocketDialog()
+ w.show()
+ a.exec_()
diff --git a/src/Mod/PartDesign/fcsprocket/sprocket.py b/src/Mod/PartDesign/fcsprocket/sprocket.py
new file mode 100644
index 0000000000..9c1bc1c6b2
--- /dev/null
+++ b/src/Mod/PartDesign/fcsprocket/sprocket.py
@@ -0,0 +1,139 @@
+# (c) 2020 Adam Spontarelli
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU Lesser General Public License (LGPL)
+# as published by the Free Software Foundation; either version 2 of
+# the License, or (at your option) any later version.
+# for detail see the LICENCE text file.
+#
+# FCGear is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Library General Public License for more details.
+#
+# You should have received a copy of the GNU Library General Public
+# License along with FCGear; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+
+from math import cos, sin, tan, sqrt, radians, acos, atan, asin, degrees
+
+
+def CreateSprocket(w, P, N, Dr):
+ """
+ Create a sprocket
+
+ w is the wirebuilder object (in which the sprocket will be constructed)
+ P is the chain pitch
+ N is the number of teeth
+ Dr is the roller diameter
+
+ Remaining variables can be found in Standard Handbook of Chains
+ """
+ Ds = 1.005 * Dr + (0.003 * 25.4)
+ R = Ds / 2
+ alpha = 35 + 60/N
+ beta = 18 - 56 / N
+ M = 0.8 * Dr * cos(radians(35) + radians(60/N))
+ T = 0.8 * Dr * sin(radians(35) + radians(60/N))
+ E = 1.3025 * Dr + (0.0015 * 25.4)
+ W = 1.4 * Dr * cos(radians(180/N))
+ V = 1.4 * Dr * sin(radians(180/N))
+ F = Dr * (0.8 * cos(radians(18) - radians(56)/N) + 1.4 *
+ cos(radians(17) - radians(64) / N) - 1.3025) - (0.0015 * 25.4)
+ PD = P / (sin(radians(180)/N))
+ H = sqrt(F**2 - (1.4 * Dr - P/2)**2)
+ OD = P * (0.6 + 1/tan(radians(180/N)))
+
+ # The sprocket tooth gullet consists of four segments
+ x0 = 0
+ y0 = PD/2 - R
+
+ # ---- Segment 1 -----
+ alpha = 35 + 60/N
+ x1 = -R * cos(radians(alpha))
+ y1 = PD/2 - R * sin(radians(alpha))
+ arc_end = [x1, y1]
+
+ # ---- Segment 2 -----
+ alpha = 35 + 60/N
+ beta = 18 - 56 / N
+ x2 = M - E * cos(radians(alpha-beta))
+ y2 = T - E * sin(radians(alpha-beta)) + PD/2
+
+ # # ---- Segment 3 -----
+ y2o = y2 - PD/2
+ hyp = sqrt((-W-x2)**2 + (-V-y2o)**2)
+ AP = sqrt(hyp**2 - F**2)
+ gamma = atan((y2o + V)/(x2 + W))
+ alpha = asin(AP / hyp)
+ beta = 180 - (90 - degrees(alpha)) - (90 - degrees(gamma))
+ x3o = AP * sin(radians(beta))
+ y3o = AP * cos(radians(beta))
+ x3 = x2 - x3o
+ y3 = y2 + y3o
+
+ # ---- Segment 4 -----
+ alpha = 180/N
+ m = -1/tan(radians(alpha))
+ yf = PD/2 - V
+ A = 1 + m**2
+ B = 2*m*yf - 2*W
+ C = W**2 + yf**2 - F**2
+ # x4a = (-B - sqrt(B**2 - 4 * A * C)) / (2*A)
+ x4b = (-B + sqrt(B**2 - 4 * A * C)) / (2*A)
+ x4 = -x4b
+ y4 = m * x4
+
+ p0 = [x0,y0]
+ p1 = [x1,y1]
+ p2 = [x2,y2]
+ p3 = [x3,y3]
+ p4 = [x4,y4]
+ p5 = [-x1,y1]
+ p6 = [-x2,y2]
+ p7 = [-x3,y3]
+ p8 = [-x4,y4]
+
+ w.move(p4) # vectors are lists [x,y]
+ w.arc(p3, F, 0)
+ w.line(p2)
+ w.arc(p1, E, 1)
+ w.arc(p0, R, 1)
+
+ # ---- Mirror -----
+ w.arc(p5, R, 1)
+ w.arc(p6, E, 1)
+ w.line(p7)
+ w.arc(p8, F, 0)
+
+ # ---- Polar Array ----
+ alpha = -radians(360/N)
+ for n in range(1,N):
+ # falling gullet slope
+ w.arc(rotate(p3, alpha*n), F, 0)
+ w.line(rotate(p2, alpha*n))
+ w.arc(rotate(p1, alpha*n), E, 1)
+ w.arc(rotate(p0, alpha*n), R, 1)
+
+ # rising gullet slope
+ w.arc(rotate(p5, alpha*n), R, 1)
+ w.line(rotate(p6, alpha*n))
+ w.arc(rotate(p7, alpha*n), E, 0)
+ w.arc(rotate(p8, alpha*n), F, 0)
+
+ w.close()
+
+ return w
+
+
+def rotate(pt, rads):
+ """
+ rotate pt by rads radians about origin
+ """
+ sinA = sin(rads)
+ cosA = cos(rads)
+ return (pt[0] * cosA - pt[1] * sinA,
+ pt[0] * sinA + pt[1] * cosA)
+
+
+